]> git.lyx.org Git - features.git/blob - development/tools/gen_lfuns.py
Update LFUNs.lyx to current format
[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 from datetime import date
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 = "\n\\begin_inset Newline newline\n\\end_inset\n\n"
29 LYX_BACKSLASH = "\n\\backslash\n"
30
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"
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 = """# gen_lfuns.py generated this file. For more info see http://www.lyx.org/
46 \\lyxformat 501
47 \\begin_document
48 \\begin_header
49 \\origin /systemlyxdir/doc/
50 \\textclass article
51 \\begin_preamble
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}%
57 }{
58   \\endlist
59 }
60 \\end_preamble
61 \\use_default_options false
62 \\maintain_unincluded_children false
63 \\begin_local_layout
64 Style Description
65 LabelIndent           MM
66 LeftMargin            MMMMMxx
67 End
68 \\end_local_layout
69 \\language english
70 \\language_package default
71 \\inputencoding auto
72 \\fontencoding global
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
79 \\font_sc false
80 \\font_osf false
81 \\font_sf_scale 100 100
82 \\font_tt_scale 100 100
83 \\graphics default
84 \\default_output_format default
85 \\output_sync 0
86 \\bibtex_command default
87 \\index_command default
88 \\paperfontsize default
89 \\spacing single
90 \\use_hyperref false
91 \\papersize default
92 \\use_geometry true
93 \\use_package amsmath 1
94 \\use_package amssymb 1
95 \\use_package cancel 0
96 \\use_package esint 1
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
103 \\cite_engine basic
104 \\cite_engine_type default
105 \\biblio_style plain
106 \\use_bibtopic false
107 \\use_indices false
108 \\paperorientation portrait
109 \\suppress_date false
110 \\justification true
111 \\use_refstyle 0
112 \\index Index
113 \\shortcut idx
114 \\color #008000
115 \\end_index
116 \\leftmargin 2.5cm
117 \\topmargin 2cm
118 \\rightmargin 3cm
119 \\bottommargin 2.5cm
120 \\secnumdepth 3
121 \\tocdepth 3
122 \\paragraph_separation indent
123 \\paragraph_indentation default
124 \\quotes_language english
125 \\papercolumns 1
126 \\papersides 1
127 \\paperpagestyle default
128 \\tracking_changes false
129 \\output_changes false
130 \\html_math_output 0
131 \\html_css_as_file 0
132 \\html_be_strict false
133 \\end_header
134
135 \\begin_body
136
137 \\begin_layout Title
138 \\SpecialChar LyX
139  Functions (LFUNs)
140 \\end_layout
141
142 \\begin_layout Author
143 The \\SpecialChar LyX
144  Team
145 \\end_layout
146
147 \\begin_layout Date""" + "\n" + str(date.today()) + """
148 \\end_layout
149
150 """
151
152 LFUNS_INTRO ="""\\begin_layout Section*
153 About this manual
154 \\end_layout
155
156 \\begin_layout Standard
157 This manual documents the 
158 \\begin_inset Quotes eld
159 \\end_inset
160
161 LyX Functions
162 \\begin_inset Quotes erd
163 \\end_inset
164
165  (abbreviated LFUNs).
166  These are commands that are used to make \\SpecialChar LyX
167  perform specific actions.
168  \\SpecialChar LyX
169  itself uses these functions internally, and every internal action is
170  bound to an LFUN.
171 \\end_layout
172
173 \\begin_layout Standard
174 LFUNs are also used in the files that define keyboard shortcuts, menu or
175  toolbar items.
176  So if you want to change\\SpecialChar breakableslash
177 customize the user interface, you need to deal
178  with LFUNs.
179  Furthermore, external programs can use LFUNs to communicate with and 
180 \\begin_inset Quotes eld
181 \\end_inset
182
183 remote-control
184 \\begin_inset Quotes erd
185 \\end_inset
186
187  \\SpecialChar LyX
188  .
189  Finally, you can also issue LFUNs directly via the so called mini-buffer
190  which can be opened via 
191 \\begin_inset Info
192 type  "shortcuts"
193 arg   "command-execute"
194 \\end_inset
195
196 .
197 \\end_layout
198
199 \\begin_layout Standard
200 In the following, all LFUNs are listed, categorized by function.
201 \\end_layout
202
203 """
204
205
206 LFUNS_FOOTER = """\\end_body
207 \\end_document
208 """
209
210 def parse_lfun(str):
211     """Takes a comment block (str) and parses it for fields describing the LFUN. Returns a dict containing the fields."""
212     
213     lfun = dict(action="", notion="", syntax="", params="", sample="", origin="")
214     field = ""
215     lines = str.splitlines()
216     # strip leading whitespace and * from the lines of the comment to get 
217     # rid of unimportant characters
218     for i in range(0, len(lines)):
219         lines[i] = lines[i].strip(" *")
220     
221     for i in range(0, len(lines) - 1):
222         # work out what field is being read if none of these is found, the line will be added
223         #     to the last field edited
224         # since the field identifier is not included skip it out if it's found, otherwise skip
225         #     nothing as an existing field is being added to
226         # if a field id is found, then its the first line of the field so set the pre_space to ""
227         #     so that the first line isn't prespaced
228         if lines[i].startswith(LFUN_ACTION_ID):
229             field = "action"
230             pre_space = ""
231             skip = len(ID_DICT[field])
232         elif lines[i].startswith(LFUN_NOTION_ID):
233             field = "notion"
234             pre_space = ""
235             skip = len(ID_DICT[field])
236         elif lines[i].startswith(LFUN_SYNTAX_ID):
237             field = "syntax"
238             pre_space = ""
239             skip = len(ID_DICT[field])
240         elif lines[i].startswith(LFUN_PARAMS_ID):
241             field = "params"
242             pre_space = ""
243             skip = len(ID_DICT[field])
244         elif lines[i].startswith(LFUN_SAMPLE_ID):
245             field = "sample"
246             pre_space = ""
247             skip = len(ID_DICT[field])
248         elif lines[i].startswith(LFUN_ORIGIN_ID):
249             field = "origin"
250             pre_space = ""
251             skip = len(ID_DICT[field])
252         elif lines[i].startswith(LFUN_ENDVAR):
253             break
254         else:
255             skip = 0
256             # if a manual line break was found last line, don't prespace this line
257             if i > 1 and lines[i-1].endswith("\\n"):
258                 pre_space = ""
259             else:
260                 pre_space = " "
261         
262         # add the line to the field, processing it for \ characters and \n
263         # which, if occurring at the end of a line, must become a LYX_NEWLINE
264         line = lines[i][skip:]
265         
266         # deal with \htmlonly
267         # TODO: convert chars found in htmlonly to unicode
268         start = line.find(HTMLONLY_START)
269         if start > 0:
270             # if removing the htmlonly element leaves a double space, go back one to remove it
271             if line[start-1] == " ":
272                 start = start - 1
273             end = line.find(HTMLONLY_END)
274             if end > start:
275                 end = line.find(HTMLONLY_END) + len(HTMLONLY_END)
276                 line = line[:start] + line[end:]
277             #else:
278             # TODO: if HTMLONLY_END is not found, look on the next line
279             # TODO: in the current LyXAction.cpp there are no htmlonly fields which go over a line break
280         
281         # deal with \ but leave \n if at the end of the line
282         slash_idx = line.find("\\")
283         while slash_idx >= 0:
284             if slash_idx < len(line)-2 \
285             or slash_idx == len(line)-1:
286                 # true when the \ is not the last or second last char
287                 #      or when the slash is the last char of the line
288                 
289                 # slash must be interpreted literaly so swap it for a LYX_BACKSLASH
290                 line = line[:slash_idx] + LYX_BACKSLASH + line[slash_idx+1:]
291                 # skip the index ahead beyond the added text
292                 slash_idx = slash_idx + len(LYX_BACKSLASH)
293             elif line[slash_idx+1] != "n": # only evaluated if the line ends "\x" where 'x' != 'n'
294                 line = line[:slash_idx] + LYX_BACKSLASH + line[slash_idx+1:]
295                 # skip the index ahead beyond the added text
296                 slash_idx = slash_idx + len(LYX_BACKSLASH) 
297             # look for the next \
298             slash_idx = line.find("\\", slash_idx+1)
299             
300         # \n at the end of lines will not be processed by the above while loop
301         # so sort those out now
302         # sometime lines end " \n" so chop the space if its there
303         if line.endswith(" \\n"):
304             line = line[:len(line)-3] + LYX_NEWLINE
305         elif line.endswith("\\n"):
306             line = line[:len(line)-2] + LYX_NEWLINE
307         
308         # any references to other LFUNs need the # removing
309         # TODO: actually insert a cross-reference here
310         line = line.replace("#LFUN", "LFUN")
311         
312         # handle the few #lyx:: cases
313         line = line.replace("#lyx::", "lyx::")
314
315         # the first line might not have a field in it in which
316         # case the variable field won't have a value, so check
317         # to avoid an error
318         if field != "":
319             lfun[field] = lfun[field] + pre_space + line
320         
321         # TODO: sort out chopping lines of more that 80 chars in length
322         
323     return lfun
324
325 def write_fields(file, lfun):
326     """Writes the LFUN contained in the dict lfun to the file. Does not write a the file header or footer"""
327     # add lfun to LFUNs.lyx
328     file.write("\\begin_layout Subsection*\n")
329     file.write(lfun["name"] + "\n")
330     file.write("\\end_layout\n")
331     #file.write("\n")
332     if lfun["action"] != "":
333         file.write("\\begin_layout Description\n")
334         file.write("Action " + lfun["action"] + "\n")
335         #file.write("\n")
336         file.write("\\end_layout\n")
337         #file.write("\n")
338     if lfun["notion"] != "":
339         file.write("\\begin_layout Description\n")
340         file.write("Notion " + lfun["notion"] + "\n")
341         file.write("\\end_layout\n")
342         #file.write("\n")
343     if lfun["syntax"] != "":
344         file.write("\\begin_layout Description\n")
345         file.write("Syntax " + lfun["syntax"] + "\n")
346         file.write("\\end_layout\n")
347         #file.write("\n")
348     if lfun["params"] != "":
349         file.write("\\begin_layout Description\n")
350         file.write("Params " + lfun["params"] + "\n")
351         file.write("\\end_layout\n")
352         #file.write("\n")
353     if lfun["sample"] != "":
354         file.write("\\begin_layout Description\n")
355         file.write("Sample " + lfun["sample"] + "\n")
356         file.write("\\end_layout\n")
357         #file.write("\n")
358     if lfun["origin"] != "":
359         file.write("\\begin_layout Description\n")
360         file.write("Origin " + lfun["origin"] + "\n")
361         file.write("\\end_layout\n")
362         #file.write("\n")
363     file.write("\n")
364
365 def write_sections(file,lfuns):
366     """Write sections of LFUNs"""
367     sections = ["Layout", "Edit", "Math", "Buffer", "System", "Hidden"]
368     section_headings = {
369         "Layout":  "Layout Functions (Font, Layout and Textclass related)",
370         "Edit":  "Editing Functions (Cursor and Mouse Movement, Copy/Paste etc.)",
371         "Math":  "Math Editor Functions",
372         "Buffer":  "Buffer Fuctions (File and Window related)",
373         "System":  "System Functions (Preferences, LyX Server etc.)",
374         "Hidden":  "Hidden Functions (not listed for configuration)"
375         }
376         # write the lfuns to the file
377     for val in sections:
378         file.write("\\begin_layout Section\n")
379         file.write(section_headings[val] + "\n")
380         file.write("\\end_layout\n")
381         for lf in lfuns:
382             if lf["type"] == val:
383                 write_fields(file, lf)
384     
385 def main(argv):
386     # parse command line arguments
387     script_path, script_name = os.path.split(argv[0])
388     if len(argv) < 2:
389         error(usage(script_name))
390     # input file
391     lyxaction_path = argv[1]
392     if not os.path.isfile(lyxaction_path):
393         error(script_name + ": %s is not a valid path" % lyxaction_path)
394
395     # output file
396     if len(argv) == 3:
397         lfuns_path = argv[2]
398         if os.path.isdir(lfuns_path):
399             lfuns_path = lfuns_path + "LFUNs.lyx"
400         elif os.path.exists(lfuns_path):
401             error(script_name + ": %s already exists, delete it and rerun the script" % lfuns_path)
402         lfuns_file = open(lfuns_path, 'wb')
403     else:
404         lfuns_file = sys.stdout
405
406     sys.stderr.write(script_name + ": Start processing " + argv[1] + '\n')
407     # Read the input file and write the output file
408     lyxaction_file = open(lyxaction_path, 'rb')
409
410     lyxaction_text = lyxaction_file.read()
411
412     lfuns_file.write(LFUNS_HEADER)
413     
414     # An introductory section
415     lfuns_file.write(LFUNS_INTRO)
416
417     # seek to the important bit of LyXAction.cpp
418     try:
419         start = lyxaction_text.index("ev_item const items[] = {")
420     except ValueError:
421         lyxaction_file.close()
422         lfuns_file.close()
423         error(script_name + ": LFUNs not found in " + lyxaction_file)
424
425     done = count = 0
426
427     lfun_list_unsorted = []
428
429     while done == 0:
430         # look for a doxygen comment
431         start = lyxaction_text.find(DOXYGEN_START, start)
432         end = lyxaction_text.find(DOXYGEN_END, start) + len(DOXYGEN_END)
433         name = ""
434         atype = ""
435         snippet = lyxaction_text[start:end]
436         defline = snippet.replace("\n", "")
437         match = re.match(r'.*\s*\{\s*(.+),\s*"(.*)",\s*([\w\|\s]+),\s*(\w+)\s*\},.*$', defline)
438         if match:
439             name = match.group(2)
440             atype = match.group(4)
441         # parse the lfun if it is found
442         if start > 0:
443             if name:
444                 count = count + 1
445                 lfun = parse_lfun(snippet)
446                 lfun["name"] = name
447                 lfun["type"] = atype
448                 # save the lfun (we sort it before writing)
449                 lfun_list_unsorted.append(lfun)
450             # get the next one
451             start = end
452         else:
453             # if no more lfuns are found, EOF reached
454             done = 1
455
456     lfun_list = sorted(lfun_list_unsorted, key=lambda k: k['name'])
457     
458     # write the lfuns to the file
459     write_sections(lfuns_file, lfun_list)
460
461     sys.stderr.write(script_name + ": Created documentation for " + str(count) + " LFUNs\n")
462
463     # write the last part of LFUNs.lyx
464     lfuns_file.write(LFUNS_FOOTER)
465     
466     lyxaction_file.close()
467     lfuns_file.close()
468     
469     sys.stderr.write(script_name + ": Finished\n")
470     
471 if __name__ == "__main__":
472     main(sys.argv)