2 # -*- coding: utf-8 -*-
5 # This file is part of LyX, the document processor.
6 # Licence details can be found in the file COPYING.
10 # Full author contact details are available in file CREDITS
13 # gen_lfuns.py <path/to/LyXAction.cpp> <where/to/save/LFUNs.lyx>
19 sys.stderr.write(message + '\n')
23 return "Usage: %s <path/to/LyXAction.cpp> [<where/to/save/LFUNs.lyx>]" % prog_name
28 LYX_NEWLINE = u"\n\\begin_inset Newline newline\n\\end_inset\n\n"
29 LYX_BACKSLASH = u"\n\\backslash\n"
31 HTMLONLY_START = u"\\htmlonly"
32 HTMLONLY_END = u"\\endhtmlonly"
33 LFUN_NAME_ID = u"\\var lyx::FuncCode lyx::"
34 LFUN_ACTION_ID = u"\\li Action: "
35 LFUN_NOTION_ID = u"\\li Notion: "
36 LFUN_SYNTAX_ID = u"\\li Syntax: "
37 LFUN_PARAMS_ID = u"\\li Params: "
38 LFUN_SAMPLE_ID = u"\\li Sample: "
39 LFUN_ORIGIN_ID = u"\\li Origin: "
40 LFUN_ENDVAR = u"\\endvar"
42 ID_DICT = dict(name=LFUN_NAME_ID, action=LFUN_ACTION_ID, notion=LFUN_NOTION_ID,
43 syntax=LFUN_SYNTAX_ID, params=LFUN_PARAMS_ID, sample=LFUN_SAMPLE_ID, origin=LFUN_ORIGIN_ID)
45 LFUNS_HEADER = u"""# gen_lfuns.py generated this file. For more info see http://www.lyx.org/
49 \\save_transient_properties true
50 \\origin /systemlyxdir/doc/
53 \\renewcommand{\\descriptionlabel}[1]{\\hspace\\labelsep\\upshape\\bfseries #1:}
54 \\renewenvironment{description}{\\list{}{%
55 \\setlength{\\itemsep}{-2pt}
56 \\advance\\leftmargini6\\p@ \\itemindent-12\\p@
57 \\labelwidth\\z@ \\let\\makelabel\\descriptionlabel}%
62 \\use_default_options false
63 \\maintain_unincluded_children false
71 \\language_package default
74 \\font_roman "default" "default"
75 \\font_sans "default" "default"
76 \\font_typewriter "default" "default"
77 \\font_math "auto" "auto"
78 \\font_default_family default
79 \\use_non_tex_fonts false
82 \\font_sf_scale 100 100
83 \\font_tt_scale 100 100
86 \\default_output_format default
88 \\bibtex_command default
89 \\index_command default
90 \\paperfontsize default
95 \\use_package amsmath 1
96 \\use_package amssymb 1
97 \\use_package cancel 0
99 \\use_package mathdots 0
100 \\use_package mathtools 0
101 \\use_package mhchem 1
102 \\use_package stackrel 0
103 \\use_package stmaryrd 0
104 \\use_package undertilde 0
106 \\cite_engine_type default
110 \\paperorientation portrait
111 \\suppress_date false
124 \\paragraph_separation indent
125 \\paragraph_indentation default
126 \\quotes_language english
129 \\paperpagestyle default
130 \\tracking_changes false
131 \\output_changes false
134 \\html_be_strict false
144 \\begin_layout Author
145 The \\SpecialChar LyX
151 LFUNS_INTRO = u"""\\begin_layout Section*
155 \\begin_layout Standard
156 This manual documents the
157 \\begin_inset Quotes eld
161 \\begin_inset Quotes erd
165 These are commands that are used to make \\SpecialChar LyX
166 perform specific actions.
168 itself uses these functions internally, and every internal action is
172 \\begin_layout Standard
173 LFUNs are also used in the files that define keyboard shortcuts, menu or
175 So if you want to change\\SpecialChar breakableslash
176 customize the user interface, you need to deal
178 Furthermore, external programs can use LFUNs to communicate with and
179 \\begin_inset Quotes eld
183 \\begin_inset Quotes erd
188 Finally, you can also issue LFUNs directly via the so called mini-buffer
189 which can be opened via
192 arg "command-execute"
198 \\begin_layout Standard
199 In the following, all LFUNs are listed, categorized by function.
205 LFUNS_FOOTER = u"""\\end_body
210 """Takes a comment block (str) and parses it for fields describing the LFUN. Returns a dict containing the fields."""
212 lfun = dict(action="", notion="", syntax="", params="", sample="", origin="")
214 lines = str.splitlines()
215 # strip leading whitespace and * from the lines of the comment to get
216 # rid of unimportant characters
217 for i in range(0, len(lines)):
218 lines[i] = lines[i].strip(" *")
220 for i in range(0, len(lines) - 1):
221 # work out what field is being read if none of these is found, the line will be added
222 # to the last field edited
223 # since the field identifier is not included skip it out if it's found, otherwise skip
224 # nothing as an existing field is being added to
225 # if a field id is found, then its the first line of the field so set the pre_space to ""
226 # so that the first line isn't prespaced
227 if lines[i].startswith(LFUN_ACTION_ID):
230 skip = len(ID_DICT[field])
231 elif lines[i].startswith(LFUN_NOTION_ID):
234 skip = len(ID_DICT[field])
235 elif lines[i].startswith(LFUN_SYNTAX_ID):
238 skip = len(ID_DICT[field])
239 elif lines[i].startswith(LFUN_PARAMS_ID):
242 skip = len(ID_DICT[field])
243 elif lines[i].startswith(LFUN_SAMPLE_ID):
246 skip = len(ID_DICT[field])
247 elif lines[i].startswith(LFUN_ORIGIN_ID):
250 skip = len(ID_DICT[field])
251 elif lines[i].startswith(LFUN_ENDVAR):
255 # if a manual line break was found last line, don't prespace this line
256 if i > 1 and lines[i-1].endswith("\\n"):
261 # add the line to the field, processing it for \ characters and \n
262 # which, if occurring at the end of a line, must become a LYX_NEWLINE
263 line = lines[i][skip:]
265 # deal with \htmlonly
266 # TODO: convert chars found in htmlonly to unicode
267 start = line.find(HTMLONLY_START)
269 # if removing the htmlonly element leaves a double space, go back one to remove it
270 if line[start-1] == " ":
272 end = line.find(HTMLONLY_END)
274 end = line.find(HTMLONLY_END) + len(HTMLONLY_END)
275 line = line[:start] + line[end:]
277 # TODO: if HTMLONLY_END is not found, look on the next line
278 # TODO: in the current LyXAction.cpp there are no htmlonly fields which go over a line break
280 # deal with \ but leave \n if at the end of the line
281 slash_idx = line.find("\\")
282 while slash_idx >= 0:
283 if slash_idx < len(line)-2 \
284 or slash_idx == len(line)-1:
285 # true when the \ is not the last or second last char
286 # or when the slash is the last char of the line
288 # slash must be interpreted literaly so swap it for a LYX_BACKSLASH
289 line = line[:slash_idx] + LYX_BACKSLASH + line[slash_idx+1:]
290 # skip the index ahead beyond the added text
291 slash_idx = slash_idx + len(LYX_BACKSLASH)
292 elif line[slash_idx+1] != "n": # only evaluated if the line ends "\x" where 'x' != 'n'
293 line = line[:slash_idx] + LYX_BACKSLASH + line[slash_idx+1:]
294 # skip the index ahead beyond the added text
295 slash_idx = slash_idx + len(LYX_BACKSLASH)
296 # look for the next \
297 slash_idx = line.find("\\", slash_idx+1)
299 # \n at the end of lines will not be processed by the above while loop
300 # so sort those out now
301 # sometime lines end " \n" so chop the space if its there
302 if line.endswith(" \\n"):
303 line = line[:len(line)-3] + LYX_NEWLINE
304 elif line.endswith("\\n"):
305 line = line[:len(line)-2] + LYX_NEWLINE
307 # any references to other LFUNs need the # removing
308 # TODO: actually insert a cross-reference here
309 line = line.replace("#LFUN", "LFUN")
311 # handle the few #lyx:: cases
312 line = line.replace("#lyx::", "lyx::")
314 # the first line might not have a field in it in which
315 # case the variable field won't have a value, so check
318 lfun[field] = lfun[field] + pre_space + line
320 # TODO: sort out chopping lines of more that 80 chars in length
324 def write_fields(file, lfun):
325 """Writes the LFUN contained in the dict lfun to the file. Does not write a the file header or footer"""
326 # add lfun to LFUNs.lyx
327 file.write(u"\\begin_layout Subsection*\n")
328 file.write(lfun["name"] + "\n")
329 file.write(u"\\end_layout\n")
331 if lfun["action"] != "":
332 file.write(u"\\begin_layout Description\n")
333 file.write("Action " + lfun["action"] + "\n")
334 file.write(u"\\end_layout\n")
336 if lfun["notion"] != "":
337 file.write(u"\\begin_layout Description\n")
338 file.write("Notion " + lfun["notion"] + "\n")
339 file.write(u"\\end_layout\n")
341 if lfun["syntax"] != "":
342 file.write(u"\\begin_layout Description\n")
343 file.write("Syntax " + lfun["syntax"] + "\n")
344 file.write(u"\\end_layout\n")
346 if lfun["params"] != "":
347 file.write(u"\\begin_layout Description\n")
348 file.write("Params " + lfun["params"] + "\n")
349 file.write(u"\\end_layout\n")
351 if lfun["sample"] != "":
352 file.write(u"\\begin_layout Description\n")
353 file.write("Sample " + lfun["sample"] + "\n")
354 file.write(u"\\end_layout\n")
356 if lfun["origin"] != "":
357 file.write(u"\\begin_layout Description\n")
358 file.write("Origin " + lfun["origin"] + "\n")
359 file.write(u"\\end_layout\n")
362 def write_sections(file,lfuns):
363 """Write sections of LFUNs"""
364 sections = ["Layout", "Edit", "Math", "Buffer", "System", "Hidden"]
366 "Layout": u"Layout Functions (Font, Layout and Textclass related)",
367 "Edit": u"Editing Functions (Cursor and Mouse Movement, Copy/Paste etc.)",
368 "Math": u"Math Editor Functions",
369 "Buffer": u"Buffer Fuctions (File and Window related)",
370 "System": u"System Functions (Preferences, LyX Server etc.)",
371 "Hidden": u"Hidden Functions (not listed for configuration)"
373 # write the lfuns to the file
375 file.write(u"\\begin_layout Section\n")
376 file.write(section_headings[val] + "\n")
377 file.write(u"\\end_layout\n")
380 if lf["type"] == val:
381 write_fields(file, lf)
384 # parse command line arguments
385 script_path, script_name = os.path.split(argv[0])
387 error(usage(script_name))
389 lyxaction_path = argv[1]
390 if not os.path.isfile(lyxaction_path):
391 error(script_name + ": %s is not a valid path" % lyxaction_path)
396 if os.path.isdir(lfuns_path):
397 lfuns_path = lfuns_path + "LFUNs.lyx"
398 elif os.path.exists(lfuns_path):
399 error(script_name + ": %s already exists, delete it and rerun the script" % lfuns_path)
400 lfuns_file = io.open(lfuns_path, 'w', encoding='utf_8')
402 lfuns_file = sys.stdout
404 sys.stderr.write(script_name + ": Start processing " + argv[1] + '\n')
405 # Read the input file and write the output file
406 lyxaction_file = io.open(lyxaction_path, 'r', encoding='utf_8')
408 lyxaction_text = lyxaction_file.read()
410 lfuns_file.write(LFUNS_HEADER)
412 # An introductory section
413 lfuns_file.write(LFUNS_INTRO)
415 # seek to the important bit of LyXAction.cpp
417 start = lyxaction_text.index("ev_item const items[] = {")
419 lyxaction_file.close()
421 error(script_name + ": LFUNs not found in " + lyxaction_file)
425 lfun_list_unsorted = []
428 # look for a doxygen comment
429 start = lyxaction_text.find(DOXYGEN_START, start)
430 end = lyxaction_text.find(DOXYGEN_END, start) + len(DOXYGEN_END)
433 snippet = lyxaction_text[start:end]
434 defline = snippet.replace("\n", "")
435 match = re.match(r'.*\s*\{\s*(.+),\s*"(.*)",\s*([\w\|\s]+),\s*(\w+)\s*\},.*$', defline)
437 name = match.group(2)
438 atype = match.group(4)
439 # parse the lfun if it is found
443 lfun = parse_lfun(snippet)
446 # save the lfun (we sort it before writing)
447 lfun_list_unsorted.append(lfun)
451 # if no more lfuns are found, EOF reached
454 lfun_list = sorted(lfun_list_unsorted, key=lambda k: k['name'])
456 # write the lfuns to the file
457 write_sections(lfuns_file, lfun_list)
459 sys.stderr.write(script_name + ": Created documentation for " + str(count) + " LFUNs\n")
461 # write the last part of LFUNs.lyx
462 lfuns_file.write(LFUNS_FOOTER)
464 lyxaction_file.close()
467 sys.stderr.write(script_name + ": Finished\n")
469 if __name__ == "__main__":