]> git.lyx.org Git - lyx.git/blob - development/tools/gen_lfuns.py
Cmake export tests: Some more corrected exports.
[lyx.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("\\end_layout\n")
336         file.write("\n")
337     if lfun["notion"] != "":
338         file.write("\\begin_layout Description\n")
339         file.write("Notion " + lfun["notion"] + "\n")
340         file.write("\\end_layout\n")
341         file.write("\n")
342     if lfun["syntax"] != "":
343         file.write("\\begin_layout Description\n")
344         file.write("Syntax " + lfun["syntax"] + "\n")
345         file.write("\\end_layout\n")
346         file.write("\n")
347     if lfun["params"] != "":
348         file.write("\\begin_layout Description\n")
349         file.write("Params " + lfun["params"] + "\n")
350         file.write("\\end_layout\n")
351         file.write("\n")
352     if lfun["sample"] != "":
353         file.write("\\begin_layout Description\n")
354         file.write("Sample " + lfun["sample"] + "\n")
355         file.write("\\end_layout\n")
356         file.write("\n")
357     if lfun["origin"] != "":
358         file.write("\\begin_layout Description\n")
359         file.write("Origin " + lfun["origin"] + "\n")
360         file.write("\\end_layout\n")
361         file.write("\n")
362
363 def write_sections(file,lfuns):
364     """Write sections of LFUNs"""
365     sections = ["Layout", "Edit", "Math", "Buffer", "System", "Hidden"]
366     section_headings = {
367         "Layout":  "Layout Functions (Font, Layout and Textclass related)",
368         "Edit":  "Editing Functions (Cursor and Mouse Movement, Copy/Paste etc.)",
369         "Math":  "Math Editor Functions",
370         "Buffer":  "Buffer Fuctions (File and Window related)",
371         "System":  "System Functions (Preferences, LyX Server etc.)",
372         "Hidden":  "Hidden Functions (not listed for configuration)"
373         }
374         # write the lfuns to the file
375     for val in sections:
376         file.write("\\begin_layout Section\n")
377         file.write(section_headings[val] + "\n")
378         file.write("\\end_layout\n")
379         file.write("\n")
380         for lf in lfuns:
381             if lf["type"] == val:
382                 write_fields(file, lf)
383     
384 def main(argv):
385     # parse command line arguments
386     script_path, script_name = os.path.split(argv[0])
387     if len(argv) < 2:
388         error(usage(script_name))
389     # input file
390     lyxaction_path = argv[1]
391     if not os.path.isfile(lyxaction_path):
392         error(script_name + ": %s is not a valid path" % lyxaction_path)
393
394     # output file
395     if len(argv) == 3:
396         lfuns_path = argv[2]
397         if os.path.isdir(lfuns_path):
398             lfuns_path = lfuns_path + "LFUNs.lyx"
399         elif os.path.exists(lfuns_path):
400             error(script_name + ": %s already exists, delete it and rerun the script" % lfuns_path)
401         lfuns_file = open(lfuns_path, 'wb')
402     else:
403         lfuns_file = sys.stdout
404
405     sys.stderr.write(script_name + ": Start processing " + argv[1] + '\n')
406     # Read the input file and write the output file
407     lyxaction_file = open(lyxaction_path, 'rb')
408
409     lyxaction_text = lyxaction_file.read()
410
411     lfuns_file.write(LFUNS_HEADER)
412     
413     # An introductory section
414     lfuns_file.write(LFUNS_INTRO)
415
416     # seek to the important bit of LyXAction.cpp
417     try:
418         start = lyxaction_text.index("ev_item const items[] = {")
419     except ValueError:
420         lyxaction_file.close()
421         lfuns_file.close()
422         error(script_name + ": LFUNs not found in " + lyxaction_file)
423
424     done = count = 0
425
426     lfun_list_unsorted = []
427
428     while done == 0:
429         # look for a doxygen comment
430         start = lyxaction_text.find(DOXYGEN_START, start)
431         end = lyxaction_text.find(DOXYGEN_END, start) + len(DOXYGEN_END)
432         name = ""
433         atype = ""
434         snippet = lyxaction_text[start:end]
435         defline = snippet.replace("\n", "")
436         match = re.match(r'.*\s*\{\s*(.+),\s*"(.*)",\s*([\w\|\s]+),\s*(\w+)\s*\},.*$', defline)
437         if match:
438             name = match.group(2)
439             atype = match.group(4)
440         # parse the lfun if it is found
441         if start > 0:
442             if name:
443                 count = count + 1
444                 lfun = parse_lfun(snippet)
445                 lfun["name"] = name
446                 lfun["type"] = atype
447                 # save the lfun (we sort it before writing)
448                 lfun_list_unsorted.append(lfun)
449             # get the next one
450             start = end
451         else:
452             # if no more lfuns are found, EOF reached
453             done = 1
454
455     lfun_list = sorted(lfun_list_unsorted, key=lambda k: k['name'])
456     
457     # write the lfuns to the file
458     write_sections(lfuns_file, lfun_list)
459
460     sys.stderr.write(script_name + ": Created documentation for " + str(count) + " LFUNs\n")
461
462     # write the last part of LFUNs.lyx
463     lfuns_file.write(LFUNS_FOOTER)
464     
465     lyxaction_file.close()
466     lfuns_file.close()
467     
468     sys.stderr.write(script_name + ": Finished\n")
469     
470 if __name__ == "__main__":
471     main(sys.argv)