version 0.5.0
[flinks.git] / flinkspkg / Config.py
blob76de18f8132e2961b6b1228457a68dd090ebba9c
1 # Part of flinks
2 # (C) Martin Bays 2008
3 # Released under the terms of the GPLv3
5 from __future__ import unicode_literals
7 import os,sys
8 from string import *
10 from io import StringIO
12 import curses
13 import curses.ascii as ascii
15 import re
17 from .Browser import Browser
18 from .constants import VERSION
20 defaultConfig = "version "+VERSION+"""
21 # options
23 # how many words to flash per minute
24 set wpm 450
26 # "Home page", loaded if no url is passed on the command line
27 set initialUrl http://en.wikipedia.org
29 # show current location in the document?
30 set showPoint True
32 # show abbreviated status line and prompts?
33 set abbreviate False
35 # blank between words?
36 set blankBetween True
38 # how many links to cycle through, and how many of the most recent should be
39 # emphasised
40 set linkDisplayNum 4
41 set linkDisplayEmphNum 1
43 # how many difficult "words" (defined as character strings which are long or
44 # contain nonalphabetic characters) to show
45 set trickyDisplayNum 0
47 # how many documents to keep in the history cache
48 set maxCached 5
50 # how many blanks to insert at the end of a pause/sentence/paragraph
51 set paraBlankSpace 6
52 set sentenceBlankSpace 3
53 set pauseBlankSpace 1
55 # show context when paused?
56 set showContext True
58 # pause at regular intervals?
59 set blink False
60 # pause for blinkTime seconds out of blinkDelay seconds
61 set blinkTime 0.3
62 set blinkDelay 6
63 # wait until the end of a sentence before blinking?
64 set blinkWaitSentence False
66 # speed in "skim mode"
67 set skimWpm 1000
69 # a word ending in '.', '?' or '!' will be considered to end a sentence unless
70 # it matches the following regular expression:
71 set notSentenceEnder (Mr|Mrs|Ms|Dr|Prof|St|Dr|Rd|i\.?e|e\.?g|c\.?f|.*\.\.|)\.$
73 # emphasise words between quotation marks?
74 set boldQuotes False
76 # read out sentences and words as they are flashed?
77 set speech False
79 # commands to output speech audio;
80 # sayGenQuotedSpeech is used when within quotes ('"'), sayGenSpeech otherwise.
81 # '%d' must occur in each, and is replaced with the speed in words per minute.
82 set sayGenSpeech espeak --stdin --stdout -v en-uk -s %d
83 set sayGenQuotedSpeech espeak --stdin --stdout -v en-uk-north -p 55 -s %d
84 # sayPlaySpeech: command to play audio produced by speech commands
85 set sayPlaySpeech aplay -q
88 # keybindings
90 bind > changespeed 25
91 bind k changespeed 25
92 bind K changespeed 100
93 bind Up changespeed 100
94 bind < changespeed -25
95 bind j changespeed -25
96 bind J changespeed -100
97 bind Down changespeed -100
99 bind l seekSentence 1
100 bind L seekParagraph 1
101 bind ^D seekParagraph 1
102 bind Right seekSentence 1
103 bind h seekSentence -1
104 bind H seekParagraph -1
105 bind ^U seekParagraph 1
106 bind Left seekSentence -1
107 bind o seekWord 10
108 bind O seekWord 50
109 bind NPage seekWord 50
110 bind i seekWord -10
111 bind I seekWord -50
112 bind PPage seekWord -50
113 bind . seekWord 1
114 bind ^F seekWord 1
115 bind , seekWord -1
116 bind ^B seekWord -1
117 bind ] seekLink 1
118 bind [ seekLink -1
120 bind ^A seekStart
121 bind Home seekStart
122 bind ^E seekEnd
123 bind End seekEnd
125 bind * skipLinks
127 bind g go
128 bind G goCurrent
130 bind / search 1
131 bind ? search -1
132 bind n searchAgain 1
133 bind N searchAgain -1
135 bind b history -1
136 bind Backspace history -1
137 bind u history 1
139 bind ^R reload
141 bind q quit
143 bind ^L refresh
145 bind 1 followLink 1
146 bind 2 followLink 2
147 bind 3 followLink 3
148 bind 4 followLink 4
149 bind 5 followLink 5
150 bind 6 followLink 6
151 bind 7 followLink 7
152 bind 8 followLink 8
153 bind 9 followLink 9
155 bind 0 count
157 bind m mark
158 bind ' goMark
160 bind $ skim
162 bind ^ toggle speech
164 bind space pause
166 bind : command
168 bind F1 help
171 # shortcuts:
172 # Type "g foo" at the 'Go:' prompt to search with google for "foo".
173 shortcut g http://www.google.com/search?q=%1
174 shortcut wp http://en.wikipedia.org/wiki/%1
176 # Shortcuts can be recursive;
177 shortcut wps g site:en.wikipedia.org %1
178 shortcut wk wp %1
179 shortcut wks wps %1
181 # and can have multiple arguments;
182 shortcut gsite g site:%1 %2
184 # %c expands to the current url;
185 shortcut gs gsite %c %1
187 # and $HOME expands to your home directory.
188 shortcut rss $HOME/.rawdog/out.html
191 class Config:
192 optionsInfo = {
193 # <option name> : <type>
194 'wpm' : int,
195 'initialUrl' : str,
196 'blink' : bool,
197 'blankBetween' : bool,
198 'abbreviate' : bool,
199 'showPoint' : bool,
200 'showContext' : bool,
201 'boldQuotes' : bool,
202 'speech' : bool,
203 'sayGenSpeech' : str,
204 'sayGenQuotedSpeech' : str,
205 'sayPlaySpeech' : str,
206 'notSentenceEnder' : str,
207 'linkDisplayNum' : int,
208 'linkDisplayEmphNum' : int,
209 'trickyDisplayNum' : int,
210 'maxCached' : int,
211 'paraBlankSpace' : int,
212 'sentenceBlankSpace' : int,
213 'pauseBlankSpace' : int,
214 'blinkTime' : float,
215 'blinkDelay' : float,
216 'blinkWaitSentence' : bool,
217 'skimWpm' : int
220 # this list consists of the keys of optionsInfo in the order we want
221 # --help to present them
222 optionNames = [
223 'abbreviate', 'showPoint', 'showContext', 'boldQuotes', 'blankBetween', 'blink',
224 'speech',
225 'wpm', 'skimWpm',
226 'linkDisplayNum', 'linkDisplayEmphNum', 'trickyDisplayNum',
227 'pauseBlankSpace', 'sentenceBlankSpace', 'paraBlankSpace',
228 'blinkTime', 'blinkDelay', 'blinkWaitSentence',
229 'maxCached',
230 'initialUrl', 'sayGenSpeech', 'sayGenQuotedSpeech', 'sayPlaySpeech',
231 'notSentenceEnder',
234 suppressedFromHelp = [
235 'linkDisplayEmphNum', 'initialUrl', 'sayGenSpeech',
236 'sayGenQuotedSpeech', 'sayPlaySpeech', 'notSentenceEnder' ]
238 def __init__(self):
239 self.options = {}
240 self.keyBindings = {}
241 self.urlShortcuts = {}
242 self.loadErrors = []
244 self.load(StringIO(defaultConfig), "[Default Config]")
246 filename = os.getenv('HOME')+'/.flinksrc'
247 try:
248 file = open(filename, 'r')
249 self.load(file, filename)
250 except IOError:
251 # write default config
252 open(filename, 'w').write(defaultConfig)
254 def processArgs(self, argv):
255 from getopt import gnu_getopt, GetoptError
257 shortOptDict = { "wpm": "w", "skimWpm": "W",
258 "showPoint": "p", "showContext": "c",
259 "boldQuotes": "Q",
260 "blankBetween": "b", "blink": "i",
261 "linkDisplayNum": "l", "trickyDisplayNum": "t",
262 "abbreviate": "a", "speech": "s"}
264 def printUsage():
265 def helpStr(optName):
266 short = shortOptDict.get(optName, None)
267 type = self.optionsInfo[optName]
268 if short:
269 if type == bool:
270 shopts = "-%s/%s, " % (short, short.upper())
271 else:
272 shopts = "-%s, " % short
273 else:
274 shopts = ''
275 if type == bool:
276 lopts = "--[no]%s" % optName.lower()
277 elif type in [int,float] :
278 lopts = "--%s=N" % optName.lower()
279 elif type == str:
280 lopts = "--%s=STRING" % optName.lower()
281 helpstr = " "+shopts+lopts
282 helpstr += " "*(30-len(helpstr))
283 helpstr += str(self.options[optName])
284 return helpstr
285 print(("Usage: " + argv[0] +
286 " [OPTION]... [INITIAL_URL]\nOptions: (see ~/.flinksrc for descriptions)\n"
287 " -h, --help\n" +
288 '\n'.join( [helpStr(optName)
289 for optName in self.optionNames
290 if optName not in self.suppressedFromHelp])))
292 longopts = ["help"]
293 shortopts = "h"
294 for optName in self.optionNames:
295 short=shortOptDict.get(optName,None)
296 if self.optionsInfo[optName] == bool:
297 longopts += [optName, "%s" % optName.lower()]
298 longopts += [optName, "no%s" % optName.lower()]
299 if short:
300 shortopts += short
301 shortopts += short.upper()
302 else:
303 longopts.append("%s=" % optName.lower())
304 if short: shortopts += short + ":"
306 try: opts, args = gnu_getopt(argv, shortopts, longopts)
307 except GetoptError as e:
308 print("Error parsing options: " + str(e))
309 print("")
310 printUsage()
311 return 1
313 for opt, val in opts:
314 if opt in ["-h", "--help"]:
315 printUsage()
316 return 0
318 for optName in self.optionNames:
319 short = shortOptDict.get(optName, None)
320 type = self.optionsInfo[optName]
321 if opt == "--"+optName.lower() or (short and opt == "-"+short):
322 if type == bool:
323 self.options[optName] = True
324 elif type in [int,float]:
325 try:
326 self.options[optName] = type(val)
327 except ValueError:
328 print(("--%s: expected %s value" % (
329 optName.lower(), type.__name__)))
330 return 1
331 elif type == str:
332 self.options[optName] = val
334 elif type == bool and opt == "--no"+optName.lower() or (
335 short and opt == "-"+short.upper()):
336 self.options[optName] = False
338 if args[1:]:
339 self.options["initialUrl"] = ' '.join(args[1:])
341 return None
343 def load(self, file, filename):
344 lineNumber = 0
346 def addError(details):
347 self.loadErrors.append("While parsing %s, line %d : %s" %
348 (filename, lineNumber, details))
350 for line in file:
351 lineNumber += 1
352 self.processCommand(line, errorHandler=addError)
354 def processCommand(self, command, errorHandler=(lambda _: None)):
355 words = command.split()
357 if words == [] or words[0][0] == '#':
358 return
360 command = words[0]
362 if command == "version":
363 # ignored for now
364 pass
365 elif command == "set":
366 if (len(words) < 3):
367 errorHandler("usage: set OPTION VALUE")
368 return
370 for option in list(self.optionsInfo.keys()):
371 if words[1].lower() == option.lower():
372 optionType = self.optionsInfo[option]
373 if optionType in [int,float]:
374 try:
375 self.options[option] = optionType(words[2])
376 except ValueError:
377 errorHandler("expected %s value", optionType.__name__)
378 elif optionType == bool:
379 self.options[option] = (words[2].lower() == 'true')
380 elif optionType == str:
381 self.options[option] = ' '.join(words[2:])
382 else:
383 errorHandler("*BUG*: unknown type for option value")
384 return
385 errorHandler("Unknown option: %s" % words[1])
387 elif command == "bind":
388 if (len(words) < 2):
389 errorHandler("usage: bind KEY [FUNCTION [ARGS...]]")
390 return
392 key = None
394 keyName = words[1]
396 if len(keyName) == 1:
398 key = ord(keyName)
399 elif len(keyName) == 2 and keyName[0] == '^':
400 # ^A
401 key = ascii.ctrl(ord(keyName[1]))
402 elif len(keyName) == 3 and keyName[1] == '-' and keyName[0] == 'C':
403 # C-A
404 key = ascii.ctrl(ord(keyName[2]))
405 elif len(keyName) == 3 and keyName[1] == '-' and keyName[0] == 'M':
406 # M-A
407 key = ascii.alt(ord(keyName[2]))
408 elif keyName.lower() == "space":
409 # space (special case)
410 key = ord(' ')
411 elif keyName.lower() == "esc" or keyName.lower() == "escape":
412 # escape (special case)
413 key = 27
414 else:
415 # down
416 try:
417 key = getattr(curses, 'KEY_'+keyName.upper())
418 except AttributeError:
419 pass
421 if key is None:
422 errorHandler("bad key description \"%s\"" % (words[1]))
423 return
425 if len(words) == 2:
426 # clear binding
427 if key in self.keyBindings:
428 del self.keyBindings[key]
429 else:
430 # add binding
431 method = None
432 for possMethod in list(Browser.bindableMethods.keys()):
433 if words[2].lower() == possMethod.lower():
434 method = possMethod
435 if method is None:
436 errorHandler("unknown function \"%s\"" % words[2])
437 return
439 minArgs = Browser.bindableMethods[method][0]
440 maxArgs = Browser.bindableMethods[method][1]
442 stringArgs=False
443 try: stringArgs = Browser.bindableMethods[method][3]
444 except IndexError: pass
446 if len(words) - 3 < minArgs or len(words) - 3 > maxArgs:
447 errorHandler("\"%s\" requires %d-%d arguments" %
448 (method, minArgs, maxArgs))
449 return
451 args = []
452 for arg in words[3:]:
453 if stringArgs:
454 args.append(arg)
455 else:
456 try:
457 args.append(int(arg))
458 except ValueError:
459 errorHandler("expected integer argument")
460 return
462 self.keyBindings[key] = [method, args]
464 elif command=="shortcut":
465 if (len(words) < 3):
466 errorHandler("usage: shortcut KEYWORD URL")
467 return
468 command = ' '.join(words[2:])
470 while re.search('%%%d' % i, command):
471 i+=1
472 numArgs = i-1
473 self.urlShortcuts[words[1]] = [numArgs, command]
475 else:
476 errorHandler("unknown command \"%s\"" % words[0])