Jump to content

DrLulz

Member
  • Posts

    62
  • Joined

  • Last visited

  • Days Won

    1

Posts posted by DrLulz

  1. My initial attempt at an Anki workflow. More to come after boards (have to put away addicting things such as Alfred :mellow:). 
    I welcome any and all suggestions.

     

    Download, GitHub, Packal.
     

    op1mnBZ.png

     

    Requirements:

     

    Commands:

    • :anki
    • :aset
    • :apath

     

    Anki Terminology:

    • Collection = Group of Decks
    • Notes = Collection of Facts
    • Cards = Representation of Facts
    • A note has a one to many relationship with cards, while a card can have only one associated note.

     

    Custom Dark Theme:

    • Cards (notes) created from the workflow use this theme.
    • Once the first card is created (from within workflow) the theme is available within Anki.
    • The theme comes loaded with jQuery v1.11.2 and plugins ZoomMagnific PopupPanzoom (mobile only), and Noty.
    • You can find it in Anki under Tools > Manage Note Types > Alfred Dark.
    • If creating cards from inside Anki the theme has optional fields.
      • Front, F Note
        • Front side, and optional note
      • Back, B Note
        • Back side and optional note
      • class
        • The theme default is to center all text.
        • To adjust text to the left enter left in the class field.
      • Noty
        • Show an optional note on the back-side of the card.
        • Good for reinforcement.
      • http
        • Entering a URL in this field displays a link in the bottom right on the back-side.
        • Accepts www.site.com without http
      • video - Accepts youtube and vimeo urls. Link to the video is displayed in the upper left on the back-side.

     

    Workflow Progression:

     

    1. :aset

    • As of now contains two actionable items.
      • Update collection (manual collection refresh)
      • Set Anki collection path (redirects to :apath)
    • :apath
      • The workflow looks for the Anki collection in the most typical locations.
      • If the path is not found the user will be prompted to enter the path manually.
        • The default directory is /Users/OSX_NAME/Documents/ANKI_USER/collection.anki2
        • The default ANKI_USER created when Anki is first run is User 1. If you have changed this, enter your Anki user name.
      • If the path is found :apath is only useful if needing to switch between collections.

    2. :anki

    • Search the collection for a deck
      • Search by name, or deck id
      • If the deck doesn’t exist you can create a new deck with the query as the title.
    • Select deck
    • Search for notes within selected deck
      • Search by facts, or tags
      • If the card is not found you can create a basic, two-sided card (cloze additions on the to-do list). The theme is the custom dark theme described above.
    • Select card
      • Currently the only option after selecting a card is to modify its tags.
      • tags are entered as #tag1 #tag2 #tag3

     

    Credits:

    • This workflow uses the python workflow library Alfred-Workflow (by deanishe).
    • The internal structure borrows *heavily* from the FuzzyFolders and Reddit workflows (also by deanishe).
    • The new_card.py was written by (guess who) deanishe, as a demo for my edification.

     

    TO-DO:

    • Anki Sync
    • File Action to import csv’s
    • Sort decks by new, reviewing, missed
    • More robust display of deck/card statistics
    • Open Anki to a specific deck
    • Choose model (theme) when adding cards
    • Allow for cloze cards
    • Rename decks
  2. I appreciate the demo. Thank you for that.

     

    I'm going with the leveled approach for the "drill-down". The easier the workflow is to navigate the more I use it, so usability and functionality are my top concerns. 

     

    In the Alfred-Workflow readthedocs tutorial you use argparse, but I've noticed in many of you workflows you use docopt. What's the advantage?

     

    Also, in one of my tests I chained a script filter to a run script which saves cached_data before calling a trigger. Its working ok, but is this disadvantageous? It won't be used too often, as I'm shooting for leveled navigation, but its nice to have options.

  3. So, as always it seems, I need to dig a little deeper before asking questions. My first question is outlined, in detail, in the Alfred-Workflow docs under Persistent Data. Please disregard question 1.

     

    I'm not a programmer, so I'm really looking for a way to think about structuring this project. Though, I'm sure its hard to say given that I don't have more information regarding the desired goal. I'm trying to prevent a complete rework after I'm months into the project, but then again, that may be just what I need to actually learn something. :o

  4. I have a few questions regarding workflow structure, storage, and python how-to's.

     

    Anki is a flashcard program, it is written in python, and has a sqlite database.

     

     

    1. In order to connect to the database I need to supply a path. Since I'm using the Anki module alone (without anki's version of PyQt, aqt), I have to supply the path manually. The way I'm doing this is rather costly, and I would like to "store" the path after a successful connection is made, so further instances of the workflow have access to and use this path. Below, I've covered the most likely paths to Anki, but at a later time I would like to ask the user, through Alfred, to input the path if an error occurs.

     

    As a point of style, is it customary to import a module within a def?

    def get_col():    
        default_locations = [
                             os.environ['HOME']+'/Anki/User 1/collection.anki2',
                             os.environ['HOME']+'/.anki/User 1/collection.anki2',
                             'collection.anki2'
                            ]
        for location in default_locations:
            if os.path.exists(location):
                return location
            else:
                import glob
                home_path = os.path.expanduser('~')
                pattern = (home_path + '/**' + '/Anki' + '/*' + '/collection.anki2')
                for path in glob.glob(pattern):
                    return path
    

    2. When a result is selected with Alfred, and passed to the next script as the query, how can I get the result of the second script to display in Alfred? The wf.add_item doesn't seem to do it. The script 2, at the end of the post, should display the card count.

     

     

    3. Besides querying Anki's database, eventually I would like to add cards from Alfred. The flow of the workflow ideally would be to first select a deck, and then options and deck statistics would display below in the results area. One option would be to add a card to the selected deck. After choosing "Add Card" the user would type in the front side, and by some magic of python, the front side text would display as the first result, the input area would clear and allow for the backside to be entered, which would display as the second result before submitting the change. This is why I'm asking for suggestions for structure. Is there a best way to account for these options by structuring the workflow a particular way?

     

    Further down the road I could allow for changes to the front or back after the initial input so that mistakes on card entry could be corrected. And much further down, I could allow for images to be attached with Alfred's file action. I've been playing with Anki heavily, I've authored a addon, HTML 2 ANKI (repo), and generally feel confident to start merging what I've learned into an Alfred workflow. 

     

     

    4. Within the workflow I've supplied the Anki module, but to import the module I first have to append the path with sys.path.append, else I receive errors, or run the script from the Anki folder. I did make sure to include __init__.py in all subfolders, however, the Anki module's __init__.py is not empty, and this seems to be the source of my frustration.  I won't need the module until I start adding cards with Alfred (Anki says that doing it through the database is not recommended, mentioning that when updating Anki issues may arise), but I wanted to know if appending the path is acceptable here.

     

    I'll be sitting for step1 this summer, so I'm just trying to form a plan at this point, but afterwards I'd like to get this going. Anki's popular for people learning new languages, and med students, so I'm hoping it will be of use for a few people. As always suggestions are most appreciated.

     

     

    Script 1:

    # -*- coding: utf-8 -*-
    
    
    # [Run from subdirectory]
    import os, sys
    #abspath = os.path.abspath(__file__)
    #dname = os.path.dirname(abspath) + '/main'
    #sys.path.append(dname)
    
    # [IMPORT]
    import re
    import sqlite3
    import json
    #from anki import storage, stats
    #from BeautifulSoup import BeautifulSoup, Comment, NavigableString
    from workflow import Workflow
    
    
    log = None
        
    def get_col():
        default_locations = [
                             os.environ['HOME']+'/Anki/User 1/collection.anki2',
                             os.environ['HOME']+'/.anki/User 1/collection.anki2',
                             'collection.anki2'
                            ]
        for location in default_locations:
            if os.path.exists(location):
                return location
            else:
                import glob
                home_path = os.path.expanduser('~')
                pattern = (home_path + '/**' + '/Anki' + '/*' + '/collection.anki2')
                for path in glob.glob(pattern):
                    return path
        return None
    
    
    
    def db_decks(db):
    
        results =[]
        deck_db = db.execute("SELECT decks FROM col")
    
        decks = json.loads(deck_db.fetchone()[0])
    
        for d in decks.items():
            deck = {'title': None, 'id': None}
    
            deck['title'] = d[1]['name']
            deck['id'] = d[0]
            
            log.debug(deck)
            
            if deck['id'] is None:
                continue
    
            results.append(deck)
        
        return results
    
    
    
    def key_for_deck(deck):
        return '{} {}'.format(deck['title'], deck['id'])
    
    
    
    def main(wf):
            
        query = None
    
        if len(wf.args):
            query = wf.args[0]
        
        apath = get_col()
        connection = sqlite3.connect(apath)
    
        decks = db_decks(connection)
    
        if query:
            decks = wf.filter(query, decks, key_for_deck)
    
        if not decks:
            wf.add_item('No items', icon=ICON_WARNING)
            
        for deck in decks:
            wf.add_item(
                title    = deck['title'],
                subtitle = deck['id'],
                arg      = deck['id'] + ' ' + deck['title'] + ' ' + apath,
                valid    = True,
                icon     = 'icon.png')
    
        wf.send_feedback()
    
    
    if __name__ == '__main__':
        wf = Workflow()
        log = wf.logger
        sys.exit(wf.run(main))
    

    Script 2:

     

    1. How can I get it's result to display in Alfred?

    2. With very large decks (many cards), I get an error saying so, I'm thinking this is the sqlite, ideas? 

    # -*- coding: utf-8 -*-
    
    import os, sys, re
    import sqlite3, json
    from workflow import Workflow
    
    log = None    
    
    def db_cards(db, did):
            
        cards_did = db.execute("SELECT COUNT(*) "
                                        "FROM cards "
                                        "WHERE cards.did = ?", (did,) )
                        
        card_count = cards_did.fetchone()
        result = card_count[0]
        
        return result
    
    
    def main(wf):
            
        did, d_title, apath = wf.args[0].split()
        connection = sqlite3.connect(apath)
        count = db_cards(connection, did)
    
        wf.add_item(
            title=d_title,
            subtitle=unicode(count),
            arg=did,
            valid=True,
            icon='icon.png')
    
        wf.send_feedback()
        
        
        
    if __name__ == '__main__':
        wf = Workflow()
        log = wf.logger
        sys.exit(wf.run(main))
    
  5. I have an executable located at ~/Documents/anki/runanki

     

    echo $PATH = /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin

     

    I created an alias in .bash_profile, but I know from searching that Alfred doesn't load the local environment.

     

    I would like to call runanki from a run script, but can't figure it out. I don't really care about using the alias in Alfred.

     

     

    Have tried the following, all of which work in the terminal. What is my hangup?

    /Users/drlulz/Documents/anki/runanki
    cd ~/Documents/anki
    runanki
    shopt -s expand_aliases
    
    if [ -f "${HOME}/.bash_profile" ] ; then
    source "${HOME}/.bash_profile"
    fi
    
    
    runanki
  6. The version I'm using was downloaded last week from Packal. When I type "pocket wf:reset" and press return nothing occurs visibly, its is as if its waiting for an argument. I assume it would prompt me to reauthorize after running the reset?

  7. Paste that text (it will look sort of like gobbedly gook) here.

    (dp0Voutputs
    p1
    (lp2
    (dp3
    Vdescription
    p4
    VPortable Document Format
    p5
    sVarg
    p6
    Vpdf
    p7
    sa(dp8
    g4
    Vnative Haskell
    p9
    sg6
    Vnative
    p10
    sa(dp11
    g4
    VJSON version of native AST
    p12
    sg6
    Vjson
    p13
    sa(dp14
    g4
    Vplain text
    p15
    sg6
    Vplain
    p16
    sa(dp17
    g4
    Vpandoc\u2019s extended markdown
    p18
    sg6
    Vmarkdown
    p19
    sa(dp20
    g4
    Voriginal unextended markdown
    p21
    sg6
    Vmarkdown_strict
    p22
    sa(dp23
    g4
    VPHP Markdown extra extended markdown
    p24
    sg6
    Vmarkdown_phpextra
    p25
    sa(dp26
    g4
    Vgithub extended markdown
    p27
    sg6
    Vmarkdown_github
    p28
    sa(dp29
    g4
    VreStructuredText
    p30
    sg6
    Vrst
    p31
    sa(dp32
    g4
    VXHTML 1
    p33
    sg6
    Vhtml
    p34
    sa(dp35
    g4
    VHTML 5
    p36
    sg6
    Vhtml5
    p37
    sa(dp38
    g4
    VLaTeX
    p39
    sg6
    Vlatex
    p40
    sa(dp41
    g4
    VLaTeX beamer slide show
    p42
    sg6
    Vbeamer
    p43
    sa(dp44
    g4
    VConTeXt
    p45
    sg6
    Vcontext
    p46
    sa(dp47
    g4
    Vgroff man
    p48
    sg6
    Vman
    p49
    sa(dp50
    g4
    VMediaWiki markup
    p51
    sg6
    Vmediawiki
    p52
    sa(dp53
    g4
    VDokuWiki markup
    p54
    sg6
    Vdokuwiki
    p55
    sa(dp56
    g4
    VTextile
    p57
    sg6
    Vtextile
    p58
    sa(dp59
    g4
    VEmacs Org-Mode
    p60
    sg6
    Vorg
    p61
    sa(dp62
    g4
    VGNU Texinfo
    p63
    sg6
    Vtexinfo
    p64
    sa(dp65
    g4
    VOPML
    p66
    sg6
    Vopml
    p67
    sa(dp68
    g4
    VDocBook
    p69
    sg6
    Vdocbook
    p70
    sa(dp71
    g4
    VOpenDocument
    p72
    sg6
    Vopendocument
    p73
    sa(dp74
    g4
    VOpenOffice text document
    p75
    sg6
    Vodt
    p76
    sa(dp77
    g4
    VWord docx
    p78
    sg6
    Vdocx
    p79
    sa(dp80
    g4
    VHaddock markup
    p81
    sg6
    Vhaddock
    p82
    sa(dp83
    g4
    Vrich text format
    p84
    sg6
    Vrtf
    p85
    sa(dp86
    g4
    VEPUB v2 book
    p87
    sg6
    Vepub
    p88
    sa(dp89
    g4
    VEPUB v3
    p90
    sg6
    Vepub3
    p91
    sa(dp92
    g4
    VFictionBook2 e-book
    p93
    sg6
    Vfb2
    p94
    sa(dp95
    g4
    VAsciiDoc
    p96
    sg6
    Vasciidoc
    p97
    sa(dp98
    g4
    VInDesign ICML
    p99
    sg6
    Vicml
    p100
    sa(dp101
    g4
    VSlidy HTML and javascript slide show
    p102
    sg6
    Vslidy
    p103
    sa(dp104
    g4
    VSlideous HTML and javascript slide show
    p105
    sg6
    Vslideous
    p106
    sa(dp107
    g4
    VDZSlides HTML5 + javascript slide show
    p108
    sg6
    Vdzslides
    p109
    sa(dp110
    g4
    Vreveal.js HTML5 + javascript slide show
    p111
    sg6
    Vrevealjs
    p112
    sa(dp113
    g4
    VS5 HTML and javascript slide show
    p114
    sg6
    Vs5
    p115
    sasVinputs
    p116
    (lp117
    (dp118
    Vdescription
    p119
    Vnative Haskell
    p120
    sVarg
    p121
    Vnative
    p122
    sa(dp123
    g119
    VJSON version of native AST
    p124
    sg121
    Vjson
    p125
    sa(dp126
    g119
    Vpandoc\u2019s extended markdown
    p127
    sg121
    Vmarkdown
    p128
    sa(dp129
    g119
    Voriginal unextended markdown
    p130
    sg121
    Vmarkdown_strict
    p131
    sa(dp132
    g119
    VPHP Markdown Extra extended markdown
    p133
    sg121
    Vmarkdown_phpextra
    p134
    sa(dp135
    g119
    Vgithub extended markdown
    p136
    sg121
    Vmarkdown_github
    p137
    sa(dp138
    g119
    VTextile
    p139
    sg121
    Vtextile
    p140
    sa(dp141
    g119
    VreStructuredText
    p142
    sg121
    Vrst
    p143
    sa(dp144
    g119
    VHTML
    p145
    sg121
    Vhtml
    p146
    sa(dp147
    g119
    VDocBook
    p148
    sg121
    Vdocbook
    p149
    sa(dp150
    g119
    Vtxt2tags
    p151
    sg121
    Vt2t
    p152
    sa(dp153
    g119
    Vdocx
    p154
    sg121
    Vdocx
    p155
    sa(dp156
    g119
    VEPUB
    p157
    sg121
    Vepub
    p158
    sa(dp159
    g119
    VOPML
    p160
    sg121
    Vopml
    p161
    sa(dp162
    g119
    VEmacs Org-mode
    p163
    sg121
    Vorg
    p164
    sa(dp165
    g119
    VMediaWiki markup
    p166
    sg121
    Vmediawiki
    p167
    sa(dp168
    g119
    VTWiki markup
    p169
    sg121
    Vtwiki
    p170
    sa(dp171
    g119
    VHaddock markup
    p172
    sg121
    Vhaddock
    p173
    sa(dp174
    g119
    VLaTeX
    p175
    sg121
    Vlatex
    p176
    sas.
  8. Pandoctor 1.0.5

     

    Alfred 2.6 (374)
    Starting debug for 'Pandoctor'
    
    
    [ERROR: alfred.workflow.action.script] 10:51:12 pandoctor.py:1206 DEBUG    {u'<argument>': u'/Users/drlulz/Desktop/Untitled.md',
     u'<flag>': u'in_path',
     u'config': False,
     u'help': False,
     u'launch': False,
     u'run': False,
     u'search': False,
     u'store': True}
    10:51:12 workflow.py:940 DEBUG    Loading cached data from : /Users/drlulz/Library/Caches/com.runningwithcrayons.Alfred-2/Workflow Data/com.hackademic.pandoctor/runner.cache
    10:51:12 workflow.py:940 DEBUG    Loading cached data from : /Users/drlulz/Library/Caches/com.runningwithcrayons.Alfred-2/Workflow Data/com.hackademic.pandoctor/pandoc.cache
    10:51:12 workflow.py:970 DEBUG    Cached data saved at : /Users/drlulz/Library/Caches/com.runningwithcrayons.Alfred-2/Workflow Data/com.hackademic.pandoctor/runner.cache
    [ERROR: alfred.workflow.output.script] 10:51:12 pandoctor.py:1206 DEBUG    {u'<argument>': u'[path]',
     u'<flag>': u'pandoc_inputs',
     u'config': False,
     u'help': False,
     u'launch': True,
     u'run': False,
     u'search': False,
     u'store': False}
    10:51:12 workflow.py:940 DEBUG    Loading cached data from : /Users/drlulz/Library/Caches/com.runningwithcrayons.Alfred-2/Workflow Data/com.hackademic.pandoctor/runner.cache
    10:51:12 workflow.py:940 DEBUG    Loading cached data from : /Users/drlulz/Library/Caches/com.runningwithcrayons.Alfred-2/Workflow Data/com.hackademic.pandoctor/pandoc.cache
    [STDERR: alfred.workflow.input.scriptfilter] 10:51:13 pandoctor.py:1206 DEBUG    {u'<argument>': u'markdown',
     u'<flag>': u'inputs',
     u'config': False,
     u'help': False,
     u'launch': False,
     u'run': False,
     u'search': True,
     u'store': False}
    10:51:13 workflow.py:940 DEBUG    Loading cached data from : /Users/drlulz/Library/Caches/com.runningwithcrayons.Alfred-2/Workflow Data/com.hackademic.pandoctor/runner.cache
    10:51:13 workflow.py:940 DEBUG    Loading cached data from : /Users/drlulz/Library/Caches/com.runningwithcrayons.Alfred-2/Workflow Data/com.hackademic.pandoctor/pandoc.cache
    [ERROR: alfred.workflow.action.script] 10:51:15 pandoctor.py:1206 DEBUG    {u'<argument>': u'markdown',
     u'<flag>': u'in_format',
     u'config': False,
     u'help': False,
     u'launch': False,
     u'run': False,
     u'search': False,
     u'store': True}
    10:51:15 workflow.py:940 DEBUG    Loading cached data from : /Users/drlulz/Library/Caches/com.runningwithcrayons.Alfred-2/Workflow Data/com.hackademic.pandoctor/runner.cache
    10:51:15 workflow.py:940 DEBUG    Loading cached data from : /Users/drlulz/Library/Caches/com.runningwithcrayons.Alfred-2/Workflow Data/com.hackademic.pandoctor/pandoc.cache
    10:51:15 workflow.py:970 DEBUG    Cached data saved at : /Users/drlulz/Library/Caches/com.runningwithcrayons.Alfred-2/Workflow Data/com.hackademic.pandoctor/runner.cache
    [ERROR: alfred.workflow.output.script] 10:51:15 pandoctor.py:1206 DEBUG    {u'<argument>': u'',
     u'<flag>': u'pandoc_outputs',
     u'config': False,
     u'help': False,
     u'launch': True,
     u'run': False,
     u'search': False,
     u'store': False}
    10:51:15 workflow.py:940 DEBUG    Loading cached data from : /Users/drlulz/Library/Caches/com.runningwithcrayons.Alfred-2/Workflow Data/com.hackademic.pandoctor/runner.cache
    10:51:15 workflow.py:940 DEBUG    Loading cached data from : /Users/drlulz/Library/Caches/com.runningwithcrayons.Alfred-2/Workflow Data/com.hackademic.pandoctor/pandoc.cache
    [STDERR: alfred.workflow.input.scriptfilter] 10:51:15 pandoctor.py:1206 DEBUG    {u'<argument>': u'',
     u'<flag>': u'outputs',
     u'config': False,
     u'help': False,
     u'launch': False,
     u'run': False,
     u'search': True,
     u'store': False}
    10:51:15 workflow.py:940 DEBUG    Loading cached data from : /Users/drlulz/Library/Caches/com.runningwithcrayons.Alfred-2/Workflow Data/com.hackademic.pandoctor/runner.cache
    10:51:15 workflow.py:940 DEBUG    Loading cached data from : /Users/drlulz/Library/Caches/com.runningwithcrayons.Alfred-2/Workflow Data/com.hackademic.pandoctor/pandoc.cache
    [STDERR: alfred.workflow.input.scriptfilter] 10:51:16 pandoctor.py:1206 DEBUG    {u'<argument>': u'w',
     u'<flag>': u'outputs',
     u'config': False,
     u'help': False,
     u'launch': False,
     u'run': False,
     u'search': True,
     u'store': False}
    10:51:16 workflow.py:940 DEBUG    Loading cached data from : /Users/drlulz/Library/Caches/com.runningwithcrayons.Alfred-2/Workflow Data/com.hackademic.pandoctor/runner.cache
    10:51:16 workflow.py:940 DEBUG    Loading cached data from : /Users/drlulz/Library/Caches/com.runningwithcrayons.Alfred-2/Workflow Data/com.hackademic.pandoctor/pandoc.cache
    [STDERR: alfred.workflow.input.scriptfilter] 10:51:16 pandoctor.py:1206 DEBUG    {u'<argument>': u'wi',
     u'<flag>': u'outputs',
     u'config': False,
     u'help': False,
     u'launch': False,
     u'run': False,
     u'search': True,
     u'store': False}
    10:51:16 workflow.py:940 DEBUG    Loading cached data from : /Users/drlulz/Library/Caches/com.runningwithcrayons.Alfred-2/Workflow Data/com.hackademic.pandoctor/runner.cache
    10:51:16 workflow.py:940 DEBUG    Loading cached data from : /Users/drlulz/Library/Caches/com.runningwithcrayons.Alfred-2/Workflow Data/com.hackademic.pandoctor/pandoc.cache
    [STDERR: alfred.workflow.input.scriptfilter] 10:51:17 pandoctor.py:1206 DEBUG    {u'<argument>': u'wik',
     u'<flag>': u'outputs',
     u'config': False,
     u'help': False,
     u'launch': False,
     u'run': False,
     u'search': True,
     u'store': False}
    10:51:17 workflow.py:940 DEBUG    Loading cached data from : /Users/drlulz/Library/Caches/com.runningwithcrayons.Alfred-2/Workflow Data/com.hackademic.pandoctor/runner.cache
    10:51:17 workflow.py:940 DEBUG    Loading cached data from : /Users/drlulz/Library/Caches/com.runningwithcrayons.Alfred-2/Workflow Data/com.hackademic.pandoctor/pandoc.cache
    [STDERR: alfred.workflow.input.scriptfilter] 10:51:17 pandoctor.py:1206 DEBUG    {u'<argument>': u'wiki',
     u'<flag>': u'outputs',
     u'config': False,
     u'help': False,
     u'launch': False,
     u'run': False,
     u'search': True,
     u'store': False}
    10:51:17 workflow.py:940 DEBUG    Loading cached data from : /Users/drlulz/Library/Caches/com.runningwithcrayons.Alfred-2/Workflow Data/com.hackademic.pandoctor/runner.cache
    10:51:17 workflow.py:940 DEBUG    Loading cached data from : /Users/drlulz/Library/Caches/com.runningwithcrayons.Alfred-2/Workflow Data/com.hackademic.pandoctor/pandoc.cache
    [ERROR: alfred.workflow.action.script] 10:51:21 pandoctor.py:1206 DEBUG    {u'<argument>': u'mediawiki',
     u'<flag>': u'out_format',
     u'config': False,
     u'help': False,
     u'launch': False,
     u'run': False,
     u'search': False,
     u'store': True}
    10:51:21 workflow.py:940 DEBUG    Loading cached data from : /Users/drlulz/Library/Caches/com.runningwithcrayons.Alfred-2/Workflow Data/com.hackademic.pandoctor/runner.cache
    10:51:21 workflow.py:940 DEBUG    Loading cached data from : /Users/drlulz/Library/Caches/com.runningwithcrayons.Alfred-2/Workflow Data/com.hackademic.pandoctor/pandoc.cache
    10:51:21 workflow.py:970 DEBUG    Cached data saved at : /Users/drlulz/Library/Caches/com.runningwithcrayons.Alfred-2/Workflow Data/com.hackademic.pandoctor/runner.cache
    [ERROR: alfred.workflow.output.script] 10:51:21 pandoctor.py:1206 DEBUG    {u'<argument>': u'',
     u'<flag>': u'pandoc_options',
     u'config': False,
     u'help': False,
     u'launch': True,
     u'run': False,
     u'search': False,
     u'store': False}
    10:51:21 workflow.py:940 DEBUG    Loading cached data from : /Users/drlulz/Library/Caches/com.runningwithcrayons.Alfred-2/Workflow Data/com.hackademic.pandoctor/runner.cache
    10:51:21 workflow.py:940 DEBUG    Loading cached data from : /Users/drlulz/Library/Caches/com.runningwithcrayons.Alfred-2/Workflow Data/com.hackademic.pandoctor/pandoc.cache
    [ERROR: alfred.workflow.input.scriptfilter] Code 1: 10:51:21 pandoctor.py:1206 DEBUG    {u'<argument>': u'',
     u'<flag>': u'options',
     u'config': False,
     u'help': False,
     u'launch': False,
     u'run': False,
     u'search': True,
     u'store': False}
    10:51:21 workflow.py:940 DEBUG    Loading cached data from : /Users/drlulz/Library/Caches/com.runningwithcrayons.Alfred-2/Workflow Data/com.hackademic.pandoctor/runner.cache
    10:51:21 workflow.py:940 DEBUG    Loading cached data from : /Users/drlulz/Library/Caches/com.runningwithcrayons.Alfred-2/Workflow Data/com.hackademic.pandoctor/pandoc.cache
    10:51:21 workflow.py:1275 ERROR    u'options'
    Traceback (most recent call last):
      File "/Users/drlulz/Google Drive/MacBook/Alfred/Alfred.alfredpreferences/workflows/user.workflow.155F404E-339A-46A3-9025-93C99FEF4B1D/workflow/workflow.py", line 1273, in run
        func(self)
      File "pandoctor.py", line 1208, in main
        res = pd.run(args)
      File "pandoctor.py", line 355, in run
        return method()
      File "pandoctor.py", line 442, in search_codepath
        data = getattr(self.pandoc, prop, None)
      File "pandoctor.py", line 174, in options
        return self.data['options']KeyError: u'options'
  9. I got as far as returning the below from python, but was running into many issues in different circumstances.

     

    [(u'insulin', u'http://www.merriam-webster.com/medical/insulin' u'a protein hormone that is synthesized in the pancreas from proinsulin and secreted by the beta cells of the islets of Langerhans, that is essential for the metabolism of carbohydrates, lipids, and proteins, that regulates blood sugar levels by facilitating the uptake of glucose into tissues, by promoting its conversion into glycogen, fatty acids, and triglycerides, and by reducing the release of glucose from the liver, and that when produced in insufficient quantities results in diabetes mellitus '), (u'insulin coma therapy', u'http://www.merriam-webster.com/medical/insulin%20coma%20therapy' <sx>INSULIN SHOCK THERAPY</sx>), (u'insulin{ndash}dependent diabetes', u'http://www.merriam-webster.com/medical/insulin%7Bndash%7Ddependent%20diabetes' <sx>type 1 diabetes</sx>)] 

     

     

    One such issue was encoding. When reading your tutorial, you mention to convert everything initially as its brought in and then convert back on the way out. There seems to be many ways to approach this (u'', unicode(), .decode(), etc), though I may be mixing apples and oranges. How can I reveal the encoding of any given string, so that I can begin to understand this idea.

     

     

    Before your answer I had changed the URL to u'x' + urllib.quote(y) + u'z' + API out of desperation. So the ? and = are inferred from their type? This would make good sense.  In python, would you call {'key': API_KEY} a record, and how is this distinct from dict?

     

    I had given up on str.replace, and was using the below, but the definition still contained some children. Removing BeautifulSoup all together seems optimal to say the least.

           term = word.get('id')
           url = u'http://www.merriam-webster.com/medical/' + urllib.quote(term)
           def_tag = word.find('dt')       
           term_def = def_tag.contents[0] 
    Your last two points I would like to ask questions on after I've digested it a little. 
     
    Also, thank you very much for taking the time.
  10. Here's what I've changed. I not getting an error but I'm not getting results either.

    # encoding: utf-8
    
    from workflow import Workflow, ICON_WEB, web
    from bs4 import BeautifulSoup
    #from lxml import etree
    import sys
    
    
    
    
    API_KEY = 'API'
    
    
    
    
    def _mdict_search(query):
        url = u'http://www.dictionaryapi.com/api/v1/references/medical/xml/' + query + "?key="
        params = dict(auth_token=API_KEY)
        
        r = web.get(url, params)
        r.raise_for_status()
        return _mdict_results(r.content)
    
    
    
    
    def _mdict_results(content):
        soup = BeautifulSoup(content)
        words = soup.find_all(id=True)
        results = []
        for word in words:
            term = word.find_all('entry')
            title = wf.decode(term.replace('<entry id="', '').replace('">', ''))
            wf.logger.debug(title)
            url = u'http://www.merriam-webster.com/medical/' + title
            define = table.find('dt')
            desc = wf.decode(define.replace('<dt>', '').replace('</dt>', '')) # going to be a problem
            results.append((title, url, desc))
        return results
    
    
    
    
    def main(wf):
        query = wf.args[0]
    
    
        def wrapper():
            return _mdict_search(query)
    
    
        #results = wf.cached_data('results', wrapper, max_age=60)
        results = _mdict_search(query)
    
    
        for result in results:
            wf.add_item(
                title=result[0],
                subtitle=result[2],
                arg=result[1],
                valid=True,
                icon=ICON_WEB)
    
    
        wf.send_feedback()
    
    
    if __name__ == '__main__':
        wf = Workflow()
        sys.exit(wf.run(main))

    Where am i going wrong?

  11. Thanks for the switch in perspective.

     

    I read part one of your tutorial twice, the second time very slowly. As an exercise I'm trying to recreate the idea with something I'd use often. Merriam-Webster has a Medical Dictionary, and also an API. The results are returned with xml, so I found a thread where you do something similar. In the example you're grabbing html tags, unless I'm missing some fundamental idea (very likely the case) I didn't see why I couldn't do this with the returned xml. Though, now that I'm writing this I don't see why I'm parsing xml to turn it back to xml other than to display it in Alfred. I still don't think I'm looking at this correctly. It would great if I could get this working similar to your searchio, filtering per keypress. 

     

     

     

    The Dictionary API Returns:

    <entry_list version="1.0">
    <entry id="insulin">
        <hw>in·su·lin</hw>
        <pr>ˈin(t)-s(ə-)lən</pr>
        <sound>
            <wav>insuli01.wav</wav>
            <wpr>!in(t)-s(u-)lun</wpr>
        </sound>
        <fl>noun</fl>
        <def>
            <sensb>
            <sens>
            <dt>
                a protein hormone that is synthesized in the pancreas from proinsulin and secreted by the beta cells of the is            lets of Langerhans, that is essential for the metabolism of carbohydrates, lipids, and proteins, that regulate            s blood sugar levels by facilitating the uptake of glucose into tissues, by promoting its conversion into glyc            ogen, fatty acids, and triglycerides, and by reducing the release of glucose from the liver, and that when pro            duced in insufficient quantities results in diabetes mellitus
                <dx>
                    see
                    <dxt>ILETIN</dxt>
                </dx>
            </dt>
            </sens>
            </sensb>
        </def>
    </entry>
    </entry_list>

    Based on the linked thread, I was trying:

    # encoding: utf-8
    
    from workflow import Workflow, ICON_WEB, web
    from lib import BeautifulSoup
    import sys
    
    
    API_KEY = 'API KEY'
    
    
    def request_mdict_search(query):
        url = u'http://www.dictionaryapi.com/api/v1/references/medical/xml'
        r = web.get(url, query, {'?key=': API_KEY})
    
        r.raise_for_status()
        return parse_mdict_results(r.content)
    
    
    def parse_mdict_results(content):
        soup = BeautifulSoup(content)
        words = soup.findAll('entry')
        results = []
        for word in words:
            part1 = word.find('entry')
            title = wf.decode(part1.replace('<entry id="', '').replace('">', ''))
            wf.logger.debug(title)
            url = u'http://www.merriam-webster.com/medical/' + title
            part2 = table.find('dt')
            desc = wf.decode(part2.replace('<dt>', '').replace('</dt>', '')) # going to be a problem
            results.append((title, url, desc))
        return results
    
    
    def main(wf):
        query = wf.args[0]
    
        def wrapper():
            return request_mdict_search(query)
    
        #results = wf.cached_data('results', wrapper, max_age=60)
        results = request_mdict_search(query)
    
        for result in results:
            wf.add_item(
                title=result[0],
                subtitle=result[1],
                arg=result[1],
                valid=True,
                icon=ICON_WEB)
    
        wf.send_feedback()
    
    if __name__ == '__main__':
        wf = Workflow()
        sys.exit(wf.run(main))

    Ideally I'd have the Title and Subtitle in Alfred as the Word and Definition. Though I'm concerned with <dx> & <dxt> tags inside the definition tag. I would need to ignore these in the result.

     

    Alternatively the website gives a php example.

    <?php
    
    // This function grabs the definition of a word in XML format.
    function grab_xml_definition ($word, $ref, $key)
        {    $uri = "http://www.dictionaryapi.com/api/v1/references/" . urlencode($ref) . "/xml/" .
                        urlencode($word) . "?key=" . urlencode($key);
            return file_get_contents($uri);
        };
    
    $xdef = grab_xml_definition("test", "medical", "API_KEY");
    
    ?>

    Which, if either, of the two directions should I work towards?

  12. I'm not 100% certain what exact usage you have in mind, but I use External Triggers extensively in my Pandoctor workflow to chain actions together. Look that up and see if that might work.

    What I was going for was to start the workflow using a keyword with flags as options. So, something like "export -t -i -400 : tag1 tag2" would export highlights, titlecase text, export images at a width of 400px, and apply tags. Another option is infer spaces (-s) which is dependent on a wordlist. I was trying to speed up this process of inferring spaces by having wordlists suited to different areas, so the list would be shorter. (Enter snafu) If the user choose to infer spaces on their text I wanted the applescript to call up Alfred, have Alfred present my xml, make a selection, and then pass the query back to the Applescript to finish up.

     

    On a different note, am I correct in assuming the xml "autocomplete" has nothing to do with filtering the xml based on user input, but is only to.. well... autocomplete by pressing right arrow? I tried to dissect the Evernote workflow to see how he accomplishes this. It's a bit complicated for my current level, but it looks like he's matching the typed letters to notebook names and then making the xml on the fly. How does one filter results?

     

     

     

    I'm not sure what you're getting at here either. Why do you need to modify proxy.scpt? Two things on this:

     

    (1) Keep your code and your data separate. If you are modifying a script to be run, then you should consider it data. Keep the original (template) in the workflow directory, and then all modified copies should be in either the data or the cache directory.

     

    (2) If you're using Python in other places, then you should try to stick with it as much as possible. But if you want to use a "Choose From List" AppleScript function, then you could also try to call that from Python. Basically, the "Choose from List" AppleScript is just a string that you can invoke via the shell via osascript, so you use can Python to construct the string and call as Python would call any shell script.

     

    (3) AppleScript prompts can be cool, but I always try to keep my use of them to a minimum. If your "Choose From List" comes up with just a couple (say fewer than 9) options, and you want to select only "one" option, then you could always keep everything in a script filter and have the user choose from a list of results there.

    The proxy.scpt serves only one purpose for my workflow, which is to store the value of a variable for later retrieval (Its just one line, property word_list : ""). I cringed a little when I went this direction because I know there has to be a better, more elegant, way to achieve such a simple task. I read about cached data and persistent data, but I'm not sure how to access and write to these directories, and I still don't know if that's overkill for such a simple thing.

     

    In regards to "Choose from list," I was trying to steer clear of having applescript accept input and do that solely from Alfred (better aesthetics). The last part (your #3) was my goal, but I couldn't for the life of me figure a way to get the query back to the running script.

     

     

     

     

    Trying to use Alfred as a dialog box is a pretty hairy thing to try. Can you not split the script into two parts? One that calls Alfred with the list and a second one that Alfred calls with the user's choice?

     

    What is your program/workflow intended to do?

     

    Splitting the script, that sounds like it might work. I will need to read more about cached data so that the original options are passed along to the second script. My confusion lied wholly in the fact that I couldn't see a way to get more input from the user (with Alfred) to modify the running process.

  13. Doing a Skim search with dictation and then advancing page results with Alfred remote. 

     

     

    1. Remote Trigger --> Keyword

    2. Dictate Query --> Skim Search Applescript

    3. Use Run Script on the Remote to advance page results. (icon)

     

     

    The search Applescript is "listening" (not really, but you get the idea) for keypress with: 

     

    on isControlKeyPressed()

    return (do shell script "/usr/bin/python -c 'import Cocoa; print Cocoa.NSEvent.modifierFlags() & Cocoa.NSControlKeyMask > 1'") is "True"

    end isControlKeyPressed

     

    To advance the page use:

     

    tell application id "sevs"

    control key down

    delay 0.2

    control key up

    end tell

  14. I thought I'd put this here, if it's not the right place let me know. The dropbox link below is a somewhat cohesive version of my fork.

     

    A few more questions. 

     

    If I want to include _skimmer.app in the workflow do I have your permission, and if yes what is the correct way to credit you?

     

    In your experience how often do new app releases break workflows? Initially I had everything setup correctly in the above posts, but I never thought Evernote itself was the issue, and I still think its odd that file://localhost will not run from OmniOutliner after Yosemite. 

     

    Dropbox

  15. I've been doing some reading. I didn't want to reply back without a specific question, and I'm not sure if I should start a new thread for this so I'll just post it here until someone tells me otherwise. The wikify workflow was a big help, and I was successful getting crosstalk working between applescript and python.

     

     

    This work around is laughable. Please, someone point me in the right direction.

     
    I am attempting to call the Alfred window from Applescript, make a selection, and then return the result. The idea is described in this post.
     
    I’ve done the following.
     
    tell application "Alfred 2" to search "choose list"
     
    The keyword “choose list” is a script filter with an xml list.
     
    The result/query is passed to a Run Script which modifies a property of another proxy.scpt.
     
    Back to the Applescript, directly after search “choose list”, I retrieve the modified property of proxy.scpt. To ensure the returned property was the result of Script Filter —> Run Script I get the modified time of proxy.scpt and repeat until its greater than the current time, exiting after x many seconds. 
     
    set word_list to my get_wrd_list()
    
    on get_wrd_list()
        set proxy_path to quoted form of ((do shell script "pwd") & "/proxy.scpt")
        set time_start to do shell script "date +%s"
        set time_mod to do shell script "stat -f %m " & proxy_path
    
        repeat until time_mod > time_start
            set time_mod to do shell script "stat -f %m " & proxy_path
            set time_exit to do shell script "date +%s"
            if time_exit - time_start > 10 then exit repeat
        end repeat
    
        set proxy_value to load script ((POSIX file ((do shell script "pwd") & "/proxy.scpt")))
        return proxy_value's word_list 
    end get_wrd_list
     
    Like I said, laughable and borderline ridiculous if not completely.
     
    I’d like to do this in python. I’ve been looking at the Alfred-Workflow documentation, which is extensive and yet tailor made for someone starting out. I’m slowly becoming more proficient, and currently understand 25% of what I read (up from 10% last month), but some of the subtle nuances are lost.
     
     
    How should I go about this?
     
×
×
  • Create New...