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")
335 file.write("\\end_layout\n")
337 if lfun["notion"] != "":
338 file.write("\\begin_layout Description\n")
339 file.write("Notion " + lfun["notion"] + "\n")
340 file.write("\\end_layout\n")
342 if lfun["syntax"] != "":
343 file.write("\\begin_layout Description\n")
344 file.write("Syntax " + lfun["syntax"] + "\n")
345 file.write("\\end_layout\n")
347 if lfun["params"] != "":
348 file.write("\\begin_layout Description\n")
349 file.write("Params " + lfun["params"] + "\n")
350 file.write("\\end_layout\n")
352 if lfun["sample"] != "":
353 file.write("\\begin_layout Description\n")
354 file.write("Sample " + lfun["sample"] + "\n")
355 file.write("\\end_layout\n")
357 if lfun["origin"] != "":
358 file.write("\\begin_layout Description\n")
359 file.write("Origin " + lfun["origin"] + "\n")
360 file.write("\\end_layout\n")
363 def write_sections(file,lfuns):
364 """Write sections of LFUNs"""
365 sections = ["Layout", "Edit", "Math", "Buffer", "System", "Hidden"]
367 "Layout": "Layout Functions (Font, Layout and Textclass related)",
368 "Edit": "Editing Functions (Cursor and Mouse Movement, Copy/Paste etc.)",
369 "Math": "Math Editor Functions",
370 "Buffer": "Buffer Fuctions (File and Window related)",
371 "System": "System Functions (Preferences, LyX Server etc.)",
372 "Hidden": "Hidden Functions (not listed for configuration)"
374 # write the lfuns to the file
376 file.write("\\begin_layout Section\n")
377 file.write(section_headings[val] + "\n")
378 file.write("\\end_layout\n")
381 if lf["type"] == val:
382 write_fields(file, lf)
385 # parse command line arguments
386 script_path, script_name = os.path.split(argv[0])
388 error(usage(script_name))
390 lyxaction_path = argv[1]
391 if not os.path.isfile(lyxaction_path):
392 error(script_name + ": %s is not a valid path" % lyxaction_path)
397 if os.path.isdir(lfuns_path):
398 lfuns_path = lfuns_path + "LFUNs.lyx"
399 elif os.path.exists(lfuns_path):
400 error(script_name + ": %s already exists, delete it and rerun the script" % lfuns_path)
401 lfuns_file = open(lfuns_path, 'wb')
403 lfuns_file = sys.stdout
405 sys.stderr.write(script_name + ": Start processing " + argv[1] + '\n')
406 # Read the input file and write the output file
407 lyxaction_file = open(lyxaction_path, 'rb')
409 lyxaction_text = lyxaction_file.read()
411 lfuns_file.write(LFUNS_HEADER)
413 # An introductory section
414 lfuns_file.write(LFUNS_INTRO)
416 # seek to the important bit of LyXAction.cpp
418 start = lyxaction_text.index("ev_item const items[] = {")
420 lyxaction_file.close()
422 error(script_name + ": LFUNs not found in " + lyxaction_file)
426 lfun_list_unsorted = []
429 # look for a doxygen comment
430 start = lyxaction_text.find(DOXYGEN_START, start)
431 end = lyxaction_text.find(DOXYGEN_END, start) + len(DOXYGEN_END)
434 snippet = lyxaction_text[start:end]
435 defline = snippet.replace("\n", "")
436 match = re.match(r'.*\s*\{\s*(.+),\s*"(.*)",\s*([\w\|\s]+),\s*(\w+)\s*\},.*$', defline)
438 name = match.group(2)
439 atype = match.group(4)
440 # parse the lfun if it is found
444 lfun = parse_lfun(snippet)
447 # save the lfun (we sort it before writing)
448 lfun_list_unsorted.append(lfun)
452 # if no more lfuns are found, EOF reached
455 lfun_list = sorted(lfun_list_unsorted, key=lambda k: k['name'])
457 # write the lfuns to the file
458 write_sections(lfuns_file, lfun_list)
460 sys.stderr.write(script_name + ": Created documentation for " + str(count) + " LFUNs\n")
462 # write the last part of LFUNs.lyx
463 lfuns_file.write(LFUNS_FOOTER)
465 lyxaction_file.close()
468 sys.stderr.write(script_name + ": Finished\n")
470 if __name__ == "__main__":