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