예제 #1
0
def test_basepath_is_none():
    """Blah"""
    with pytest.raises(click.Abort):
        Anki(None)

    with pytest.raises(click.Abort):
        Anki('/non/existing/path')
예제 #2
0
파일: cli.py 프로젝트: ckp95/apy
def tag(query, add_tags, remove_tags):
    """List tags or add/remove tags from matching notes.

    If neither of the options --add-tags or --remove-tags are supplied, then
    this command simply lists all tags.
    """
    with Anki(cfg['base']) as a:
        if add_tags is None and remove_tags is None:
            a.list_tags()
            return

        n_notes = len(list(a.find_notes(query)))
        if n_notes == 0:
            click.echo('No matching notes!')
            raise click.Abort()

        click.echo(f'The operation will be applied to {n_notes} matched notes:')
        a.list_notes(query)
        click.echo('')

        if add_tags is not None:
            click.echo(f'Add tags:    {click.style(add_tags, fg="green")}')
        if remove_tags is not None:
            click.echo(f'Remove tags: {click.style(remove_tags, fg="red")}')

        if not click.confirm(click.style('Continue?', fg='blue')):
            raise click.Abort()

        if add_tags is not None:
            a.change_tags(query, add_tags)

        if remove_tags is not None:
            a.change_tags(query, remove_tags, add=False)
예제 #3
0
def tag(query, add_tags, remove_tags):
    """Add or remove tags from notes that match the query."""
    if add_tags is None and remove_tags is None:
        click.echo(f'Please specify either -a and/or -r to add/remove tags!')
        return

    with Anki(cfg['base']) as a:
        n_notes = len(list(a.find_notes(query)))
        if n_notes == 0:
            click.echo(f'No matching notes!')
            raise click.Abort()

        click.echo(f'The operation will be applied to {n_notes} matched notes:')
        a.list_notes(query)
        click.echo('')

        if add_tags is not None:
            click.echo(f'Add tags:    {click.style(add_tags, fg="green")}')
        if remove_tags is not None:
            click.echo(f'Remove tags: {click.style(remove_tags, fg="red")}')

        if not click.confirm(click.style('Continue?', fg='blue')):
            raise click.Abort()

        if add_tags is not None:
            a.change_tags(query, add_tags)

        if remove_tags is not None:
            a.change_tags(query, remove_tags, add=False)
예제 #4
0
파일: cli.py 프로젝트: ckp95/apy
def add(tags, model, deck):
    """Add notes interactively from terminal.

    Examples:

    \b
        # Add notes to deck "MyDeck" with tags 'my-tag' and 'new-tag'
        apy add -t "my-tag new-tag" -d MyDeck

    \b
        # Ask for the model and the deck for each new card
        apy add -m ASK -d ask
    """
    with Anki(cfg['base']) as a:
        notes = a.add_notes_with_editor(tags, model, deck)

        decks = [a.col.decks.name(c.did) for n in notes for c in n.n.cards()]
        n_notes = len(notes)
        n_decks = len(decks)

        if a.n_decks > 1:
            if n_notes == 1:
                click.echo(f'Added note to deck: {decks[0]}')
            elif n_decks > 1:
                click.echo(f'Added {n_notes} notes to {n_decks} different decks')
            else:
                click.echo(f'Added {n_notes} notes to deck: {decks[0]}')
        else:
            click.echo(f'Added {n_notes} notes')

        if click.confirm('Review added notes?'):
            for i, note in enumerate(notes):
                note.review(i, n_notes, remove_actions=['Abort'])
예제 #5
0
def list_notes(query):
    """List notes that match a given query."""
    from apy.anki import Anki

    with Anki(cfg['base']) as a:
        for note in a.find_notes(query):
            note.print_short()
예제 #6
0
def info():
    """Print some basic statistics."""
    from apy.anki import Anki
    from apy.config import cfg_file

    if cfg_file.exists():
        click.echo(f"Config file:             {cfg_file}")
        for key in cfg.keys():
            click.echo(f"Config loaded:           {key}")
    else:
        click.echo(f"Config file:             Not found")

    with Anki(cfg['base']) as a:
        click.echo(f"Collecton path:          {a.col.path}")
        click.echo(f"Scheduler version:       {a.col.schedVer()}")
        click.echo(f"Number of notes:         {a.col.noteCount()}")
        click.echo(f"Number of cards:         {a.col.cardCount()}")
        click.echo(
            f"Number of cards (due):   {len(a.col.findNotes('is:due'))}")
        click.echo(
            f"Number of marked cards:  {len(a.col.findNotes('tag:marked'))}")

        click.echo(f"Number of decks:         {a.col.decks.count()}")
        for d in sorted(a.deck_names):
            click.echo(f"  - {d}")

        models = sorted(a.model_names)
        click.echo(f"Number of models:        {len(models)}")
        for m in models:
            click.echo(f"  - {m}")
예제 #7
0
def get_empty():
    """Create empty Anki collection"""
    (fd, name) = tempfile.mkstemp(suffix=".anki2")
    os.close(fd)
    os.unlink(name)
    a = Anki(path=name)
    return a
예제 #8
0
def add_single(fields, tags=None, preset=None, model_name=None, deck=None):
    """Add a single note from command line arguments.

    Examples:

    \b
        # Add a note to the default deck
        apy add-single myfront myback

    \b
        # Add a cloze deletion note to the default deck
        apy add-single -m Cloze "cloze {{c1::deletion}}" "extra text"

    \b
        # Add a note to deck "MyDeck" with tags 'my-tag' and 'new-tag'
        apy add-single -t "my-tag new-tag" -d MyDeck myfront myback
    """
    with Anki(**cfg) as a:
        tags_preset = ' '.join(cfg['presets'][preset]['tags'])
        if not tags:
            tags = tags_preset
        else:
            tags += ' ' + tags_preset

        if not model_name:
            model_name = cfg['presets'][preset]['model']

        a.add_notes_single(fields, tags, model_name, deck)
예제 #9
0
파일: cli.py 프로젝트: ckp95/apy
def add_from_file(file, tags):
    """Add notes from Markdown file.

    For input file syntax specification, see docstring for
    markdown_file_to_notes() in convert.py.
    """
    with Anki(cfg['base']) as a:
        notes = a.add_notes_from_file(file, tags)

        decks = [a.col.decks.name(c.did) for n in notes for c in n.n.cards()]
        n_notes = len(notes)
        n_decks = len(decks)

        if a.n_decks > 1:
            if n_notes == 1:
                click.echo(f'Added note to deck: {decks[0]}')
            elif n_decks > 1:
                click.echo(f'Added {n_notes} notes to {n_decks} different decks')
            else:
                click.echo(f'Added {n_notes} notes to deck: {decks[0]}')
        else:
            click.echo(f'Added {n_notes} notes')

        if click.confirm('Review added notes?'):
            for i, note in enumerate(notes):
                note.review(i, n_notes, remove_actions=['Abort'])
예제 #10
0
파일: cli.py 프로젝트: ckp95/apy
def edit_css(model_name, sync_after):
    """Edit the CSS template for the specified model."""
    with Anki(cfg['base']) as a:
        a.edit_model_css(model_name)

        if a.modified and sync_after:
            a.sync()
            a.modified = False
예제 #11
0
파일: cli.py 프로젝트: ckp95/apy
def review(query):
    """Review marked notes."""
    with Anki(cfg['base']) as a:
        notes = list(a.find_notes(query))
        number_of_notes = len(notes)
        for i, note in enumerate(notes):
            if not note.review(i, number_of_notes):
                break
예제 #12
0
파일: test_models.py 프로젝트: Aylexx/apy
def test_rename_model():
    """Test that we can rename models"""
    with Anki(path=testCol, debug=True) as a:
        assert 'MyTest' in a.model_names

        a.rename_model('MyTest', 'NewModelName')

        assert 'NewModelName' in a.model_names
        assert 'MyTest' not in a.model_names
예제 #13
0
def add_from_file(file, tags):
    """Add notes from Markdown file.

    For input file syntax specification, see docstring for
    markdown_file_to_notes() in convert.py.
    """
    with Anki(**cfg) as a:
        notes = a.add_notes_from_file(file, tags)
        _added_notes_postprocessing(a, notes)
예제 #14
0
def review(query):
    """Review marked notes."""
    from apy.anki import Anki

    with Anki(cfg['base']) as a:
        notes = list(a.find_notes(query))
        number_of_notes = len(notes)
        for i, note in enumerate(notes):
            if not _review_note(a, note, i, number_of_notes):
                break
예제 #15
0
def test_decks():
    """Test empty collection"""
    with Anki(path=testCol, debug=True) as a:
        assert a.col.decks.count() == 2
        assert a.col.decks.current()['name'] == 'NewDeck'
        assert list(a.deck_names) == ['Default', 'NewDeck']

        notes = a.add_notes_from_file(testDir + '/' + 'data/deck.md')

        assert a.col.decks.name(notes[0].n.cards()[0].did) == 'Default'
        assert a.col.decks.name(notes[1].n.cards()[0].did) == 'NewDeck'
예제 #16
0
def list_cards(query):
    """List cards that match a given query."""
    from apy.anki import Anki
    from apy.convert import html_to_screen, clean_html

    with Anki(cfg['base']) as a:
        for cid in a.find_cards(query):
            c = a.col.getCard(cid)
            question = html_to_screen(clean_html(c.q())).replace('\n', ' ')
            # answer = html_to_screen(clean_html(c.a())).replace('\n', ' ')
            click.echo(f'lapses: {c.lapses:2d}  ease: {c.factor/10}%  Q: ' +
                       question[:80])
예제 #17
0
def test_change_tags():
    """Test empty collection"""
    with Anki(path=testCol, debug=True) as a:
        a.add_notes_from_file(testDir + '/' + 'data/deck.md')

        query = 'tag:test'
        n_original = len(list(a.find_notes(query)))

        a.change_tags(query, 'testendret')
        assert len(list(a.find_notes('tag:testendret'))) == n_original

        a.change_tags(query, 'test', add=False)
        assert len(list(a.find_notes(query))) == 0
예제 #18
0
def info():
    """Print some basic statistics."""
    if cfg_file.exists():
        click.echo(f"Config file:             {cfg_file}")
        for key in cfg.keys():
            click.echo(f"Config loaded:           {key}")
    else:
        click.echo("Config file:             Not found")

    with Anki(**cfg) as a:
        click.echo(f"Collecton path:          {a.col.path}")
        click.echo(f"Scheduler version:       {a.col.schedVer()}")

        if a.col.decks.count() > 1:
            click.echo("Decks:")
            for name in sorted(a.deck_names):
                click.echo(f"  - {name}")

        sum_notes = a.col.noteCount()
        sum_cards = a.col.cardCount()
        sum_due = len(a.col.findNotes('is:due'))
        sum_marked = len(a.col.findNotes('tag:marked'))
        sum_flagged = len(a.col.findNotes('-flag:0'))
        sum_new = len(a.col.findNotes('is:new'))
        sum_susp = len(a.col.findNotes('is:suspended'))

        click.echo(
            f"\n{'Model':24s} {'notes':>7s} {'cards':>7s} "
            f"{'due':>7s} {'new':>7s} {'susp.':>7s} {'marked':>7s} {'flagged':>7s}"
        )
        click.echo("-" * 80)
        models = sorted(a.model_names)
        for m in models:
            nnotes = len(set(a.col.findNotes(f'"note:{m}"')))
            ncards = len(a.find_cards(f'"note:{m}"'))
            ndue = len(a.find_cards(f'"note:{m}" is:due'))
            nmarked = len(a.find_cards(f'"note:{m}" tag:marked'))
            nflagged = len(a.find_cards(f'"note:{m}" -flag:0'))
            nnew = len(a.find_cards(f'"note:{m}" is:new'))
            nsusp = len(a.find_cards(f'"note:{m}" is:suspended'))
            name = m[:24]
            click.echo(f"{name:24s} {nnotes:7d} {ncards:7d} "
                       f"{ndue:7d} {nnew:7d} {nsusp:7d} "
                       f"{nmarked:7d} {nflagged:7d}")
        click.echo("-" * 80)
        click.echo(f"{'Sum':24s} {sum_notes:7d} {sum_cards:7d} "
                   f"{sum_due:7d} {sum_new:7d} {sum_susp:7d} "
                   f"{sum_marked:7d} {sum_flagged:7d}")
        click.echo("-" * 80)
예제 #19
0
def add(tags, model_name, deck):
    """Add notes interactively from terminal.

    Examples:

    \b
        # Add notes to deck "MyDeck" with tags 'my-tag' and 'new-tag'
        apy add -t "my-tag new-tag" -d MyDeck

    \b
        # Ask for the model and the deck for each new card
        apy add -m ASK -d ask
    """
    with Anki(**cfg) as a:
        notes = a.add_notes_with_editor(tags, model_name, deck)
        _added_notes_postprocessing(a, notes)
예제 #20
0
def tag(query, add_tags, remove_tags):
    """Add/Remove tags to/from notes that match QUERY.

    The default QUERY is "tag:marked OR -flag:0". This default can be
    customized in the config file `~/.config/apy/apy.json`, e.g. with

    \b
    {
      "query": "tag:marked OR tag:leech"
    }

    If neither of the options --add-tags or --remove-tags are supplied, then
    this command simply lists all tags.
    """
    if query:
        query = " ".join(query)
    else:
        query = cfg["query"]

    with Anki(**cfg) as a:
        if add_tags is None and remove_tags is None:
            a.list_tags()
            return

        n_notes = len(list(a.find_notes(query)))
        if n_notes == 0:
            click.echo('No matching notes!')
            raise click.Abort()

        click.echo(
            f'The operation will be applied to {n_notes} matched notes:')
        a.list_notes(query)
        click.echo('')

        if add_tags is not None:
            click.echo(f'Add tags:    {click.style(add_tags, fg="green")}')
        if remove_tags is not None:
            click.echo(f'Remove tags: {click.style(remove_tags, fg="red")}')

        if not click.confirm(click.style('Continue?', fg='blue')):
            raise click.Abort()

        if add_tags is not None:
            a.change_tags(query, add_tags)

        if remove_tags is not None:
            a.change_tags(query, remove_tags, add=False)
예제 #21
0
def list_cards(query, verbose):
    """List cards that match QUERY.

    The default QUERY is "tag:marked OR -flag:0". This default can be
    customized in the config file `~/.config/apy/apy.json`, e.g. with

    \b
    {
      "query": "tag:marked OR tag:leech"
    }
    """
    if query:
        query = " ".join(query)
    else:
        query = cfg["query"]

    with Anki(**cfg) as a:
        a.list_cards(query, verbose)
예제 #22
0
def review(query):
    """Review/Edit notes that match QUERY.

    The default QUERY is "tag:marked OR -flag:0". This default can be
    customized in the config file `~/.config/apy/apy.json`, e.g. with

    \b
    {
      "query": "tag:marked OR tag:leech"
    }
    """
    if query:
        query = " ".join(query)
    else:
        query = cfg["query"]

    with Anki(**cfg) as a:
        notes = list(a.find_notes(query))
        number_of_notes = len(notes)
        for i, note in enumerate(notes):
            if not note.review(i, number_of_notes):
                break
예제 #23
0
def info():
    """Print some basic statistics."""
    if cfg_file.exists():
        click.echo(f"Config file:             {cfg_file}")
        for key in cfg.keys():
            click.echo(f"Config loaded:           {key}")
    else:
        click.echo(f"Config file:             Not found")

    with Anki(cfg['base']) as a:
        click.echo(f"Collecton path:          {a.col.path}")
        click.echo(f"Scheduler version:       {a.col.schedVer()}")

        if a.col.decks.count() > 1:
            click.echo("Decks:")
            for name in sorted(a.deck_names):
                click.echo(f"  - {name}")

        sum_notes = a.col.noteCount()
        sum_cards = a.col.cardCount()
        sum_due = len(a.col.findNotes('is:due'))
        sum_marked = len(a.col.findNotes('tag:marked'))

        click.echo(f"\n{'Model':26s} {'notes':>8s} {'cards':>8s} "
                   f"{'due':>8s} {'marked':>8s}")
        click.echo("-"*62)
        models = sorted(a.model_names)
        for m in models:
            nnotes = len(a.col.findNotes(f"note:'{m}'"))
            ncards = len(a.find_cards(f"note:'{m}'"))
            ndue = len(a.find_cards(f"note:'{m}' is:due"))
            nmarked = len(a.find_cards(f"note:'{m}' tag:marked"))
            click.echo(f"{m:26s} {nnotes:8d} {ncards:8d} "
                       f"{ndue:8d} {nmarked:8d}")
        click.echo("-"*62)
        click.echo(f"{'Sum':26s} {sum_notes:8d} {sum_cards:8d} "
                   f"{sum_due:8d} {sum_marked:8d}")
        click.echo("-"*62)
예제 #24
0
def reposition(position, query):
    """Reposition cards that match QUERY.

    Sets the new position to POSITION and shifts other cards.

    Note that repositioning only works with new cards!
    """
    query = " ".join(query)

    with Anki(**cfg) as a:
        cids = list(a.find_cards(query))
        if not cids:
            click.echo(f'No matching cards for query: {query}!')
            raise click.Abort()

        for cid in cids:
            card = a.col.getCard(cid)
            if card.type != 0:
                click.echo('Can only reposition new cards!')
                raise click.Abort()

        a.col.sched.sortCards(cids, position, 1, False, True)
        a.modified = True
예제 #25
0
파일: cli.py 프로젝트: ckp95/apy
def sync():
    """Synchronize collection with AnkiWeb."""
    with Anki(cfg['base']) as a:
        a.sync()
예제 #26
0
파일: cli.py 프로젝트: ckp95/apy
def list_cards(query, verbose):
    """List cards that match a given query."""
    with Anki(cfg['base']) as a:
        a.list_cards(query, verbose)
예제 #27
0
파일: cli.py 프로젝트: ckp95/apy
def rename(old_name, new_name):
    """Rename model from old_name to new_name."""
    with Anki(cfg['base']) as a:
        a.rename_model(old_name, new_name)
예제 #28
0
def rename(old_name, new_name):
    """Rename model from old_name to new_name."""
    with Anki(**cfg) as a:
        a.rename_model(old_name, new_name)
예제 #29
0
파일: cli.py 프로젝트: ckp95/apy
def check_media():
    """Check media"""
    with Anki(cfg['base']) as a:
        a.check_media()
예제 #30
0
def sync():
    """Synchronize collection with AnkiWeb."""
    with Anki(**cfg) as a:
        a.sync()