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
85 \\default_output_format default
87 \\bibtex_command default
88 \\index_command default
89 \\paperfontsize default
94 \\use_package amsmath 1
95 \\use_package amssymb 1
96 \\use_package cancel 0
98 \\use_package mathdots 0
99 \\use_package mathtools 0
100 \\use_package mhchem 1
101 \\use_package stackrel 0
102 \\use_package stmaryrd 0
103 \\use_package undertilde 0
105 \\cite_engine_type default
109 \\paperorientation portrait
110 \\suppress_date false
123 \\paragraph_separation indent
124 \\paragraph_indentation default
125 \\quotes_language english
128 \\paperpagestyle default
129 \\tracking_changes false
130 \\output_changes false
133 \\html_be_strict false
143 \\begin_layout Author
144 The \\SpecialChar LyX
150 LFUNS_INTRO = u"""\\begin_layout Section*
154 \\begin_layout Standard
155 This manual documents the
156 \\begin_inset Quotes eld
160 \\begin_inset Quotes erd
164 These are commands that are used to make \\SpecialChar LyX
165 perform specific actions.
167 itself uses these functions internally, and every internal action is
171 \\begin_layout Standard
172 LFUNs are also used in the files that define keyboard shortcuts, menu or
174 So if you want to change\\SpecialChar breakableslash
175 customize the user interface, you need to deal
177 Furthermore, external programs can use LFUNs to communicate with and
178 \\begin_inset Quotes eld
182 \\begin_inset Quotes erd
187 Finally, you can also issue LFUNs directly via the so called mini-buffer
188 which can be opened via
191 arg "command-execute"
197 \\begin_layout Standard
198 In the following, all LFUNs are listed, categorized by function.
204 LFUNS_FOOTER = u"""\\end_body
209 """Takes a comment block (str) and parses it for fields describing the LFUN. Returns a dict containing the fields."""
211 lfun = dict(action="", notion="", syntax="", params="", sample="", origin="")
213 lines = str.splitlines()
214 # strip leading whitespace and * from the lines of the comment to get
215 # rid of unimportant characters
216 for i in range(0, len(lines)):
217 lines[i] = lines[i].strip(" *")
219 for i in range(0, len(lines) - 1):
220 # work out what field is being read if none of these is found, the line will be added
221 # to the last field edited
222 # since the field identifier is not included skip it out if it's found, otherwise skip
223 # nothing as an existing field is being added to
224 # if a field id is found, then its the first line of the field so set the pre_space to ""
225 # so that the first line isn't prespaced
226 if lines[i].startswith(LFUN_ACTION_ID):
229 skip = len(ID_DICT[field])
230 elif lines[i].startswith(LFUN_NOTION_ID):
233 skip = len(ID_DICT[field])
234 elif lines[i].startswith(LFUN_SYNTAX_ID):
237 skip = len(ID_DICT[field])
238 elif lines[i].startswith(LFUN_PARAMS_ID):
241 skip = len(ID_DICT[field])
242 elif lines[i].startswith(LFUN_SAMPLE_ID):
245 skip = len(ID_DICT[field])
246 elif lines[i].startswith(LFUN_ORIGIN_ID):
249 skip = len(ID_DICT[field])
250 elif lines[i].startswith(LFUN_ENDVAR):
254 # if a manual line break was found last line, don't prespace this line
255 if i > 1 and lines[i-1].endswith("\\n"):
260 # add the line to the field, processing it for \ characters and \n
261 # which, if occurring at the end of a line, must become a LYX_NEWLINE
262 line = lines[i][skip:]
264 # deal with \htmlonly
265 # TODO: convert chars found in htmlonly to unicode
266 start = line.find(HTMLONLY_START)
268 # if removing the htmlonly element leaves a double space, go back one to remove it
269 if line[start-1] == " ":
271 end = line.find(HTMLONLY_END)
273 end = line.find(HTMLONLY_END) + len(HTMLONLY_END)
274 line = line[:start] + line[end:]
276 # TODO: if HTMLONLY_END is not found, look on the next line
277 # TODO: in the current LyXAction.cpp there are no htmlonly fields which go over a line break
279 # deal with \ but leave \n if at the end of the line
280 slash_idx = line.find("\\")
281 while slash_idx >= 0:
282 if slash_idx < len(line)-2 \
283 or slash_idx == len(line)-1:
284 # true when the \ is not the last or second last char
285 # or when the slash is the last char of the line
287 # slash must be interpreted literaly so swap it for a LYX_BACKSLASH
288 line = line[:slash_idx] + LYX_BACKSLASH + line[slash_idx+1:]
289 # skip the index ahead beyond the added text
290 slash_idx = slash_idx + len(LYX_BACKSLASH)
291 elif line[slash_idx+1] != "n": # only evaluated if the line ends "\x" where 'x' != 'n'
292 line = line[:slash_idx] + LYX_BACKSLASH + line[slash_idx+1:]
293 # skip the index ahead beyond the added text
294 slash_idx = slash_idx + len(LYX_BACKSLASH)
295 # look for the next \
296 slash_idx = line.find("\\", slash_idx+1)
298 # \n at the end of lines will not be processed by the above while loop
299 # so sort those out now
300 # sometime lines end " \n" so chop the space if its there
301 if line.endswith(" \\n"):
302 line = line[:len(line)-3] + LYX_NEWLINE
303 elif line.endswith("\\n"):
304 line = line[:len(line)-2] + LYX_NEWLINE
306 # any references to other LFUNs need the # removing
307 # TODO: actually insert a cross-reference here
308 line = line.replace("#LFUN", "LFUN")
310 # handle the few #lyx:: cases
311 line = line.replace("#lyx::", "lyx::")
313 # the first line might not have a field in it in which
314 # case the variable field won't have a value, so check
317 lfun[field] = lfun[field] + pre_space + line
319 # TODO: sort out chopping lines of more that 80 chars in length
323 def write_fields(file, lfun):
324 """Writes the LFUN contained in the dict lfun to the file. Does not write a the file header or footer"""
325 # add lfun to LFUNs.lyx
326 file.write(u"\\begin_layout Subsection*\n")
327 file.write(lfun["name"] + "\n")
328 file.write(u"\\end_layout\n")
330 if lfun["action"] != "":
331 file.write(u"\\begin_layout Description\n")
332 file.write("Action " + lfun["action"] + "\n")
333 file.write(u"\\end_layout\n")
335 if lfun["notion"] != "":
336 file.write(u"\\begin_layout Description\n")
337 file.write("Notion " + lfun["notion"] + "\n")
338 file.write(u"\\end_layout\n")
340 if lfun["syntax"] != "":
341 file.write(u"\\begin_layout Description\n")
342 file.write("Syntax " + lfun["syntax"] + "\n")
343 file.write(u"\\end_layout\n")
345 if lfun["params"] != "":
346 file.write(u"\\begin_layout Description\n")
347 file.write("Params " + lfun["params"] + "\n")
348 file.write(u"\\end_layout\n")
350 if lfun["sample"] != "":
351 file.write(u"\\begin_layout Description\n")
352 file.write("Sample " + lfun["sample"] + "\n")
353 file.write(u"\\end_layout\n")
355 if lfun["origin"] != "":
356 file.write(u"\\begin_layout Description\n")
357 file.write("Origin " + lfun["origin"] + "\n")
358 file.write(u"\\end_layout\n")
361 def write_sections(file,lfuns):
362 """Write sections of LFUNs"""
363 sections = ["Layout", "Edit", "Math", "Buffer", "System", "Hidden"]
365 "Layout": u"Layout Functions (Font, Layout and Textclass related)",
366 "Edit": u"Editing Functions (Cursor and Mouse Movement, Copy/Paste etc.)",
367 "Math": u"Math Editor Functions",
368 "Buffer": u"Buffer Fuctions (File and Window related)",
369 "System": u"System Functions (Preferences, LyX Server etc.)",
370 "Hidden": u"Hidden Functions (not listed for configuration)"
372 # write the lfuns to the file
374 file.write(u"\\begin_layout Section\n")
375 file.write(section_headings[val] + "\n")
376 file.write(u"\\end_layout\n")
379 if lf["type"] == val:
380 write_fields(file, lf)
383 # parse command line arguments
384 script_path, script_name = os.path.split(argv[0])
386 error(usage(script_name))
388 lyxaction_path = argv[1]
389 if not os.path.isfile(lyxaction_path):
390 error(script_name + ": %s is not a valid path" % lyxaction_path)
395 if os.path.isdir(lfuns_path):
396 lfuns_path = lfuns_path + "LFUNs.lyx"
397 elif os.path.exists(lfuns_path):
398 error(script_name + ": %s already exists, delete it and rerun the script" % lfuns_path)
399 lfuns_file = io.open(lfuns_path, 'w', encoding='utf_8')
401 lfuns_file = sys.stdout
403 sys.stderr.write(script_name + ": Start processing " + argv[1] + '\n')
404 # Read the input file and write the output file
405 lyxaction_file = io.open(lyxaction_path, 'r', encoding='utf_8')
407 lyxaction_text = lyxaction_file.read()
409 lfuns_file.write(LFUNS_HEADER)
411 # An introductory section
412 lfuns_file.write(LFUNS_INTRO)
414 # seek to the important bit of LyXAction.cpp
416 start = lyxaction_text.index("ev_item const items[] = {")
418 lyxaction_file.close()
420 error(script_name + ": LFUNs not found in " + lyxaction_file)
424 lfun_list_unsorted = []
427 # look for a doxygen comment
428 start = lyxaction_text.find(DOXYGEN_START, start)
429 end = lyxaction_text.find(DOXYGEN_END, start) + len(DOXYGEN_END)
432 snippet = lyxaction_text[start:end]
433 defline = snippet.replace("\n", "")
434 match = re.match(r'.*\s*\{\s*(.+),\s*"(.*)",\s*([\w\|\s]+),\s*(\w+)\s*\},.*$', defline)
436 name = match.group(2)
437 atype = match.group(4)
438 # parse the lfun if it is found
442 lfun = parse_lfun(snippet)
445 # save the lfun (we sort it before writing)
446 lfun_list_unsorted.append(lfun)
450 # if no more lfuns are found, EOF reached
453 lfun_list = sorted(lfun_list_unsorted, key=lambda k: k['name'])
455 # write the lfuns to the file
456 write_sections(lfuns_file, lfun_list)
458 sys.stderr.write(script_name + ": Created documentation for " + str(count) + " LFUNs\n")
460 # write the last part of LFUNs.lyx
461 lfuns_file.write(LFUNS_FOOTER)
463 lyxaction_file.close()
466 sys.stderr.write(script_name + ": Finished\n")
468 if __name__ == "__main__":