Skip to content

Commit 9e5087e

Browse files
committed
Merge pull request hzlzh#38 from james016/master
Add Create New File from template workflow
2 parents e259a2d + f6cd065 commit 9e5087e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1561
-0
lines changed
52.6 KB
Binary file not shown.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# -*- coding: utf-8 -*-
2+
'''
3+
Alfred Python
4+
A simple python module for alfred workflow。
5+
6+
JinnLynn
7+
http://jeeker.net
8+
The MIT License
9+
10+
For more information, see the project page:
11+
https://github.com/JinnLynn/alfred-python
12+
'''
13+
from __future__ import absolute_import, division, unicode_literals
14+
15+
__version__ = '0.3.1'
16+
__author__ = 'JinnLynn <eatfishlin@gmail.com>'
17+
__license__ = 'The MIT License'
18+
__copyright__ = 'Copyright 2013 JinnLynn'
19+
20+
from .core import *
21+
from .feedback import Feedback, Item
22+
from . import util
23+
from . import cache
24+
from . import config
25+
from . import storage
26+
from . import request
Binary file not shown.
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# -*- coding: utf-8 -*-
2+
from __future__ import absolute_import, division, unicode_literals
3+
import os, json, time, shutil, codecs
4+
import hashlib
5+
6+
from . import util
7+
from . import core
8+
9+
# { 'expire_time' : 0, name: '', data' : {} }
10+
11+
_DEFAULT_EXPIRE = 60 * 60 * 24
12+
13+
_cache_dir = os.path.join(core._cache_base_dir, core.bundleID())
14+
15+
def _getFilepath(name):
16+
if not os.path.exists(_cache_dir):
17+
os.makedirs(_cache_dir)
18+
# convert to md5, more safe for file name
19+
return os.path.join(_cache_dir, '{}.json'.format(util.hashDigest(name)))
20+
21+
def _getContent(name):
22+
try:
23+
filepath = _getFilepath(name)
24+
with codecs.open(filepath, 'r', 'utf-8') as f:
25+
return json.load(f)
26+
except:
27+
pass
28+
29+
def set(name, data, expire=_DEFAULT_EXPIRE):
30+
filepath = _getFilepath(name)
31+
try:
32+
cache = {
33+
'expire_time' : time.time() + expire,
34+
'name' : name,
35+
'data' : data
36+
}
37+
with codecs.open(filepath, 'w', 'utf-8') as f:
38+
json.dump(cache, f, indent=4)
39+
except:
40+
pass
41+
42+
def get(name, default=None):
43+
try:
44+
cache = _getContent(name)
45+
if cache['expire_time'] >= time.time():
46+
return cache['data']
47+
except:
48+
pass
49+
delete(name)
50+
return default
51+
52+
def delete(name):
53+
cache_file = _getFilepath(name)
54+
if os.path.exists(cache_file):
55+
os.remove(cache_file)
56+
57+
def clean():
58+
cache_dir = os.path.join(core._cache_base_dir, core.bundleID())
59+
if os.path.exists(cache_dir):
60+
shutil.rmtree(cache_dir)
61+
62+
def cleanExpired():
63+
if not os.path.exists(_cache_dir):
64+
return
65+
to_remove = []
66+
for f in os.listdir(_cache_dir):
67+
if not f.endswith('.json'):
68+
continue
69+
filepath = os.path.join(_cache_dir, f)
70+
try:
71+
with codecs.open(filepath, 'r', 'utf-8') as fp:
72+
cache = json.load(fp)
73+
if cache['expire_time'] < time.time():
74+
to_remove.append(filepath)
75+
except Exception, e:
76+
to_remove.append(filepath)
77+
for f in to_remove:
78+
os.remove(f)
79+
80+
def timeout(name):
81+
try:
82+
cache = _getContent(name)
83+
if cache['expire_time'] >= time.time():
84+
return cache['expire_time'] - time.time()
85+
except:
86+
pass
87+
return -1
Binary file not shown.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# -*- coding: utf-8 -*-
2+
from __future__ import absolute_import, division, unicode_literals
3+
import os, json, codecs
4+
5+
from . import core
6+
7+
def _getFilepath():
8+
config_dir = os.path.join(core._config_base_dir, core.bundleID())
9+
if not os.path.exists(config_dir):
10+
os.makedirs(config_dir)
11+
return os.path.join(config_dir, 'config.json')
12+
13+
def _save(configs):
14+
filepath = _getFilepath()
15+
with codecs.open(filepath, 'w', 'utf-8') as f:
16+
json.dump(configs, f, indent=4)
17+
18+
def getAll():
19+
filepath = _getFilepath()
20+
try:
21+
with codecs.open(filepath, 'r', 'utf-8') as f:
22+
return json.load(f)
23+
except:
24+
pass
25+
return {}
26+
27+
def get(key, default=None):
28+
configs = getAll()
29+
return configs.get(key, default)
30+
31+
def set(**kwargs):
32+
configs = getAll()
33+
for k, v in kwargs.items():
34+
configs[k] = v
35+
_save(configs)
36+
37+
def delete(key):
38+
configs = getAll()
39+
if key not in configs:
40+
return
41+
configs.pop(key)
42+
_save(configs)
43+
44+
def clean():
45+
filepath = _getFilepath()
46+
if os.path.exists(filepath):
47+
os.remove(filepath)
Binary file not shown.
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# -*- coding: utf-8 -*-
2+
from __future__ import absolute_import, division, unicode_literals
3+
import os, sys, subprocess, codecs
4+
import plistlib
5+
from datetime import datetime
6+
import unicodedata
7+
import traceback
8+
9+
from .feedback import Feedback, Item
10+
11+
_bundle_id = None
12+
_config_base_dir = os.path.expanduser('~/Library/Application Support/Alfred 2/Workflow Data/')
13+
_cache_base_dir = os.path.expanduser('~/Library/Caches/com.runningwithcrayons.Alfred-2/Workflow Data/')
14+
_log_base_dir = os.path.expanduser('~/Library/Logs/Alfred 2')
15+
_storage_base_dir = '/tmp/Alfred 2'
16+
17+
PY2 = sys.version_info.major == 2
18+
PY3 = sys.version_info.major == 3
19+
20+
def bundleID():
21+
global _bundle_id
22+
if not _bundle_id:
23+
try:
24+
plist_path = os.path.abspath('./info.plist')
25+
prefs = plistlib.readPlist(plist_path)
26+
_bundle_id = prefs['bundleid'].strip()
27+
if not _bundle_id:
28+
raise ValueError('bundle id missing.')
29+
except:
30+
raiseWithFeedback()
31+
return _bundle_id
32+
33+
def setDefaultEncodingUTF8():
34+
reload(sys)
35+
sys.setdefaultencoding('utf-8')
36+
del sys.setdefaultencoding
37+
38+
def decode(s):
39+
return unicodedata.normalize("NFC", s.decode("utf-8"))
40+
41+
def log(s):
42+
log_dir = os.path.join(_log_base_dir, bundleID())
43+
if not os.path.exists(log_dir):
44+
os.makedirs(log_dir)
45+
now = datetime.now()
46+
log_file = os.path.join(log_dir, '{}.log'.format(now.strftime('%Y-%m-%d')))
47+
log_text = '{}: {}\n'.format(now.strftime('%Y-%m-%d %H:%M:%S.%f'), s)
48+
with codecs.open(log_file, 'a', 'utf-8') as f:
49+
f.write(log_text)
50+
51+
def argv(pos, default=None):
52+
try:
53+
arg = sys.argv[pos]
54+
except:
55+
return default
56+
return arg
57+
58+
def exitWithFeedback(**kwargs):
59+
retcode = kwargs.pop('retcode', 0)
60+
fb = Feedback()
61+
fb.addItem(**kwargs)
62+
fb.output()
63+
sys.exit(retcode)
64+
65+
def exit(msg='', retcode=0):
66+
if msg:
67+
print(msg)
68+
sys.exit(retcode)
69+
70+
def query(word):
71+
scpt = 'tell application "Alfred 2" to search "{}"'.format(word)
72+
subprocess.call(['osascript', '-e', scpt])
73+
74+
def notify(title, subtitle, text='', sound=True):
75+
try:
76+
import objc, AppKit
77+
app = AppKit.NSApplication.sharedApplication()
78+
NSUserNotification = objc.lookUpClass("NSUserNotification")
79+
NSUserNotificationCenter = objc.lookUpClass("NSUserNotificationCenter")
80+
notification = NSUserNotification.alloc().init()
81+
notification.setTitle_(title)
82+
notification.setSubtitle_(subtitle)
83+
notification.setInformativeText_(text)
84+
if sound:
85+
notification.setSoundName_("NSUserNotificationDefaultSoundName")
86+
NSUserNotificationCenter.defaultUserNotificationCenter().scheduleNotification_(notification)
87+
except Exception as e:
88+
log('Notification failed. {}'.format(e))
89+
90+
# ONLY used in 'try...except...'
91+
def raiseWithFeedback(feedback=None):
92+
exc = traceback.format_exc()
93+
if not exc or len(exc.split('\n')) < 4:
94+
return
95+
excs = [s.strip() for s in exc.split('\n')]
96+
item = Item(title=excs[3], subtitle=(': ').join(excs[1:3]), valid=False)
97+
if not isinstance(feedback, Feedback):
98+
exitWithFeedback(item=item)
99+
feedback.addItem(item=item)
100+
feedback.output()
101+
exit()
5.98 KB
Binary file not shown.
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# -*- coding: utf-8 -*-
2+
from __future__ import absolute_import, division, unicode_literals
3+
from xml.etree import ElementTree
4+
import xml.sax.saxutils as saxutils
5+
import os, copy, random
6+
7+
from .util import uid
8+
9+
class Item(object):
10+
def __init__(self, **kwargs):
11+
self.content = {
12+
'title' : kwargs.get('title', ''),
13+
'subtitle' : kwargs.get('subtitle', ''),
14+
'icon' : kwargs.get('icon') if kwargs.get('icon') else 'icon.png'
15+
}
16+
17+
it = kwargs.get('icontype', '').lower()
18+
self.icon_type = it if it in ['fileicon', 'filetype'] else None
19+
20+
valid = kwargs.get('valid', None)
21+
if isinstance(valid, basestring) and valid.lower() == 'no':
22+
valid = 'no'
23+
elif isinstance(valid, bool) and not valid:
24+
valid = 'no'
25+
else:
26+
valid = None
27+
28+
self.attrb = {
29+
'uid' : kwargs.get('uid', uid()),
30+
'arg' : kwargs.get('arg', None),
31+
'valid' : valid,
32+
'autocomplete' : kwargs.get('autocomplete', None),
33+
'type' : kwargs.get('type', None)
34+
}
35+
36+
self.content = dict((k, v) for k, v in self.content.items() if v is not None)
37+
self.attrb = dict((k, v) for k, v in self.attrb.items() if v is not None)
38+
39+
def copy(self):
40+
return copy.copy(self)
41+
42+
def getXMLElement(self):
43+
item = ElementTree.Element('item', self.attrb)
44+
for k, v in self.content.items():
45+
attrb = {}
46+
if k == 'icon' and self.icon_type:
47+
attrb['type'] = self.icon_type
48+
sub = ElementTree.SubElement(item, k, attrb)
49+
sub.text = v
50+
return item
51+
52+
class Feedback(object):
53+
def __init__(self):
54+
self.items = []
55+
56+
def __repr__(self):
57+
return self.get()
58+
59+
def addItem(self, **kwargs):
60+
item = kwargs.pop('item', None)
61+
item = item if isinstance(item, Item) else Item(**kwargs)
62+
self.items.append(item)
63+
64+
def clean(self):
65+
self.items = []
66+
67+
def isEmpty(self):
68+
return not bool(self.items)
69+
70+
def get(self, unescape = False):
71+
ele_tree = ElementTree.Element('items')
72+
for item in self.items:
73+
ele_tree.append(item.getXMLElement())
74+
res = ElementTree.tostring(ele_tree) #! 不要使用encoding='utf-8'等其它编码,防止某些特殊字符使得alfred解析错误
75+
if unescape:
76+
return saxutils.unescape(res)
77+
return res
78+
79+
def output(self):
80+
print(self.get())

0 commit comments

Comments
 (0)