]> git.lyx.org Git - features.git/blob - development/tools/gen_lfuns.py
Make gen_lfuns.py python3 compatible
[features.git] / development / tools / gen_lfuns.py
1 #! /usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 # file gen_lfuns.py
5 # This file is part of LyX, the document processor.
6 # Licence details can be found in the file COPYING.
7
8 # author Ewan Davies
9
10 # Full author contact details are available in file CREDITS
11
12 # Usage:
13 # gen_lfuns.py <path/to/LyXAction.cpp> <where/to/save/LFUNs.lyx>
14
15 import sys,re,os.path
16 import io
17
18 def error(message):
19     sys.stderr.write(message + '\n')
20     sys.exit(1)
21
22 def usage(prog_name):
23     return "Usage: %s <path/to/LyXAction.cpp> [<where/to/save/LFUNs.lyx>]" % prog_name
24
25 DOXYGEN_START = "/*!"
26 DOXYGEN_END = "},"
27
28 LYX_NEWLINE = u"\n\\begin_inset Newline newline\n\\end_inset\n\n"
29 LYX_BACKSLASH = u"\n\\backslash\n"
30
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"
41
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)
44
45 LFUNS_HEADER = u"""# gen_lfuns.py generated this file. For more info see http://www.lyx.org/
46 \\lyxformat 506
47 \\begin_document
48 \\begin_header
49 \\save_transient_properties true
50 \\origin /systemlyxdir/doc/
51 \\textclass article
52 \\begin_preamble
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}%
58 }{
59   \\endlist
60 }
61 \\end_preamble
62 \\use_default_options false
63 \\maintain_unincluded_children false
64 \\begin_local_layout
65 Style Description
66 LabelIndent           MM
67 LeftMargin            MMMMMxx
68 End
69 \\end_local_layout
70 \\language english
71 \\language_package default
72 \\inputencoding auto
73 \\fontencoding global
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
80 \\font_sc false
81 \\font_osf false
82 \\font_sf_scale 100 100
83 \\font_tt_scale 100 100
84 \\graphics default
85 \\default_output_format default
86 \\output_sync 0
87 \\bibtex_command default
88 \\index_command default
89 \\paperfontsize default
90 \\spacing single
91 \\use_hyperref false
92 \\papersize default
93 \\use_geometry true
94 \\use_package amsmath 1
95 \\use_package amssymb 1
96 \\use_package cancel 0
97 \\use_package esint 1
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
104 \\cite_engine basic
105 \\cite_engine_type default
106 \\biblio_style plain
107 \\use_bibtopic false
108 \\use_indices false
109 \\paperorientation portrait
110 \\suppress_date false
111 \\justification true
112 \\use_refstyle 0
113 \\index Index
114 \\shortcut idx
115 \\color #008000
116 \\end_index
117 \\leftmargin 2.5cm
118 \\topmargin 2cm
119 \\rightmargin 3cm
120 \\bottommargin 2.5cm
121 \\secnumdepth 3
122 \\tocdepth 3
123 \\paragraph_separation indent
124 \\paragraph_indentation default
125 \\quotes_language english
126 \\papercolumns 1
127 \\papersides 1
128 \\paperpagestyle default
129 \\tracking_changes false
130 \\output_changes false
131 \\html_math_output 0
132 \\html_css_as_file 0
133 \\html_be_strict false
134 \\end_header
135
136 \\begin_body
137
138 \\begin_layout Title
139 \\SpecialChar LyX
140  Functions (LFUNs)
141 \\end_layout
142
143 \\begin_layout Author
144 The \\SpecialChar LyX
145  Team
146 \\end_layout
147
148 """
149
150 LFUNS_INTRO = u"""\\begin_layout Section*
151 About this manual
152 \\end_layout
153
154 \\begin_layout Standard
155 This manual documents the 
156 \\begin_inset Quotes eld
157 \\end_inset
158
159 LyX Functions
160 \\begin_inset Quotes erd
161 \\end_inset
162
163  (abbreviated LFUNs).
164  These are commands that are used to make \\SpecialChar LyX
165  perform specific actions.
166  \\SpecialChar LyX
167  itself uses these functions internally, and every internal action is
168  bound to an LFUN.
169 \\end_layout
170
171 \\begin_layout Standard
172 LFUNs are also used in the files that define keyboard shortcuts, menu or
173  toolbar items.
174  So if you want to change\\SpecialChar breakableslash
175 customize the user interface, you need to deal
176  with LFUNs.
177  Furthermore, external programs can use LFUNs to communicate with and 
178 \\begin_inset Quotes eld
179 \\end_inset
180
181 remote-control
182 \\begin_inset Quotes erd
183 \\end_inset
184
185  \\SpecialChar LyX
186  .
187  Finally, you can also issue LFUNs directly via the so called mini-buffer
188  which can be opened via 
189 \\begin_inset Info
190 type  "shortcuts"
191 arg   "command-execute"
192 \\end_inset
193
194 .
195 \\end_layout
196
197 \\begin_layout Standard
198 In the following, all LFUNs are listed, categorized by function.
199 \\end_layout
200
201 """
202
203
204 LFUNS_FOOTER = u"""\\end_body
205 \\end_document
206 """
207
208 def parse_lfun(str):
209     """Takes a comment block (str) and parses it for fields describing the LFUN. Returns a dict containing the fields."""
210     
211     lfun = dict(action="", notion="", syntax="", params="", sample="", origin="")
212     field = ""
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(" *")
218     
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):
227             field = "action"
228             pre_space = ""
229             skip = len(ID_DICT[field])
230         elif lines[i].startswith(LFUN_NOTION_ID):
231             field = "notion"
232             pre_space = ""
233             skip = len(ID_DICT[field])
234         elif lines[i].startswith(LFUN_SYNTAX_ID):
235             field = "syntax"
236             pre_space = ""
237             skip = len(ID_DICT[field])
238         elif lines[i].startswith(LFUN_PARAMS_ID):
239             field = "params"
240             pre_space = ""
241             skip = len(ID_DICT[field])
242         elif lines[i].startswith(LFUN_SAMPLE_ID):
243             field = "sample"
244             pre_space = ""
245             skip = len(ID_DICT[field])
246         elif lines[i].startswith(LFUN_ORIGIN_ID):
247             field = "origin"
248             pre_space = ""
249             skip = len(ID_DICT[field])
250         elif lines[i].startswith(LFUN_ENDVAR):
251             break
252         else:
253             skip = 0
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"):
256                 pre_space = ""
257             else:
258                 pre_space = " "
259         
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:]
263         
264         # deal with \htmlonly
265         # TODO: convert chars found in htmlonly to unicode
266         start = line.find(HTMLONLY_START)
267         if start > 0:
268             # if removing the htmlonly element leaves a double space, go back one to remove it
269             if line[start-1] == " ":
270                 start = start - 1
271             end = line.find(HTMLONLY_END)
272             if end > start:
273                 end = line.find(HTMLONLY_END) + len(HTMLONLY_END)
274                 line = line[:start] + line[end:]
275             #else:
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
278         
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
286                 
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)
297             
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
305         
306         # any references to other LFUNs need the # removing
307         # TODO: actually insert a cross-reference here
308         line = line.replace("#LFUN", "LFUN")
309         
310         # handle the few #lyx:: cases
311         line = line.replace("#lyx::", "lyx::")
312
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
315         # to avoid an error
316         if field != "":
317             lfun[field] = lfun[field] + pre_space + line
318         
319         # TODO: sort out chopping lines of more that 80 chars in length
320         
321     return lfun
322
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")
329     file.write(u"\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")
334         file.write(u"\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")
339         file.write(u"\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")
344         file.write(u"\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")
349         file.write(u"\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")
354         file.write(u"\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")
359         file.write(u"\n")
360
361 def write_sections(file,lfuns):
362     """Write sections of LFUNs"""
363     sections = ["Layout", "Edit", "Math", "Buffer", "System", "Hidden"]
364     section_headings = {
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)"
371         }
372         # write the lfuns to the file
373     for val in sections:
374         file.write(u"\\begin_layout Section\n")
375         file.write(section_headings[val] + "\n")
376         file.write(u"\\end_layout\n")
377         file.write(u"\n")
378         for lf in lfuns:
379             if lf["type"] == val:
380                 write_fields(file, lf)
381     
382 def main(argv):
383     # parse command line arguments
384     script_path, script_name = os.path.split(argv[0])
385     if len(argv) < 2:
386         error(usage(script_name))
387     # input file
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)
391
392     # output file
393     if len(argv) == 3:
394         lfuns_path = argv[2]
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')
400     else:
401         lfuns_file = sys.stdout
402
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')
406
407     lyxaction_text = lyxaction_file.read()
408
409     lfuns_file.write(LFUNS_HEADER)
410     
411     # An introductory section
412     lfuns_file.write(LFUNS_INTRO)
413
414     # seek to the important bit of LyXAction.cpp
415     try:
416         start = lyxaction_text.index("ev_item const items[] = {")
417     except ValueError:
418         lyxaction_file.close()
419         lfuns_file.close()
420         error(script_name + ": LFUNs not found in " + lyxaction_file)
421
422     done = count = 0
423
424     lfun_list_unsorted = []
425
426     while done == 0:
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)
430         name = ""
431         atype = ""
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)
435         if match:
436             name = match.group(2)
437             atype = match.group(4)
438         # parse the lfun if it is found
439         if start > 0:
440             if name:
441                 count = count + 1
442                 lfun = parse_lfun(snippet)
443                 lfun["name"] = name
444                 lfun["type"] = atype
445                 # save the lfun (we sort it before writing)
446                 lfun_list_unsorted.append(lfun)
447             # get the next one
448             start = end
449         else:
450             # if no more lfuns are found, EOF reached
451             done = 1
452
453     lfun_list = sorted(lfun_list_unsorted, key=lambda k: k['name'])
454     
455     # write the lfuns to the file
456     write_sections(lfuns_file, lfun_list)
457
458     sys.stderr.write(script_name + ": Created documentation for " + str(count) + " LFUNs\n")
459
460     # write the last part of LFUNs.lyx
461     lfuns_file.write(LFUNS_FOOTER)
462     
463     lyxaction_file.close()
464     lfuns_file.close()
465     
466     sys.stderr.write(script_name + ": Finished\n")
467     
468 if __name__ == "__main__":
469     main(sys.argv)