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>
17 from datetime import date
20 sys.stderr.write(message + '\n')
24 return "Usage: %s <path/to/LyXAction.cpp> [<where/to/save/LFUNs.lyx>]" % prog_name
29 LYX_NEWLINE = "\n\\begin_inset Newline newline\n\\end_inset\n\n"
30 LYX_BACKSLASH = "\n\\backslash\n"
32 HTMLONLY_START = "\\htmlonly"
33 HTMLONLY_END = "\\endhtmlonly"
34 LFUN_NAME_ID = "\\var lyx::FuncCode lyx::"
35 LFUN_ACTION_ID = "\\li Action: "
36 LFUN_NOTION_ID = "\\li Notion: "
37 LFUN_SYNTAX_ID = "\\li Syntax: "
38 LFUN_PARAMS_ID = "\\li Params: "
39 LFUN_SAMPLE_ID = "\\li Sample: "
40 LFUN_ORIGIN_ID = "\\li Origin: "
41 LFUN_ENDVAR = "\\endvar"
43 ID_DICT = dict(name=LFUN_NAME_ID, action=LFUN_ACTION_ID, notion=LFUN_NOTION_ID,
44 syntax=LFUN_SYNTAX_ID, params=LFUN_PARAMS_ID, sample=LFUN_SAMPLE_ID, origin=LFUN_ORIGIN_ID)
46 LFUNS_HEADER = """# gen_lfuns.py generated this file. For more info see http://www.lyx.org/
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
64 \\language_package default
69 \\font_typewriter default
71 \\font_default_family default
72 \\use_non_tex_fonts false
78 \\default_output_format default
80 \\bibtex_command default
81 \\index_command default
82 \\paperfontsize default
87 \\use_package amsmath 1
88 \\use_package amssymb 1
89 \\use_package cancel 0
91 \\use_package mathdots 0
92 \\use_package mathtools 0
93 \\use_package mhchem 1
94 \\use_package stackrel 0
95 \\use_package stmaryrd 0
96 \\use_package undertilde 0
98 \\cite_engine_type default
102 \\paperorientation portrait
103 \\suppress_date false
116 \\paragraph_separation indent
117 \\paragraph_indentation default
118 \\quotes_language english
121 \\paperpagestyle default
122 \\tracking_changes false
123 \\output_changes false
126 \\html_be_strict false
132 LyX Functions (LFUNs)
135 \\begin_layout Author
139 \\begin_layout Date""" + "\n" + str(date.today()) + """
142 \\begin_layout Standard
146 \\begin_layout Plain Layout
156 \\begin_inset VSpace 1cm
162 LFUNS_FOOTER = """\\end_body
167 """Takes a comment block (str) and parses it for fields describing the LFUN. Returns a dict containing the fields."""
169 lfun = dict(name="", action="", notion="", syntax="", params="", sample="", origin="")
171 lines = str.splitlines()
172 # strip leading whitespace and * from the lines of the comment to get
173 # rid of unimportant characters
174 for i in range(0, len(lines)):
175 lines[i] = lines[i].strip(" *")
177 for i in range(0, len(lines) - 1):
178 # work out what field is being read if none of these is found, the line will be added
179 # to the last field edited
180 # since the field identifier is not included skip it out if it's found, otherwise skip
181 # nothing as an existing field is being added to
182 # if a field id is found, then its the first line of the field so set the pre_space to ""
183 # so that the first line isn't prespaced
184 if lines[i].startswith(LFUN_NAME_ID):
187 skip = len(ID_DICT[field])
188 elif lines[i].startswith(LFUN_ACTION_ID):
191 skip = len(ID_DICT[field])
192 elif lines[i].startswith(LFUN_NOTION_ID):
195 skip = len(ID_DICT[field])
196 elif lines[i].startswith(LFUN_SYNTAX_ID):
199 skip = len(ID_DICT[field])
200 elif lines[i].startswith(LFUN_PARAMS_ID):
203 skip = len(ID_DICT[field])
204 elif lines[i].startswith(LFUN_SAMPLE_ID):
207 skip = len(ID_DICT[field])
208 elif lines[i].startswith(LFUN_ORIGIN_ID):
211 skip = len(ID_DICT[field])
212 elif lines[i].startswith(LFUN_ENDVAR):
216 # if a manual line break was found last line, don't prespace this line
217 if i > 1 and lines[i-1].endswith("\\n"):
222 # add the line to the field, processing it for \ characters and \n
223 # which, if occurring at the end of a line, must become a LYX_NEWLINE
224 line = lines[i][skip:]
226 # deal with \htmlonly
227 # TODO: convert chars found in htmlonly to unicode
228 start = line.find(HTMLONLY_START)
230 # if removing the htmlonly element leaves a double space, go back one to remove it
231 if line[start-1] == " ":
233 end = line.find(HTMLONLY_END)
235 end = line.find(HTMLONLY_END) + len(HTMLONLY_END)
236 line = line[:start] + line[end:]
238 # TODO: if HTMLONLY_END is not found, look on the next line
239 # TODO: in the current LyXAction.cpp there are no htmlonly fields which go over a line break
241 # deal with \ but leave \n if at the end of the line
242 slash_idx = line.find("\\")
243 while slash_idx >= 0:
244 if slash_idx < len(line)-2 \
245 or slash_idx == len(line)-1:
246 # true when the \ is not the last or second last char
247 # or when the slash is the last char of the line
249 # slash must be interpreted literaly so swap it for a LYX_BACKSLASH
250 line = line[:slash_idx] + LYX_BACKSLASH + line[slash_idx+1:]
251 # skip the index ahead beyond the added text
252 slash_idx = slash_idx + len(LYX_BACKSLASH)
253 elif line[slash_idx+1] != "n": # only evaluated if the line ends "\x" where 'x' != 'n'
254 line = line[:slash_idx] + LYX_BACKSLASH + line[slash_idx+1:]
255 # skip the index ahead beyond the added text
256 slash_idx = slash_idx + len(LYX_BACKSLASH)
257 # look for the next \
258 slash_idx = line.find("\\", slash_idx+1)
260 # \n at the end of lines will not be processed by the above while loop
261 # so sort those out now
262 # sometime lines end " \n" so chop the space if its there
263 if line.endswith(" \\n"):
264 line = line[:len(line)-3] + LYX_NEWLINE
265 elif line.endswith("\\n"):
266 line = line[:len(line)-2] + LYX_NEWLINE
268 # any references to other LFUNs need the # removing
269 # TODO: actually insert a cross-reference here
270 line = line.replace("#LFUN", "LFUN")
272 # handle the few #lyx:: cases
273 line = line.replace("#lyx::", "lyx::")
275 # the first line might not have a field in it in which
276 # case the variable field won't have a value, so check
279 lfun[field] = lfun[field] + pre_space + line
281 # TODO: sort out chopping lines of more that 80 chars in length
285 def write_fields(file, lfun):
286 """Writes the LFUN contained in the dict lfun to the file. Does not write a the file header or footer"""
287 # add lfun to LFUNs.lyx
288 file.write("\\begin_layout Subsection*\n")
289 file.write(lfun["name"] + "\n")
290 file.write("\\end_layout\n")
292 if lfun["action"] != "":
293 file.write("\\begin_layout Description\n")
294 file.write("Action " + lfun["action"] + "\n")
296 file.write("\\end_layout\n")
298 if lfun["notion"] != "":
299 file.write("\\begin_layout Description\n")
300 file.write("Notion " + lfun["notion"] + "\n")
301 file.write("\\end_layout\n")
303 if lfun["syntax"] != "":
304 file.write("\\begin_layout Description\n")
305 file.write("Syntax " + lfun["syntax"] + "\n")
306 file.write("\\end_layout\n")
308 if lfun["params"] != "":
309 file.write("\\begin_layout Description\n")
310 file.write("Params " + lfun["params"] + "\n")
311 file.write("\\end_layout\n")
313 if lfun["sample"] != "":
314 file.write("\\begin_layout Description\n")
315 file.write("Sample " + lfun["sample"] + "\n")
316 file.write("\\end_layout\n")
318 if lfun["origin"] != "":
319 file.write("\\begin_layout Description\n")
320 file.write("Origin " + lfun["origin"] + "\n")
321 file.write("\\end_layout\n")
326 # parse command line arguments
327 script_path, script_name = os.path.split(argv[0])
329 error(usage(script_name))
331 lyxaction_path = argv[1]
332 if not os.path.exists(lyxaction_path):
333 error(script_name + ": %s is not a valid path" % lyxaction_path, usage(argv[0]))
338 if os.path.isdir(lfuns_path):
339 lfuns_path = lfuns_path + "LFUNs.lyx"
340 elif os.path.exists(lfuns_path):
341 error(script_name + ": %s already exists, delete it and rerun the script" % lfuns_path)
342 lfuns_file = open(lfuns_path, 'wb')
344 lfuns_file = sys.stdout
346 sys.stderr.write(script_name + ": Start processing " + argv[1] + '\n')
347 # Read the input file and write the output file
348 lyxaction_file = open(lyxaction_path, 'rb')
350 lyxaction_text = lyxaction_file.read()
352 lfuns_file.write(LFUNS_HEADER)
354 # seek to the important bit of LyXAction.cpp
356 start = lyxaction_text.index("ev_item const items[] = {")
358 lyxaction_file.close()
360 error(script_name + ": LFUNs not found in " + lyxaction_file)
364 lfun_list_unsorted = []
367 # look for a doxygen comment
368 start = lyxaction_text.find(DOXYGEN_START, start)
369 end = lyxaction_text.find(DOXYGEN_END, start) + len(DOXYGEN_END)
370 # parse the lfun if it is found
373 lfun = parse_lfun(lyxaction_text[start:end])
374 # save the lfun (we sort it before writing)
375 lfun_list_unsorted.append(lfun)
379 # if no more lfuns are found, EOF reached
382 lfun_list = sorted(lfun_list_unsorted, key=lambda k: k['name'])
384 # write the lfuns to the file
386 write_fields(lfuns_file, lf)
388 sys.stderr.write(script_name + ": Created documentation for " + str(count) + " LFUNs\n")
390 # write the last part of LFUNs.lyx
391 lfuns_file.write(LFUNS_FOOTER)
393 lyxaction_file.close()
396 sys.stderr.write(script_name + ": Finished\n")
398 if __name__ == "__main__":