remove generated file flinkspkg/readme.py from version control
[flinks.git] / flinkspkg / Config.py
blob1c3ea1997a0cf38e7cc2781a20098a6fed1f3c18
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
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 longopts = ["help"]
265 for optName in self.optionNames:
266 if self.optionsInfo[optName] == bool:
267 longopts += [optName, "%s" % optName.lower()]
268 longopts += [optName, "no%s" % optName.lower()]
269 else:
270 longopts.append("%s=" % optName.lower())
272 opts, args = gnu_getopt(argv, 'hw:pPaAbB', longopts)
274 for opt, val in opts:
275 if opt in ["-h", "--help"]:
276 def helpStr(optName):
277 short = shortOptDict.get(optName, None)
278 type = self.optionsInfo[optName]
279 if short:
280 if type == bool:
281 shopts = "-%s/%s, " % (short, short.upper())
282 else:
283 shopts = "-%s, " % short
284 else:
285 shopts = ''
286 if type == bool:
287 lopts = "--[no]%s" % optName.lower()
288 elif type in [int,float] :
289 lopts = "--%s=N" % optName.lower()
290 elif type == str:
291 lopts = "--%s=STRING" % optName.lower()
292 helpstr = " "+shopts+lopts
293 helpstr += " "*(30-len(helpstr))
294 helpstr += str(self.options[optName])
295 return helpstr
296 print(("Usage: " + argv[0] +
297 " [OPTION]... [INITIAL_URL]\nOptions: (see ~/.flinksrc for descriptions)\n"
298 " -h, --help\n" +
299 '\n'.join( [helpStr(optName)
300 for optName in self.optionNames
301 if optName not in self.suppressedFromHelp])))
302 return 0
304 for optName in self.optionNames:
305 short = shortOptDict.get(optName, None)
306 type = self.optionsInfo[optName]
307 if opt == "--"+optName.lower() or (short and opt == "-"+short):
308 if type == bool:
309 self.options[optName] = True
310 elif type in [int,float]:
311 try:
312 self.options[optName] = type(val)
313 except ValueError:
314 print(("--%s: expected %s value" % (
315 optName.lower(), type.__name__)))
316 return 1
317 elif type == str:
318 self.options[optName] = val
320 elif type == bool and opt == "--no"+optName.lower() or (
321 short and opt == "-"+short.upper()):
322 self.options[optName] = False
324 if args[1:]:
325 self.options["initialUrl"] = ' '.join(args[1:])
327 return None
329 def load(self, file, filename):
330 lineNumber = 0
332 def addError(details):
333 self.loadErrors.append("While parsing %s, line %d : %s" %
334 (filename, lineNumber, details))
336 for line in file:
337 lineNumber += 1
338 self.processCommand(line, errorHandler=addError)
340 def processCommand(self, command, errorHandler=(lambda _: None)):
341 words = command.split()
343 if words == [] or words[0][0] == '#':
344 return
346 command = words[0]
348 if command == "version":
349 # ignored for now
350 pass
351 elif command == "set":
352 if (len(words) < 3):
353 errorHandler("usage: set OPTION VALUE")
354 return
356 for option in list(self.optionsInfo.keys()):
357 if words[1].lower() == option.lower():
358 optionType = self.optionsInfo[option]
359 if optionType in [int,float]:
360 try:
361 self.options[option] = optionType(words[2])
362 except ValueError:
363 errorHandler("expected %s value", optionType.__name__)
364 elif optionType == bool:
365 self.options[option] = (words[2].lower() == 'true')
366 elif optionType == str:
367 self.options[option] = ' '.join(words[2:])
368 else:
369 errorHandler("*BUG*: unknown type for option value")
370 return
371 errorHandler("Unknown option: %s" % words[1])
373 elif command == "bind":
374 if (len(words) < 2):
375 errorHandler("usage: bind KEY [FUNCTION [ARGS...]]")
376 return
378 key = None
380 keyName = words[1]
382 if len(keyName) == 1:
384 key = ord(keyName)
385 elif len(keyName) == 2 and keyName[0] == '^':
386 # ^A
387 key = ascii.ctrl(ord(keyName[1]))
388 elif len(keyName) == 3 and keyName[1] == '-' and keyName[0] == 'C':
389 # C-A
390 key = ascii.ctrl(ord(keyName[2]))
391 elif len(keyName) == 3 and keyName[1] == '-' and keyName[0] == 'M':
392 # M-A
393 key = ascii.alt(ord(keyName[2]))
394 elif keyName.lower() == "space":
395 # space (special case)
396 key = ord(' ')
397 elif keyName.lower() == "esc" or keyName.lower() == "escape":
398 # escape (special case)
399 key = 27
400 else:
401 # down
402 try:
403 key = getattr(curses, 'KEY_'+keyName.upper())
404 except AttributeError:
405 pass
407 if key is None:
408 errorHandler("bad key description \"%s\"" % (words[1]))
409 return
411 if len(words) == 2:
412 # clear binding
413 if key in self.keyBindings:
414 del self.keyBindings[key]
415 else:
416 # add binding
417 method = None
418 for possMethod in list(Browser.bindableMethods.keys()):
419 if words[2].lower() == possMethod.lower():
420 method = possMethod
421 if method is None:
422 errorHandler("unknown function \"%s\"" % words[2])
423 return
425 minArgs = Browser.bindableMethods[method][0]
426 maxArgs = Browser.bindableMethods[method][1]
428 stringArgs=False
429 try: stringArgs = Browser.bindableMethods[method][3]
430 except IndexError: pass
432 if len(words) - 3 < minArgs or len(words) - 3 > maxArgs:
433 errorHandler("\"%s\" requires %d-%d arguments" %
434 (method, minArgs, maxArgs))
435 return
437 args = []
438 for arg in words[3:]:
439 if stringArgs:
440 args.append(arg)
441 else:
442 try:
443 args.append(int(arg))
444 except ValueError:
445 errorHandler("expected integer argument")
446 return
448 self.keyBindings[key] = [method, args]
450 elif command=="shortcut":
451 if (len(words) < 3):
452 errorHandler("usage: shortcut KEYWORD URL")
453 return
454 command = ' '.join(words[2:])
456 while re.search('%%%d' % i, command):
457 i+=1
458 numArgs = i-1
459 self.urlShortcuts[words[1]] = [numArgs, command]
461 else:
462 errorHandler("unknown command \"%s\"" % words[0])