/
ditrit.py
executable file
·269 lines (230 loc) · 7.4 KB
/
ditrit.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
#!/usr/bin/python2
#
# ditrit.py
# Copyright 2006 Ryan Barrett <ditrit@ryanb.org>
# http://snarfed.org/ditrit
#
# See docstring for usage details.
#
# Ideas:
# url open in browser, whois, alexa/netcraft
# email address compose in mail client, add to address book
# email mailing list go to archive page, subscription page
# address map it
# zip code map it
# phone number call, add to phonebook
# person name google, email, call
# ups/fedex/etc package number track
# rpm install
# date ?
# filename find rpm/deb that provides it
# stock symbol quote, charts
# movie name showtimes, reviews, imdb
# song name lyrics, itunes
# musician discography
# actor imdb
# project name show fm, sf
# program name (w/parens) show man page
# aim user name add to buddy list, im
# jabber username add to buddy list, im
# icq uin add to buddy list, im
# local filename open
# tv show tv listings
# author, book title isbn.nu, amazon
# citation citeseer
# misspelling spell correct
#
# google:
# - bug number
# - changelist number
# - username
# - filename
#
#
#
#
# matching
# ==
# 1. regexp
# 2. local file existence test
# 3. local domain tests (in email phonebook, in buddy list, in phonebook, in
# emacs/eclipse/idea tags file?)
# 3. web queries (check for results).
# - do in parallel?
# - use beautiful soup
#
#
# acting
# ==
# provide dotfile with regexp, command line
#
# offer multiple actions and choose one?
#
# web service to provide updated configs?
#
# remember actions for exact strings? regexps?
#
# platform independence! x selection vs windows clipboard (mac os x?),
# wxpython for ui
#
#
# longer-term
# ==
# learn from keywords in top google search results ("film" or "movie"? "song"
# or "artist"? "download"? "program"? etc.) to determine type.
#
# store mappings on server, use web service to determine action.
#
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
USAGE = """A programmable application launcher. Reads input from stdin or
the X primary selection (aka clipboard), finds a template in the config file
that matches it, and runs the corresponding command. The input may be wholly
or partially inserted into the command string.
Options:
-c <file> Read config from file, not ~/.ditritrc
-t Don't actually run the command, just print it
-X Read input from the X primary selection (ie clipboard)
-v Display verbose debugging messages
-V Print version information and exit
-h, --help Display this message
Web: http://snarfed.org/ditrit"""
import getopt
import os
import re
import string
import sys
# constants
VERSION = 'ditrit 0.1'
# default command-line options
CONFIG_FILE = None # defaults to ~/.ditritrc in parse_args
TEST = False
VERBOSE = False
X_SELECTION = False
def main():
templates = read_config()
if X_SELECTION:
input = get_x_selection()
else:
input = sys.stdin.read()
input = input.strip()
log('Read input "%s"' % input)
for (template, cmd) in templates.items():
match = re.search(template, input)
if match:
log('Matched template "%s" with command "%s"' % (template, cmd))
cmd = match.expand(cmd).split()
cmd_string = ' '.join(cmd)
log('Expanded command to "%s"' % cmd_string)
if TEST:
print cmd_string
else:
os.spawnvp(os.P_NOWAIT, cmd[0], cmd)
sys.exit(0)
log('No matching template found.', error=True)
sys.exit(1)
def read_config():
""" Reads the config file from CONFIG_FILE. Returns a dictionary mapping
template regexps to commands. Doesn't use ConfigParser because it splits
at the first : or = it finds, and I need to allow those in templates.
"""
log('Using config file %s' % CONFIG_FILE)
templates = {}
for (line, linenum) in zip(file(CONFIG_FILE), xrange(1, sys.maxint)):
line = line.strip()
if not line or line[0] == '#':
continue
# this regexp matches a line in the ditrit config file. e.g.:
#
# Xtemplate regexpX command # optional comment
#
# where the delimiter X is any character (usually a double quote).
#
# \1 is the delimiter, \2 is the template, \3 is the command.
#
# this question marks means to match non-greedy, so that it stops at
# the first delimiter it sees.
# v
match = re.match(r'^(.)(.+?)\1 ([^#]+)#?', line)
if not match:
log('Error in %s, line %d:\n%s' % (CONFIG_FILE, linenum, line),
error=True)
sys.exit(1)
templates[match.group(2)] = match.group(3).strip()
log('Read config:\n' + str(templates))
return templates
def get_x_selection():
import Xlib.display # from the python X library, http://python-xlib.sf.net/
import Xlib.X
import Xlib.Xatom
display = Xlib.display.Display()
xsel_data_atom = display.intern_atom("XSEL_DATA")
screen = display.screen()
w = screen.root.create_window(0, 0, 2, 2, 0, screen.root_depth)
w.convert_selection(Xlib.Xatom.PRIMARY, # selection
Xlib.Xatom.STRING, # target
xsel_data_atom, # property
Xlib.X.CurrentTime) # time
while True:
e = display.next_event()
if e.type == Xlib.X.SelectionNotify:
break
assert e.property == xsel_data_atom
assert e.target == Xlib.Xatom.STRING
reply = w.get_full_property(xsel_data_atom, Xlib.X.AnyPropertyType)
return reply.value
def parse_args(args):
""" Parse command-line args. See doc comment for details.
"""
global CONFIG_FILE, TEST, VERBOSE, X_SELECTION
try:
options, args = getopt.getopt(args, 'c:tXvVh', 'help')
except getopt.GetoptError:
type, value, traceback = sys.exc_info()
log(value.msg, error=True)
usage()
sys.exit(2)
for option, arg in options:
if option == '-c':
CONFIG_FILE = arg
elif option == '-t':
TEST = True
elif option == '-X':
X_SELECTION = True
elif option == '-v':
VERBOSE = True
elif option == '-V':
version()
sys.exit(0)
elif option in ('-h', '--help'):
usage()
sys.exit(0)
if args:
usage()
sys.exit(2)
if not CONFIG_FILE:
CONFIG_FILE = os.path.join(os.getenv('HOME'), '.ditritrc')
return args
def log(message, error=False):
""" Overly simple logging facility. If anyone knows of a more fully
featured logging facility that *ships with Python*, please let me know!
"""
if error:
print >> sys.stderr, message
elif VERBOSE:
print message
def usage():
print USAGE
def version():
print VERSION
if __name__ == '__main__':
args = parse_args(sys.argv[1:])
main()