rework default shortcuts
[flinks.git] / flinkspkg / Config.py
blob1da80be71755e38d92e8d08d39454420a6159ac3
1 # Part of flinks
2 # (C) Martin Bays 2008
3 # Released under the terms of the GPLv3
5 import os,sys
6 from string import *
8 from StringIO import StringIO
10 import curses
11 import curses.ascii as ascii
13 import re
15 from Browser import Browser
17 defaultConfig = """# options
19 # how many words to flash per minute
20 set wpm 500
22 # "Home page", loaded if no url is passed on the command line
23 set initialUrl http://en.wikipedia.org
25 # show current location in the document?
26 set showPoint True
28 # show abbreviated status line and prompts?
29 set abbreviate False
31 # blank between words?
32 set blankBetween True
34 # how many links to cycle through, and how many of the most recent should be
35 # emphasised
36 set linkDisplayNum 4
37 set linkDisplayEmphNum 1
39 # how many difficult "words" (defined as character strings which are long or
40 # contain nonalphabetic) to show
41 set trickyDisplayNum 3
43 # how many documents to keep in the history cache
44 set maxCached 5
46 # how many blanks to insert at the end of a sentence/paragraph
47 set sentenceBlankSpace 1
48 set paraBlankSpace 2
50 # pause at regular intervals?
51 set blink False
52 # pause for blinkLength milliseconds out of every blinkPeriod milliseconds
53 set blinkLength 300
54 set blinkPeriod 6000
55 # wait until the end of a sentence before blinking?
56 set blinkWaitSentence False
58 # speed in "skim mode"
59 set skimWpm 1000
61 # keybindings
63 bind > changespeed 5
64 bind k changespeed 5
65 bind K changespeed 25
66 bind Up changespeed 25
67 bind < changespeed -5
68 bind j changespeed -5
69 bind J changespeed -25
70 bind Down changespeed -25
72 bind l seekSentence 1
73 bind L seekParagraph 1
74 bind ^D seekParagraph 1
75 bind Right seekSentence 1
76 bind h seekSentence -1
77 bind H seekParagraph -1
78 bind ^U seekParagraph 1
79 bind Left seekSentence -1
80 bind o seekWord 20
81 bind O seekWord 100
82 bind NPage seekWord 100
83 bind i seekWord -20
84 bind I seekWord -100
85 bind PPage seekWord -100
86 bind . seekWord 1
87 bind ^F seekWord 1
88 bind , seekWord -1
89 bind ^B seekWord -1
90 bind ] seekLink 1
91 bind [ seekLink -1
93 bind ^A seekStart
94 bind Home seekStart
95 bind ^E seekEnd
96 bind End seekEnd
98 bind g go
99 bind G goCurrent
101 bind / search 1
102 bind ? search -1
103 bind n searchAgain 1
104 bind N searchAgain -1
106 bind b history -1
107 bind Backspace history -1
108 bind u history 1
110 bind ^R reload
112 bind q quit
114 bind ^L refresh
116 bind 1 followLink 1
117 bind 2 followLink 2
118 bind 3 followLink 3
119 bind 4 followLink 4
120 bind 5 followLink 5
121 bind 6 followLink 6
122 bind 7 followLink 7
123 bind 8 followLink 8
124 bind 9 followLink 9
126 bind 0 count
128 bind m mark
129 bind ' goMark
131 bind $ skim
133 bind space pause
135 bind : command
138 # shortcuts:
139 # Type "g foo" at the 'Go:' prompt to search with google for "foo".
140 shortcut g http://www.google.com/search?q=%1
141 shortcut wp http://en.wikipedia.org/wiki/%1
143 # Shortcuts can be recursive;
144 shortcut wps g site:en.wikipedia.org %1
145 shortcut wk wp %1
146 shortcut wks wps %1
148 # and can have multiple arguments;
149 shortcut gsite g site:%1 %2
151 # %c expands to the current url;
152 shortcut gs gsite %c %1
154 # and $HOME expands to your home directory.
155 shortcut rss $HOME/.rawdog/out.html
158 class Config:
159 optionsInfo = {
160 # <option name> : <type>
161 'wpm' : int,
162 'initialUrl' : str,
163 'blink' : bool,
164 'blankBetween' : bool,
165 'abbreviate' : bool,
166 'showPoint' : bool,
167 'linkDisplayNum' : int,
168 'linkDisplayEmphNum' : int,
169 'trickyDisplayNum' : int,
170 'maxCached' : int,
171 'paraBlankSpace' : int,
172 'sentenceBlankSpace' : int,
173 'blinkPeriod' : int,
174 'blinkLength' : int,
175 'blinkWaitSentence' : bool,
176 'skimWpm' : int
179 # this list consists of the keys of optionsInfo in the order we want
180 # --help to present them
181 optionNames = [
182 'wpm', 'initialUrl', 'maxCached', 'abbreviate', 'showPoint',
183 'linkDisplayNum', 'linkDisplayEmphNum', 'trickyDisplayNum',
184 'paraBlankSpace', 'sentenceBlankSpace', 'blankBetween',
185 'blink', 'blinkPeriod', 'blinkLength', 'blinkWaitSentence',
186 'skimWpm' ]
188 def __init__(self):
189 self.options = {}
190 self.keyBindings = {}
191 self.urlShortcuts = {}
192 self.loadErrors = []
194 self.load(StringIO(defaultConfig), "[Default Config]")
196 filename = os.getenv('HOME')+'/.flinksrc'
197 try:
198 file = open(filename, 'r')
199 self.load(file, filename)
200 except IOError:
201 # write default config
202 open(filename, 'w').write(defaultConfig)
204 def processArgs(self, argv):
205 from getopt import gnu_getopt
207 shortOptDict = { "wpm": "w", "showPoint": "p",
208 "abbreviate": "a", "blink": "b" }
210 longopts = ["help"]
211 for optName in self.optionNames:
212 if self.optionsInfo[optName] == bool:
213 longopts += [optName, "%s" % optName.lower()]
214 longopts += [optName, "no%s" % optName.lower()]
215 else:
216 longopts.append("%s=" % optName.lower())
218 opts, args = gnu_getopt(argv, 'hw:pPaAbB', longopts)
220 for opt, val in opts:
221 if opt in ["-h", "--help"]:
222 def helpStr(optName):
223 short = shortOptDict.get(optName, None)
224 type = self.optionsInfo[optName]
225 if short:
226 if type == bool:
227 shopts = "-%s/%s, " % (short, short.upper())
228 else:
229 shopts = "-%s, " % short
230 else:
231 shopts = ''
232 if type == bool:
233 lopts = "--[no]%s" % optName.lower()
234 elif type == int:
235 lopts = "--%s=N" % optName.lower()
236 elif type == str:
237 lopts = "--%s=STRING" % optName.lower()
238 return " "+shopts+lopts
239 print ("Usage: " + argv[0] +
240 " [OPTION]... [INITIAL_URL]\nOptions:\n"
241 " -h, --help\n" +
242 join( [helpStr(optName)
243 for optName in self.optionNames
244 if optName != "initialUrl"], '\n'))
245 return 0
247 for optName in self.optionNames:
248 short = shortOptDict.get(optName, None)
249 type = self.optionsInfo[optName]
250 if opt == "--"+optName.lower() or (short and opt == "-"+short):
251 if type == bool:
252 self.options[optName] = True
253 elif type == int:
254 try:
255 self.options[optName] = int(val)
256 except ValueError:
257 print "--%s: expected integer value" % optName.lower()
258 return 1
259 elif type == str:
260 self.options[optName] = val
262 elif type == bool and opt == "--no"+optName.lower() or (
263 short and opt == "-"+short.upper()):
264 self.options[optName] = False
266 if args[1:]:
267 self.options["initialUrl"] = join(args[1:])
269 return None
271 def load(self, file, filename):
272 lineNumber = 0
274 def addError(details):
275 self.loadErrors.append("While parsing %s, line %d : %s" %
276 (filename, lineNumber, details))
278 for line in file:
279 lineNumber += 1
280 self.processCommand(line, errorHandler=addError)
282 def processCommand(self, command, errorHandler=(lambda _: None)):
283 words = split(command)
285 if words == [] or words[0][0] == '#':
286 return
288 command = words[0]
290 if command == "set":
291 if (len(words) < 3):
292 errorHandler("usage: set OPTION VALUE")
293 return
295 for option in self.optionsInfo.keys():
296 if words[1].lower() == option.lower():
297 optionType = self.optionsInfo[option]
298 if optionType == int:
299 try:
300 self.options[option] = int(words[2])
301 except ValueError:
302 errorHandler("expected integer value")
303 elif optionType == bool:
304 self.options[option] = (words[2].lower() == 'true')
305 elif optionType == str:
306 self.options[option] = words[2]
307 else:
308 errorHandler("*BUG*: unknown type for option value")
309 return
310 errorHandler("Unknown option: %s" % words[1])
312 elif command == "bind":
313 if (len(words) < 2):
314 errorHandler("usage: bind KEY [FUNCTION [ARGS...]]")
315 return
317 key = None
319 keyName = words[1]
321 if len(keyName) == 1:
323 key = ord(keyName)
324 elif len(keyName) == 2 and keyName[0] == '^':
325 # ^A
326 key = ord(ascii.ctrl(keyName[1]))
327 elif len(keyName) == 3 and keyName[1] == '-' and keyName[0] == 'C':
328 # C-A
329 key = ord(ascii.ctrl(keyName[2]))
330 elif len(keyName) == 3 and keyName[1] == '-' and keyName[0] == 'M':
331 # M-A
332 key = ord(ascii.alt(keyName[2]))
333 elif keyName.lower() == "space":
334 # space (special case)
335 key = ord(' ')
336 elif keyName.lower() == "esc" or keyName.lower() == "escape":
337 # escape (special case)
338 key = 27
339 else:
340 # down
341 try:
342 key = getattr(curses, 'KEY_'+keyName.upper())
343 except AttributeError:
344 pass
346 if key is None:
347 errorHandler("bad key description \"%s\"" % (words[1]))
348 return
350 if len(words) == 2:
351 # clear binding
352 if self.keyBindings.has_key(key):
353 del self.keyBindings[key]
354 else:
355 # add binding
356 method = None
357 for possMethod in Browser.bindableMethods.keys():
358 if words[2].lower() == possMethod.lower():
359 method = possMethod
360 if method is None:
361 errorHandler("unknown function \"%s\"" % words[2])
362 return
364 minArgs = Browser.bindableMethods[method][0]
365 maxArgs = Browser.bindableMethods[method][1]
367 if len(words) - 3 < minArgs or len(words) - 3 > maxArgs:
368 errorHandler("\"%s\" requires %d-%d arguments" %
369 (method, minArgs, maxArgs))
370 return
372 args = []
373 for arg in words[3:]:
374 try:
375 args.append(int(arg))
376 except ValueError:
377 errorHandler("expected integer argument")
378 return
380 self.keyBindings[key] = [method, args]
382 elif command=="shortcut":
383 if (len(words) < 3):
384 errorHandler("usage: shortcut KEYWORD URL")
385 return
386 command = join(words[2:])
388 while re.search('%%%d' % i, command):
389 i+=1
390 numArgs = i-1
391 self.urlShortcuts[words[1]] = [numArgs, command]
393 else:
394 errorHandler("unknown command \"%s\"" % words[0])