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>
16 from datetime import date
19 sys.stderr.write(message + '\n')
23 return "Usage: %s <path/to/LyXAction.cpp> [<where/to/save/LFUNs.lyx>]" % prog_name
28 LYX_NEWLINE = "\n\\begin_inset Newline newline\n\\end_inset\n\n"
29 LYX_BACKSLASH = "\n\\backslash\n"
31 HTMLONLY_START = "\\htmlonly"
32 HTMLONLY_END = "\\endhtmlonly"
33 LFUN_NAME_ID = "\\var lyx::FuncCode lyx::"
34 LFUN_ACTION_ID = "\\li Action: "
35 LFUN_NOTION_ID = "\\li Notion: "
36 LFUN_SYNTAX_ID = "\\li Syntax: "
37 LFUN_PARAMS_ID = "\\li Params: "
38 LFUN_SAMPLE_ID = "\\li Sample: "
39 LFUN_ORIGIN_ID = "\\li Origin: "
40 LFUN_ENDVAR = "\\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 = """# gen_lfuns.py generated this file. For more info see http://www.lyx.org/
49 \\origin /systemlyxdir/doc/
52 \\renewcommand{\\descriptionlabel}[1]{\\hspace\\labelsep\\upshape\\bfseries #1:}
53 \\renewenvironment{description}{\\list{}{%
54 \\setlength{\\itemsep}{-2pt}
55 \\advance\\leftmargini6\\p@ \\itemindent-12\\p@
56 \\labelwidth\\z@ \\let\\makelabel\\descriptionlabel}%
61 \\use_default_options false
62 \\maintain_unincluded_children false
70 \\language_package default
73 \\font_roman "default" "default"
74 \\font_sans "default" "default"
75 \\font_typewriter "default" "default"
76 \\font_math "auto" "auto"
77 \\font_default_family default
78 \\use_non_tex_fonts false
81 \\font_sf_scale 100 100
82 \\font_tt_scale 100 100
84 \\default_output_format default
86 \\bibtex_command default
87 \\index_command default
88 \\paperfontsize default
93 \\use_package amsmath 1
94 \\use_package amssymb 1
95 \\use_package cancel 0
97 \\use_package mathdots 0
98 \\use_package mathtools 0
99 \\use_package mhchem 1
100 \\use_package stackrel 0
101 \\use_package stmaryrd 0
102 \\use_package undertilde 0
104 \\cite_engine_type default
108 \\paperorientation portrait
109 \\suppress_date false
122 \\paragraph_separation indent
123 \\paragraph_indentation default
124 \\quotes_language english
127 \\paperpagestyle default
128 \\tracking_changes false
129 \\output_changes false
132 \\html_be_strict false
142 \\begin_layout Author
143 The \\SpecialChar LyX
147 \\begin_layout Date""" + "\n" + str(date.today()) + """
152 LFUNS_INTRO ="""\\begin_layout Section*
156 \\begin_layout Standard
157 This manual documents the
158 \\begin_inset Quotes eld
162 \\begin_inset Quotes erd
166 These are commands that are used to make \\SpecialChar LyX
167 perform specific actions.
169 itself uses these functions internally, and every internal action is
173 \\begin_layout Standard
174 LFUNs are also used in the files that define keyboard shortcuts, menu or
176 So if you want to change\\SpecialChar breakableslash
177 customize the user interface, you need to deal
179 Furthermore, external programs can use LFUNs to communicate with and
180 \\begin_inset Quotes eld
184 \\begin_inset Quotes erd
189 Finally, you can also issue LFUNs directly via the so called mini-buffer
190 which can be opened via
193 arg "command-execute"
199 \\begin_layout Standard
200 In the following, all LFUNs are listed, categorized by function.
206 LFUNS_FOOTER = """\\end_body
211 """Takes a comment block (str) and parses it for fields describing the LFUN. Returns a dict containing the fields."""
213 lfun = dict(action="", notion="", syntax="", params="", sample="", origin="")
215 lines = str.splitlines()
216 # strip leading whitespace and * from the lines of the comment to get
217 # rid of unimportant characters
218 for i in range(0, len(lines)):
219 lines[i] = lines[i].strip(" *")
221 for i in range(0, len(lines) - 1):
222 # work out what field is being read if none of these is found, the line will be added
223 # to the last field edited
224 # since the field identifier is not included skip it out if it's found, otherwise skip
225 # nothing as an existing field is being added to
226 # if a field id is found, then its the first line of the field so set the pre_space to ""
227 # so that the first line isn't prespaced
228 if lines[i].startswith(LFUN_ACTION_ID):
231 skip = len(ID_DICT[field])
232 elif lines[i].startswith(LFUN_NOTION_ID):
235 skip = len(ID_DICT[field])
236 elif lines[i].startswith(LFUN_SYNTAX_ID):
239 skip = len(ID_DICT[field])
240 elif lines[i].startswith(LFUN_PARAMS_ID):
243 skip = len(ID_DICT[field])
244 elif lines[i].startswith(LFUN_SAMPLE_ID):
247 skip = len(ID_DICT[field])
248 elif lines[i].startswith(LFUN_ORIGIN_ID):
251 skip = len(ID_DICT[field])
252 elif lines[i].startswith(LFUN_ENDVAR):
256 # if a manual line break was found last line, don't prespace this line
257 if i > 1 and lines[i-1].endswith("\\n"):
262 # add the line to the field, processing it for \ characters and \n
263 # which, if occurring at the end of a line, must become a LYX_NEWLINE
264 line = lines[i][skip:]
266 # deal with \htmlonly
267 # TODO: convert chars found in htmlonly to unicode
268 start = line.find(HTMLONLY_START)
270 # if removing the htmlonly element leaves a double space, go back one to remove it
271 if line[start-1] == " ":
273 end = line.find(HTMLONLY_END)
275 end = line.find(HTMLONLY_END) + len(HTMLONLY_END)
276 line = line[:start] + line[end:]
278 # TODO: if HTMLONLY_END is not found, look on the next line
279 # TODO: in the current LyXAction.cpp there are no htmlonly fields which go over a line break
281 # deal with \ but leave \n if at the end of the line
282 slash_idx = line.find("\\")
283 while slash_idx >= 0:
284 if slash_idx < len(line)-2 \
285 or slash_idx == len(line)-1:
286 # true when the \ is not the last or second last char
287 # or when the slash is the last char of the line
289 # slash must be interpreted literaly so swap it for a LYX_BACKSLASH
290 line = line[:slash_idx] + LYX_BACKSLASH + line[slash_idx+1:]
291 # skip the index ahead beyond the added text
292 slash_idx = slash_idx + len(LYX_BACKSLASH)
293 elif line[slash_idx+1] != "n": # only evaluated if the line ends "\x" where 'x' != 'n'
294 line = line[:slash_idx] + LYX_BACKSLASH + line[slash_idx+1:]
295 # skip the index ahead beyond the added text
296 slash_idx = slash_idx + len(LYX_BACKSLASH)
297 # look for the next \
298 slash_idx = line.find("\\", slash_idx+1)
300 # \n at the end of lines will not be processed by the above while loop
301 # so sort those out now
302 # sometime lines end " \n" so chop the space if its there
303 if line.endswith(" \\n"):
304 line = line[:len(line)-3] + LYX_NEWLINE
305 elif line.endswith("\\n"):
306 line = line[:len(line)-2] + LYX_NEWLINE
308 # any references to other LFUNs need the # removing
309 # TODO: actually insert a cross-reference here
310 line = line.replace("#LFUN", "LFUN")
312 # handle the few #lyx:: cases
313 line = line.replace("#lyx::", "lyx::")
315 # the first line might not have a field in it in which
316 # case the variable field won't have a value, so check
319 lfun[field] = lfun[field] + pre_space + line
321 # TODO: sort out chopping lines of more that 80 chars in length
325 def write_fields(file, lfun):
326 """Writes the LFUN contained in the dict lfun to the file. Does not write a the file header or footer"""
327 # add lfun to LFUNs.lyx
328 file.write("\\begin_layout Subsection*\n")
329 file.write(lfun["name"] + "\n")
330 file.write("\\end_layout\n")
332 if lfun["action"] != "":
333 file.write("\\begin_layout Description\n")
334 file.write("Action " + lfun["action"] + "\n")
336 file.write("\\end_layout\n")
338 if lfun["notion"] != "":
339 file.write("\\begin_layout Description\n")
340 file.write("Notion " + lfun["notion"] + "\n")
341 file.write("\\end_layout\n")
343 if lfun["syntax"] != "":
344 file.write("\\begin_layout Description\n")
345 file.write("Syntax " + lfun["syntax"] + "\n")
346 file.write("\\end_layout\n")
348 if lfun["params"] != "":
349 file.write("\\begin_layout Description\n")
350 file.write("Params " + lfun["params"] + "\n")
351 file.write("\\end_layout\n")
353 if lfun["sample"] != "":
354 file.write("\\begin_layout Description\n")
355 file.write("Sample " + lfun["sample"] + "\n")
356 file.write("\\end_layout\n")
358 if lfun["origin"] != "":
359 file.write("\\begin_layout Description\n")
360 file.write("Origin " + lfun["origin"] + "\n")
361 file.write("\\end_layout\n")
365 def write_sections(file,lfuns):
366 """Write sections of LFUNs"""
367 sections = ["Layout", "Edit", "Math", "Buffer", "System", "Hidden"]
369 "Layout": "Layout Functions (Font, Layout and Textclass related)",
370 "Edit": "Editing Functions (Cursor and Mouse Movement, Copy/Paste etc.)",
371 "Math": "Math Editor Functions",
372 "Buffer": "Buffer Fuctions (File and Window related)",
373 "System": "System Functions (Preferences, LyX Server etc.)",
374 "Hidden": "Hidden Functions (not listed for configuration)"
376 # write the lfuns to the file
378 file.write("\\begin_layout Section\n")
379 file.write(section_headings[val] + "\n")
380 file.write("\\end_layout\n")
382 if lf["type"] == val:
383 write_fields(file, lf)
386 # parse command line arguments
387 script_path, script_name = os.path.split(argv[0])
389 error(usage(script_name))
391 lyxaction_path = argv[1]
392 if not os.path.isfile(lyxaction_path):
393 error(script_name + ": %s is not a valid path" % lyxaction_path)
398 if os.path.isdir(lfuns_path):
399 lfuns_path = lfuns_path + "LFUNs.lyx"
400 elif os.path.exists(lfuns_path):
401 error(script_name + ": %s already exists, delete it and rerun the script" % lfuns_path)
402 lfuns_file = open(lfuns_path, 'wb')
404 lfuns_file = sys.stdout
406 sys.stderr.write(script_name + ": Start processing " + argv[1] + '\n')
407 # Read the input file and write the output file
408 lyxaction_file = open(lyxaction_path, 'rb')
410 lyxaction_text = lyxaction_file.read()
412 lfuns_file.write(LFUNS_HEADER)
414 # An introductory section
415 lfuns_file.write(LFUNS_INTRO)
417 # seek to the important bit of LyXAction.cpp
419 start = lyxaction_text.index("ev_item const items[] = {")
421 lyxaction_file.close()
423 error(script_name + ": LFUNs not found in " + lyxaction_file)
427 lfun_list_unsorted = []
430 # look for a doxygen comment
431 start = lyxaction_text.find(DOXYGEN_START, start)
432 end = lyxaction_text.find(DOXYGEN_END, start) + len(DOXYGEN_END)
435 snippet = lyxaction_text[start:end]
436 defline = snippet.replace("\n", "")
437 match = re.match(r'.*\s*\{\s*(.+),\s*"(.*)",\s*([\w\|\s]+),\s*(\w+)\s*\},.*$', defline)
439 name = match.group(2)
440 atype = match.group(4)
441 # parse the lfun if it is found
445 lfun = parse_lfun(snippet)
448 # save the lfun (we sort it before writing)
449 lfun_list_unsorted.append(lfun)
453 # if no more lfuns are found, EOF reached
456 lfun_list = sorted(lfun_list_unsorted, key=lambda k: k['name'])
458 # write the lfuns to the file
459 write_sections(lfuns_file, lfun_list)
461 sys.stderr.write(script_name + ": Created documentation for " + str(count) + " LFUNs\n")
463 # write the last part of LFUNs.lyx
464 lfuns_file.write(LFUNS_FOOTER)
466 lyxaction_file.close()
469 sys.stderr.write(script_name + ": Finished\n")
471 if __name__ == "__main__":