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>
18 sys.stderr.write(message + '\n')
22 return "Usage: %s <path/to/LyXAction.cpp> [<where/to/save/LFUNs.lyx>]" % prog_name
27 LYX_NEWLINE = "\n\\begin_inset Newline newline\n\\end_inset\n\n"
28 LYX_BACKSLASH = "\n\\backslash\n"
30 HTMLONLY_START = "\\htmlonly"
31 HTMLONLY_END = "\\endhtmlonly"
32 LFUN_NAME_ID = "\\var lyx::FuncCode lyx::"
33 LFUN_ACTION_ID = "\\li Action: "
34 LFUN_NOTION_ID = "\\li Notion: "
35 LFUN_SYNTAX_ID = "\\li Syntax: "
36 LFUN_PARAMS_ID = "\\li Params: "
37 LFUN_SAMPLE_ID = "\\li Sample: "
38 LFUN_ORIGIN_ID = "\\li Origin: "
39 LFUN_ENDVAR = "\\endvar"
41 ID_DICT = dict(name=LFUN_NAME_ID, action=LFUN_ACTION_ID, notion=LFUN_NOTION_ID,
42 syntax=LFUN_SYNTAX_ID, params=LFUN_PARAMS_ID, sample=LFUN_SAMPLE_ID, origin=LFUN_ORIGIN_ID)
44 LFUNS_HEADER = """# gen_lfuns.py generated this file. For more info see http://www.lyx.org/
48 \\save_transient_properties true
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
149 LFUNS_INTRO ="""\\begin_layout Section*
153 \\begin_layout Standard
154 This manual documents the
155 \\begin_inset Quotes eld
159 \\begin_inset Quotes erd
163 These are commands that are used to make \\SpecialChar LyX
164 perform specific actions.
166 itself uses these functions internally, and every internal action is
170 \\begin_layout Standard
171 LFUNs are also used in the files that define keyboard shortcuts, menu or
173 So if you want to change\\SpecialChar breakableslash
174 customize the user interface, you need to deal
176 Furthermore, external programs can use LFUNs to communicate with and
177 \\begin_inset Quotes eld
181 \\begin_inset Quotes erd
186 Finally, you can also issue LFUNs directly via the so called mini-buffer
187 which can be opened via
190 arg "command-execute"
196 \\begin_layout Standard
197 In the following, all LFUNs are listed, categorized by function.
203 LFUNS_FOOTER = """\\end_body
208 """Takes a comment block (str) and parses it for fields describing the LFUN. Returns a dict containing the fields."""
210 lfun = dict(action="", notion="", syntax="", params="", sample="", origin="")
212 lines = str.splitlines()
213 # strip leading whitespace and * from the lines of the comment to get
214 # rid of unimportant characters
215 for i in range(0, len(lines)):
216 lines[i] = lines[i].strip(" *")
218 for i in range(0, len(lines) - 1):
219 # work out what field is being read if none of these is found, the line will be added
220 # to the last field edited
221 # since the field identifier is not included skip it out if it's found, otherwise skip
222 # nothing as an existing field is being added to
223 # if a field id is found, then its the first line of the field so set the pre_space to ""
224 # so that the first line isn't prespaced
225 if lines[i].startswith(LFUN_ACTION_ID):
228 skip = len(ID_DICT[field])
229 elif lines[i].startswith(LFUN_NOTION_ID):
232 skip = len(ID_DICT[field])
233 elif lines[i].startswith(LFUN_SYNTAX_ID):
236 skip = len(ID_DICT[field])
237 elif lines[i].startswith(LFUN_PARAMS_ID):
240 skip = len(ID_DICT[field])
241 elif lines[i].startswith(LFUN_SAMPLE_ID):
244 skip = len(ID_DICT[field])
245 elif lines[i].startswith(LFUN_ORIGIN_ID):
248 skip = len(ID_DICT[field])
249 elif lines[i].startswith(LFUN_ENDVAR):
253 # if a manual line break was found last line, don't prespace this line
254 if i > 1 and lines[i-1].endswith("\\n"):
259 # add the line to the field, processing it for \ characters and \n
260 # which, if occurring at the end of a line, must become a LYX_NEWLINE
261 line = lines[i][skip:]
263 # deal with \htmlonly
264 # TODO: convert chars found in htmlonly to unicode
265 start = line.find(HTMLONLY_START)
267 # if removing the htmlonly element leaves a double space, go back one to remove it
268 if line[start-1] == " ":
270 end = line.find(HTMLONLY_END)
272 end = line.find(HTMLONLY_END) + len(HTMLONLY_END)
273 line = line[:start] + line[end:]
275 # TODO: if HTMLONLY_END is not found, look on the next line
276 # TODO: in the current LyXAction.cpp there are no htmlonly fields which go over a line break
278 # deal with \ but leave \n if at the end of the line
279 slash_idx = line.find("\\")
280 while slash_idx >= 0:
281 if slash_idx < len(line)-2 \
282 or slash_idx == len(line)-1:
283 # true when the \ is not the last or second last char
284 # or when the slash is the last char of the line
286 # slash must be interpreted literaly so swap it for a LYX_BACKSLASH
287 line = line[:slash_idx] + LYX_BACKSLASH + line[slash_idx+1:]
288 # skip the index ahead beyond the added text
289 slash_idx = slash_idx + len(LYX_BACKSLASH)
290 elif line[slash_idx+1] != "n": # only evaluated if the line ends "\x" where 'x' != 'n'
291 line = line[:slash_idx] + LYX_BACKSLASH + line[slash_idx+1:]
292 # skip the index ahead beyond the added text
293 slash_idx = slash_idx + len(LYX_BACKSLASH)
294 # look for the next \
295 slash_idx = line.find("\\", slash_idx+1)
297 # \n at the end of lines will not be processed by the above while loop
298 # so sort those out now
299 # sometime lines end " \n" so chop the space if its there
300 if line.endswith(" \\n"):
301 line = line[:len(line)-3] + LYX_NEWLINE
302 elif line.endswith("\\n"):
303 line = line[:len(line)-2] + LYX_NEWLINE
305 # any references to other LFUNs need the # removing
306 # TODO: actually insert a cross-reference here
307 line = line.replace("#LFUN", "LFUN")
309 # handle the few #lyx:: cases
310 line = line.replace("#lyx::", "lyx::")
312 # the first line might not have a field in it in which
313 # case the variable field won't have a value, so check
316 lfun[field] = lfun[field] + pre_space + line
318 # TODO: sort out chopping lines of more that 80 chars in length
322 def write_fields(file, lfun):
323 """Writes the LFUN contained in the dict lfun to the file. Does not write a the file header or footer"""
324 # add lfun to LFUNs.lyx
325 file.write("\\begin_layout Subsection*\n")
326 file.write(lfun["name"] + "\n")
327 file.write("\\end_layout\n")
329 if lfun["action"] != "":
330 file.write("\\begin_layout Description\n")
331 file.write("Action " + lfun["action"] + "\n")
332 file.write("\\end_layout\n")
334 if lfun["notion"] != "":
335 file.write("\\begin_layout Description\n")
336 file.write("Notion " + lfun["notion"] + "\n")
337 file.write("\\end_layout\n")
339 if lfun["syntax"] != "":
340 file.write("\\begin_layout Description\n")
341 file.write("Syntax " + lfun["syntax"] + "\n")
342 file.write("\\end_layout\n")
344 if lfun["params"] != "":
345 file.write("\\begin_layout Description\n")
346 file.write("Params " + lfun["params"] + "\n")
347 file.write("\\end_layout\n")
349 if lfun["sample"] != "":
350 file.write("\\begin_layout Description\n")
351 file.write("Sample " + lfun["sample"] + "\n")
352 file.write("\\end_layout\n")
354 if lfun["origin"] != "":
355 file.write("\\begin_layout Description\n")
356 file.write("Origin " + lfun["origin"] + "\n")
357 file.write("\\end_layout\n")
360 def write_sections(file,lfuns):
361 """Write sections of LFUNs"""
362 sections = ["Layout", "Edit", "Math", "Buffer", "System", "Hidden"]
364 "Layout": "Layout Functions (Font, Layout and Textclass related)",
365 "Edit": "Editing Functions (Cursor and Mouse Movement, Copy/Paste etc.)",
366 "Math": "Math Editor Functions",
367 "Buffer": "Buffer Fuctions (File and Window related)",
368 "System": "System Functions (Preferences, LyX Server etc.)",
369 "Hidden": "Hidden Functions (not listed for configuration)"
371 # write the lfuns to the file
373 file.write("\\begin_layout Section\n")
374 file.write(section_headings[val] + "\n")
375 file.write("\\end_layout\n")
378 if lf["type"] == val:
379 write_fields(file, lf)
382 # parse command line arguments
383 script_path, script_name = os.path.split(argv[0])
385 error(usage(script_name))
387 lyxaction_path = argv[1]
388 if not os.path.isfile(lyxaction_path):
389 error(script_name + ": %s is not a valid path" % lyxaction_path)
394 if os.path.isdir(lfuns_path):
395 lfuns_path = lfuns_path + "LFUNs.lyx"
396 elif os.path.exists(lfuns_path):
397 error(script_name + ": %s already exists, delete it and rerun the script" % lfuns_path)
398 lfuns_file = open(lfuns_path, 'wb')
400 lfuns_file = sys.stdout
402 sys.stderr.write(script_name + ": Start processing " + argv[1] + '\n')
403 # Read the input file and write the output file
404 lyxaction_file = open(lyxaction_path, 'rb')
406 lyxaction_text = lyxaction_file.read()
408 lfuns_file.write(LFUNS_HEADER)
410 # An introductory section
411 lfuns_file.write(LFUNS_INTRO)
413 # seek to the important bit of LyXAction.cpp
415 start = lyxaction_text.index("ev_item const items[] = {")
417 lyxaction_file.close()
419 error(script_name + ": LFUNs not found in " + lyxaction_file)
423 lfun_list_unsorted = []
426 # look for a doxygen comment
427 start = lyxaction_text.find(DOXYGEN_START, start)
428 end = lyxaction_text.find(DOXYGEN_END, start) + len(DOXYGEN_END)
431 snippet = lyxaction_text[start:end]
432 defline = snippet.replace("\n", "")
433 match = re.match(r'.*\s*\{\s*(.+),\s*"(.*)",\s*([\w\|\s]+),\s*(\w+)\s*\},.*$', defline)
435 name = match.group(2)
436 atype = match.group(4)
437 # parse the lfun if it is found
441 lfun = parse_lfun(snippet)
444 # save the lfun (we sort it before writing)
445 lfun_list_unsorted.append(lfun)
449 # if no more lfuns are found, EOF reached
452 lfun_list = sorted(lfun_list_unsorted, key=lambda k: k['name'])
454 # write the lfuns to the file
455 write_sections(lfuns_file, lfun_list)
457 sys.stderr.write(script_name + ": Created documentation for " + str(count) + " LFUNs\n")
459 # write the last part of LFUNs.lyx
460 lfuns_file.write(LFUNS_FOOTER)
462 lyxaction_file.close()
465 sys.stderr.write(script_name + ": Finished\n")
467 if __name__ == "__main__":