3 # Released under the terms of the GPLv3
5 from __future__
import unicode_literals
10 from io
import StringIO
13 import curses
.ascii
as ascii
17 from .Browser
import Browser
18 from .constants
import VERSION
20 defaultConfig
= "version "+VERSION
+"""
23 # how many words to flash per minute
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?
32 # show abbreviated status line and prompts?
35 # blank between words?
38 # how many links to cycle through, and how many of the most recent should be
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
50 # how many blanks to insert at the end of a pause/sentence/paragraph
52 set sentenceBlankSpace 3
55 # show context when paused?
58 # pause at regular intervals?
60 # pause for blinkTime seconds out of blinkDelay seconds
63 # wait until the end of a sentence before blinking?
64 set blinkWaitSentence False
66 # speed in "skim mode"
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?
76 # read out sentences and words as they are flashed?
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
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
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
109 bind NPage seekWord 50
112 bind PPage seekWord -50
133 bind N searchAgain -1
136 bind Backspace history -1
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
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
193 # <option name> : <type>
197 'blankBetween' : bool,
200 'showContext' : bool,
203 'sayGenSpeech' : str,
204 'sayGenQuotedSpeech' : str,
205 'sayPlaySpeech' : str,
206 'notSentenceEnder' : str,
207 'linkDisplayNum' : int,
208 'linkDisplayEmphNum' : int,
209 'trickyDisplayNum' : int,
211 'paraBlankSpace' : int,
212 'sentenceBlankSpace' : int,
213 'pauseBlankSpace' : int,
215 'blinkDelay' : float,
216 'blinkWaitSentence' : bool,
220 # this list consists of the keys of optionsInfo in the order we want
221 # --help to present them
223 'abbreviate', 'showPoint', 'showContext', 'boldQuotes', 'blankBetween', 'blink',
226 'linkDisplayNum', 'linkDisplayEmphNum', 'trickyDisplayNum',
227 'pauseBlankSpace', 'sentenceBlankSpace', 'paraBlankSpace',
228 'blinkTime', 'blinkDelay', 'blinkWaitSentence',
230 'initialUrl', 'sayGenSpeech', 'sayGenQuotedSpeech', 'sayPlaySpeech',
234 suppressedFromHelp
= [
235 'linkDisplayEmphNum', 'initialUrl', 'sayGenSpeech',
236 'sayGenQuotedSpeech', 'sayPlaySpeech', 'notSentenceEnder' ]
240 self
.keyBindings
= {}
241 self
.urlShortcuts
= {}
244 self
.load(StringIO(defaultConfig
), "[Default Config]")
246 filename
= os
.getenv('HOME')+'/.flinksrc'
248 file = open(filename
, 'r')
249 self
.load(file, filename
)
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",
260 "blankBetween": "b", "blink": "i",
261 "linkDisplayNum": "l", "trickyDisplayNum": "t",
262 "abbreviate": "a", "speech": "s"}
265 def helpStr(optName
):
266 short
= shortOptDict
.get(optName
, None)
267 type = self
.optionsInfo
[optName
]
270 shopts
= "-%s/%s, " % (short
, short
.upper())
272 shopts
= "-%s, " % short
276 lopts
= "--[no]%s" % optName
.lower()
277 elif type in [int,float] :
278 lopts
= "--%s=N" % optName
.lower()
280 lopts
= "--%s=STRING" % optName
.lower()
281 helpstr
= " "+shopts
+lopts
282 helpstr
+= " "*(30-len(helpstr
))
283 helpstr
+= str(self
.options
[optName
])
285 print(("Usage: " + argv
[0] +
286 " [OPTION]... [INITIAL_URL]\nOptions: (see ~/.flinksrc for descriptions)\n"
288 '\n'.join( [helpStr(optName
)
289 for optName
in self
.optionNames
290 if optName
not in self
.suppressedFromHelp
])))
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()]
301 shortopts
+= short
.upper()
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
))
313 for opt
, val
in opts
:
314 if opt
in ["-h", "--help"]:
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
):
323 self
.options
[optName
] = True
324 elif type in [int,float]:
326 self
.options
[optName
] = type(val
)
328 print(("--%s: expected %s value" % (
329 optName
.lower(), type.__name
__)))
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
339 self
.options
["initialUrl"] = ' '.join(args
[1:])
343 def load(self
, file, filename
):
346 def addError(details
):
347 self
.loadErrors
.append("While parsing %s, line %d : %s" %
348 (filename
, lineNumber
, details
))
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] == '#':
362 if command
== "version":
365 elif command
== "set":
367 errorHandler("usage: set OPTION VALUE")
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]:
375 self
.options
[option
] = optionType(words
[2])
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:])
383 errorHandler("*BUG*: unknown type for option value")
385 errorHandler("Unknown option: %s" % words
[1])
387 elif command
== "bind":
389 errorHandler("usage: bind KEY [FUNCTION [ARGS...]]")
396 if len(keyName
) == 1:
399 elif len(keyName
) == 2 and keyName
[0] == '^':
401 key
= ascii
.ctrl(ord(keyName
[1]))
402 elif len(keyName
) == 3 and keyName
[1] == '-' and keyName
[0] == 'C':
404 key
= ascii
.ctrl(ord(keyName
[2]))
405 elif len(keyName
) == 3 and keyName
[1] == '-' and keyName
[0] == 'M':
407 key
= ascii
.alt(ord(keyName
[2]))
408 elif keyName
.lower() == "space":
409 # space (special case)
411 elif keyName
.lower() == "esc" or keyName
.lower() == "escape":
412 # escape (special case)
417 key
= getattr(curses
, 'KEY_'+keyName
.upper())
418 except AttributeError:
422 errorHandler("bad key description \"%s\"" % (words
[1]))
427 if key
in self
.keyBindings
:
428 del self
.keyBindings
[key
]
432 for possMethod
in list(Browser
.bindableMethods
.keys()):
433 if words
[2].lower() == possMethod
.lower():
436 errorHandler("unknown function \"%s\"" % words
[2])
439 minArgs
= Browser
.bindableMethods
[method
][0]
440 maxArgs
= Browser
.bindableMethods
[method
][1]
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
))
452 for arg
in words
[3:]:
457 args
.append(int(arg
))
459 errorHandler("expected integer argument")
462 self
.keyBindings
[key
] = [method
, args
]
464 elif command
=="shortcut":
466 errorHandler("usage: shortcut KEYWORD URL")
468 command
= ' '.join(words
[2:])
470 while re
.search('%%%d' % i
, command
):
473 self
.urlShortcuts
[words
[1]] = [numArgs
, command
]
476 errorHandler("unknown command \"%s\"" % words
[0])