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/
51 \\renewcommand{\\descriptionlabel}[1]{\\hspace\\labelsep\\upshape\\bfseries #1:}
52 \\renewenvironment{description}{\\list{}{%
53 \\setlength{\\itemsep}{-2pt}
54 \\advance\\leftmargini6\\p@ \\itemindent-12\\p@
55 \\labelwidth\\z@ \\let\\makelabel\\descriptionlabel}%
60 \\use_default_options false
61 \\maintain_unincluded_children false
69 \\language_package default
74 \\font_typewriter default
76 \\font_default_family default
77 \\use_non_tex_fonts false
83 \\default_output_format default
85 \\bibtex_command default
86 \\index_command default
87 \\paperfontsize default
92 \\use_package amsmath 1
93 \\use_package amssymb 1
94 \\use_package cancel 0
96 \\use_package mathdots 0
97 \\use_package mathtools 0
98 \\use_package mhchem 1
99 \\use_package stackrel 0
100 \\use_package stmaryrd 0
101 \\use_package undertilde 0
103 \\cite_engine_type default
107 \\paperorientation portrait
108 \\suppress_date false
121 \\paragraph_separation indent
122 \\paragraph_indentation default
123 \\quotes_language english
126 \\paperpagestyle default
127 \\tracking_changes false
128 \\output_changes false
131 \\html_be_strict false
137 LyX Functions (LFUNs)
140 \\begin_layout Author
144 \\begin_layout Date""" + "\n" + str(date.today()) + """
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 LyX perform specific actions.
164 LyX itself uses these functions internally, and every internal action is
168 \\begin_layout Standard
169 LFUNs are also used in the files that define keyboard shortcuts, menu or
171 So if you want to change\\SpecialChar \\slash{}
172 customize the user interface, you need to deal
174 Furthermore, external programs can use LFUNs to communicate with and
175 \\begin_inset Quotes eld
179 \\begin_inset Quotes erd
183 Finally, you can also issue LFUNs directly via the so called mini-buffer
184 which can be opened via
187 arg "command-execute"
193 \\begin_layout Standard
194 In the following, all LFUNs are listed, categorized by function.
200 LFUNS_FOOTER = """\\end_body
205 """Takes a comment block (str) and parses it for fields describing the LFUN. Returns a dict containing the fields."""
207 lfun = dict(action="", notion="", syntax="", params="", sample="", origin="")
209 lines = str.splitlines()
210 # strip leading whitespace and * from the lines of the comment to get
211 # rid of unimportant characters
212 for i in range(0, len(lines)):
213 lines[i] = lines[i].strip(" *")
215 for i in range(0, len(lines) - 1):
216 # work out what field is being read if none of these is found, the line will be added
217 # to the last field edited
218 # since the field identifier is not included skip it out if it's found, otherwise skip
219 # nothing as an existing field is being added to
220 # if a field id is found, then its the first line of the field so set the pre_space to ""
221 # so that the first line isn't prespaced
222 if lines[i].startswith(LFUN_ACTION_ID):
225 skip = len(ID_DICT[field])
226 elif lines[i].startswith(LFUN_NOTION_ID):
229 skip = len(ID_DICT[field])
230 elif lines[i].startswith(LFUN_SYNTAX_ID):
233 skip = len(ID_DICT[field])
234 elif lines[i].startswith(LFUN_PARAMS_ID):
237 skip = len(ID_DICT[field])
238 elif lines[i].startswith(LFUN_SAMPLE_ID):
241 skip = len(ID_DICT[field])
242 elif lines[i].startswith(LFUN_ORIGIN_ID):
245 skip = len(ID_DICT[field])
246 elif lines[i].startswith(LFUN_ENDVAR):
250 # if a manual line break was found last line, don't prespace this line
251 if i > 1 and lines[i-1].endswith("\\n"):
256 # add the line to the field, processing it for \ characters and \n
257 # which, if occurring at the end of a line, must become a LYX_NEWLINE
258 line = lines[i][skip:]
260 # deal with \htmlonly
261 # TODO: convert chars found in htmlonly to unicode
262 start = line.find(HTMLONLY_START)
264 # if removing the htmlonly element leaves a double space, go back one to remove it
265 if line[start-1] == " ":
267 end = line.find(HTMLONLY_END)
269 end = line.find(HTMLONLY_END) + len(HTMLONLY_END)
270 line = line[:start] + line[end:]
272 # TODO: if HTMLONLY_END is not found, look on the next line
273 # TODO: in the current LyXAction.cpp there are no htmlonly fields which go over a line break
275 # deal with \ but leave \n if at the end of the line
276 slash_idx = line.find("\\")
277 while slash_idx >= 0:
278 if slash_idx < len(line)-2 \
279 or slash_idx == len(line)-1:
280 # true when the \ is not the last or second last char
281 # or when the slash is the last char of the line
283 # slash must be interpreted literaly so swap it for a LYX_BACKSLASH
284 line = line[:slash_idx] + LYX_BACKSLASH + line[slash_idx+1:]
285 # skip the index ahead beyond the added text
286 slash_idx = slash_idx + len(LYX_BACKSLASH)
287 elif line[slash_idx+1] != "n": # only evaluated if the line ends "\x" where 'x' != 'n'
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 # look for the next \
292 slash_idx = line.find("\\", slash_idx+1)
294 # \n at the end of lines will not be processed by the above while loop
295 # so sort those out now
296 # sometime lines end " \n" so chop the space if its there
297 if line.endswith(" \\n"):
298 line = line[:len(line)-3] + LYX_NEWLINE
299 elif line.endswith("\\n"):
300 line = line[:len(line)-2] + LYX_NEWLINE
302 # any references to other LFUNs need the # removing
303 # TODO: actually insert a cross-reference here
304 line = line.replace("#LFUN", "LFUN")
306 # handle the few #lyx:: cases
307 line = line.replace("#lyx::", "lyx::")
309 # the first line might not have a field in it in which
310 # case the variable field won't have a value, so check
313 lfun[field] = lfun[field] + pre_space + line
315 # TODO: sort out chopping lines of more that 80 chars in length
319 def write_fields(file, lfun):
320 """Writes the LFUN contained in the dict lfun to the file. Does not write a the file header or footer"""
321 # add lfun to LFUNs.lyx
322 file.write("\\begin_layout Subsection*\n")
323 file.write(lfun["name"] + "\n")
324 file.write("\\end_layout\n")
326 if lfun["action"] != "":
327 file.write("\\begin_layout Description\n")
328 file.write("Action " + lfun["action"] + "\n")
330 file.write("\\end_layout\n")
332 if lfun["notion"] != "":
333 file.write("\\begin_layout Description\n")
334 file.write("Notion " + lfun["notion"] + "\n")
335 file.write("\\end_layout\n")
337 if lfun["syntax"] != "":
338 file.write("\\begin_layout Description\n")
339 file.write("Syntax " + lfun["syntax"] + "\n")
340 file.write("\\end_layout\n")
342 if lfun["params"] != "":
343 file.write("\\begin_layout Description\n")
344 file.write("Params " + lfun["params"] + "\n")
345 file.write("\\end_layout\n")
347 if lfun["sample"] != "":
348 file.write("\\begin_layout Description\n")
349 file.write("Sample " + lfun["sample"] + "\n")
350 file.write("\\end_layout\n")
352 if lfun["origin"] != "":
353 file.write("\\begin_layout Description\n")
354 file.write("Origin " + lfun["origin"] + "\n")
355 file.write("\\end_layout\n")
359 def write_sections(file,lfuns):
360 """Write sections of LFUNs"""
361 sections = ["Layout", "Edit", "Math", "Buffer", "System", "Hidden"]
363 "Layout": "Layout Functions (Font, Layout and Textclass related)",
364 "Edit": "Editing Functions (Cursor and Mouse Movement, Copy/Paste etc.)",
365 "Math": "Math Editor Functions",
366 "Buffer": "Buffer Fuctions (File and Window related)",
367 "System": "System Functions (Preferences, LyX Server etc.)",
368 "Hidden": "Hidden Functions (not listed for configuration)"
370 # write the lfuns to the file
372 file.write("\\begin_layout Section\n")
373 file.write(section_headings[val] + "\n")
374 file.write("\\end_layout\n")
376 if lf["type"] == val:
377 write_fields(file, lf)
380 # parse command line arguments
381 script_path, script_name = os.path.split(argv[0])
383 error(usage(script_name))
385 lyxaction_path = argv[1]
386 if not os.path.isfile(lyxaction_path):
387 error(script_name + ": %s is not a valid path" % lyxaction_path)
392 if os.path.isdir(lfuns_path):
393 lfuns_path = lfuns_path + "LFUNs.lyx"
394 elif os.path.exists(lfuns_path):
395 error(script_name + ": %s already exists, delete it and rerun the script" % lfuns_path)
396 lfuns_file = open(lfuns_path, 'wb')
398 lfuns_file = sys.stdout
400 sys.stderr.write(script_name + ": Start processing " + argv[1] + '\n')
401 # Read the input file and write the output file
402 lyxaction_file = open(lyxaction_path, 'rb')
404 lyxaction_text = lyxaction_file.read()
406 lfuns_file.write(LFUNS_HEADER)
408 # An introductory section
409 lfuns_file.write(LFUNS_INTRO)
411 # seek to the important bit of LyXAction.cpp
413 start = lyxaction_text.index("ev_item const items[] = {")
415 lyxaction_file.close()
417 error(script_name + ": LFUNs not found in " + lyxaction_file)
421 lfun_list_unsorted = []
424 # look for a doxygen comment
425 start = lyxaction_text.find(DOXYGEN_START, start)
426 end = lyxaction_text.find(DOXYGEN_END, start) + len(DOXYGEN_END)
429 snippet = lyxaction_text[start:end]
430 defline = snippet.replace("\n", "")
431 match = re.match(r'.*\s*\{\s*(.+),\s*"(.*)",\s*([\w\|\s]+),\s*(\w+)\s*\},.*$', defline)
433 name = match.group(2)
434 atype = match.group(4)
435 # parse the lfun if it is found
439 lfun = parse_lfun(snippet)
442 # save the lfun (we sort it before writing)
443 lfun_list_unsorted.append(lfun)
447 # if no more lfuns are found, EOF reached
450 lfun_list = sorted(lfun_list_unsorted, key=lambda k: k['name'])
452 # write the lfuns to the file
453 write_sections(lfuns_file, lfun_list)
455 sys.stderr.write(script_name + ": Created documentation for " + str(count) + " LFUNs\n")
457 # write the last part of LFUNs.lyx
458 lfuns_file.write(LFUNS_FOOTER)
460 lyxaction_file.close()
463 sys.stderr.write(script_name + ": Finished\n")
465 if __name__ == "__main__":