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
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 for optName
in self
.optionNames
:
266 if self
.optionsInfo
[optName
] == bool:
267 longopts
+= [optName
, "%s" % optName
.lower()]
268 longopts
+= [optName
, "no%s" % optName
.lower()]
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
]
281 shopts
= "-%s/%s, " % (short
, short
.upper())
283 shopts
= "-%s, " % short
287 lopts
= "--[no]%s" % optName
.lower()
288 elif type in [int,float] :
289 lopts
= "--%s=N" % optName
.lower()
291 lopts
= "--%s=STRING" % optName
.lower()
292 helpstr
= " "+shopts
+lopts
293 helpstr
+= " "*(30-len(helpstr
))
294 helpstr
+= str(self
.options
[optName
])
296 print(("Usage: " + argv
[0] +
297 " [OPTION]... [INITIAL_URL]\nOptions: (see ~/.flinksrc for descriptions)\n"
299 '\n'.join( [helpStr(optName
)
300 for optName
in self
.optionNames
301 if optName
not in self
.suppressedFromHelp
])))
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
):
309 self
.options
[optName
] = True
310 elif type in [int,float]:
312 self
.options
[optName
] = type(val
)
314 print(("--%s: expected %s value" % (
315 optName
.lower(), type.__name
__)))
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
325 self
.options
["initialUrl"] = ' '.join(args
[1:])
329 def load(self
, file, filename
):
332 def addError(details
):
333 self
.loadErrors
.append("While parsing %s, line %d : %s" %
334 (filename
, lineNumber
, details
))
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] == '#':
348 if command
== "version":
351 elif command
== "set":
353 errorHandler("usage: set OPTION VALUE")
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]:
361 self
.options
[option
] = optionType(words
[2])
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:])
369 errorHandler("*BUG*: unknown type for option value")
371 errorHandler("Unknown option: %s" % words
[1])
373 elif command
== "bind":
375 errorHandler("usage: bind KEY [FUNCTION [ARGS...]]")
382 if len(keyName
) == 1:
385 elif len(keyName
) == 2 and keyName
[0] == '^':
387 key
= ascii
.ctrl(ord(keyName
[1]))
388 elif len(keyName
) == 3 and keyName
[1] == '-' and keyName
[0] == 'C':
390 key
= ascii
.ctrl(ord(keyName
[2]))
391 elif len(keyName
) == 3 and keyName
[1] == '-' and keyName
[0] == 'M':
393 key
= ascii
.alt(ord(keyName
[2]))
394 elif keyName
.lower() == "space":
395 # space (special case)
397 elif keyName
.lower() == "esc" or keyName
.lower() == "escape":
398 # escape (special case)
403 key
= getattr(curses
, 'KEY_'+keyName
.upper())
404 except AttributeError:
408 errorHandler("bad key description \"%s\"" % (words
[1]))
413 if key
in self
.keyBindings
:
414 del self
.keyBindings
[key
]
418 for possMethod
in list(Browser
.bindableMethods
.keys()):
419 if words
[2].lower() == possMethod
.lower():
422 errorHandler("unknown function \"%s\"" % words
[2])
425 minArgs
= Browser
.bindableMethods
[method
][0]
426 maxArgs
= Browser
.bindableMethods
[method
][1]
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
))
438 for arg
in words
[3:]:
443 args
.append(int(arg
))
445 errorHandler("expected integer argument")
448 self
.keyBindings
[key
] = [method
, args
]
450 elif command
=="shortcut":
452 errorHandler("usage: shortcut KEYWORD URL")
454 command
= ' '.join(words
[2:])
456 while re
.search('%%%d' % i
, command
):
459 self
.urlShortcuts
[words
[1]] = [numArgs
, command
]
462 errorHandler("unknown command \"%s\"" % words
[0])