]> git.lyx.org Git - lyx.git/blob - development/tools/gen_lfuns.py
Split TextMetrics::setRowHeight in three parts
[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
17 def error(message):
18     sys.stderr.write(message + '\n')
19     sys.exit(1)
20
21 def usage(prog_name):
22     return "Usage: %s <path/to/LyXAction.cpp> [<where/to/save/LFUNs.lyx>]" % prog_name
23
24 DOXYGEN_START = "/*!"
25 DOXYGEN_END = "},"
26
27 LYX_NEWLINE = "\n\\begin_inset Newline newline\n\\end_inset\n\n"
28 LYX_BACKSLASH = "\n\\backslash\n"
29
30 HTMLONLY_START = "\\htmlonly"
31 HTMLONLY_END = "\\endhtmlonly"
32 LFUN_NAME_ID = "\\var lyx::FuncCode lyx::"
33 LFUN_ACTION_ID = "\\li Action: "
34 LFUN_NOTION_ID = "\\li Notion: "
35 LFUN_SYNTAX_ID = "\\li Syntax: "
36 LFUN_PARAMS_ID = "\\li Params: "
37 LFUN_SAMPLE_ID = "\\li Sample: "
38 LFUN_ORIGIN_ID = "\\li Origin: "
39 LFUN_ENDVAR = "\\endvar"
40
41 ID_DICT = dict(name=LFUN_NAME_ID, action=LFUN_ACTION_ID, notion=LFUN_NOTION_ID, 
42                 syntax=LFUN_SYNTAX_ID, params=LFUN_PARAMS_ID, sample=LFUN_SAMPLE_ID, origin=LFUN_ORIGIN_ID)
43
44 LFUNS_HEADER = """# gen_lfuns.py generated this file. For more info see http://www.lyx.org/
45 \\lyxformat 506
46 \\begin_document
47 \\begin_header
48 \\save_transient_properties true
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 """
148
149 LFUNS_INTRO ="""\\begin_layout Section*
150 About this manual
151 \\end_layout
152
153 \\begin_layout Standard
154 This manual documents the 
155 \\begin_inset Quotes eld
156 \\end_inset
157
158 LyX Functions
159 \\begin_inset Quotes erd
160 \\end_inset
161
162  (abbreviated LFUNs).
163  These are commands that are used to make \\SpecialChar LyX
164  perform specific actions.
165  \\SpecialChar LyX
166  itself uses these functions internally, and every internal action is
167  bound to an LFUN.
168 \\end_layout
169
170 \\begin_layout Standard
171 LFUNs are also used in the files that define keyboard shortcuts, menu or
172  toolbar items.
173  So if you want to change\\SpecialChar breakableslash
174 customize the user interface, you need to deal
175  with LFUNs.
176  Furthermore, external programs can use LFUNs to communicate with and 
177 \\begin_inset Quotes eld
178 \\end_inset
179
180 remote-control
181 \\begin_inset Quotes erd
182 \\end_inset
183
184  \\SpecialChar LyX
185  .
186  Finally, you can also issue LFUNs directly via the so called mini-buffer
187  which can be opened via 
188 \\begin_inset Info
189 type  "shortcuts"
190 arg   "command-execute"
191 \\end_inset
192
193 .
194 \\end_layout
195
196 \\begin_layout Standard
197 In the following, all LFUNs are listed, categorized by function.
198 \\end_layout
199
200 """
201
202
203 LFUNS_FOOTER = """\\end_body
204 \\end_document
205 """
206
207 def parse_lfun(str):
208     """Takes a comment block (str) and parses it for fields describing the LFUN. Returns a dict containing the fields."""
209     
210     lfun = dict(action="", notion="", syntax="", params="", sample="", origin="")
211     field = ""
212     lines = str.splitlines()
213     # strip leading whitespace and * from the lines of the comment to get 
214     # rid of unimportant characters
215     for i in range(0, len(lines)):
216         lines[i] = lines[i].strip(" *")
217     
218     for i in range(0, len(lines) - 1):
219         # work out what field is being read if none of these is found, the line will be added
220         #     to the last field edited
221         # since the field identifier is not included skip it out if it's found, otherwise skip
222         #     nothing as an existing field is being added to
223         # if a field id is found, then its the first line of the field so set the pre_space to ""
224         #     so that the first line isn't prespaced
225         if lines[i].startswith(LFUN_ACTION_ID):
226             field = "action"
227             pre_space = ""
228             skip = len(ID_DICT[field])
229         elif lines[i].startswith(LFUN_NOTION_ID):
230             field = "notion"
231             pre_space = ""
232             skip = len(ID_DICT[field])
233         elif lines[i].startswith(LFUN_SYNTAX_ID):
234             field = "syntax"
235             pre_space = ""
236             skip = len(ID_DICT[field])
237         elif lines[i].startswith(LFUN_PARAMS_ID):
238             field = "params"
239             pre_space = ""
240             skip = len(ID_DICT[field])
241         elif lines[i].startswith(LFUN_SAMPLE_ID):
242             field = "sample"
243             pre_space = ""
244             skip = len(ID_DICT[field])
245         elif lines[i].startswith(LFUN_ORIGIN_ID):
246             field = "origin"
247             pre_space = ""
248             skip = len(ID_DICT[field])
249         elif lines[i].startswith(LFUN_ENDVAR):
250             break
251         else:
252             skip = 0
253             # if a manual line break was found last line, don't prespace this line
254             if i > 1 and lines[i-1].endswith("\\n"):
255                 pre_space = ""
256             else:
257                 pre_space = " "
258         
259         # add the line to the field, processing it for \ characters and \n
260         # which, if occurring at the end of a line, must become a LYX_NEWLINE
261         line = lines[i][skip:]
262         
263         # deal with \htmlonly
264         # TODO: convert chars found in htmlonly to unicode
265         start = line.find(HTMLONLY_START)
266         if start > 0:
267             # if removing the htmlonly element leaves a double space, go back one to remove it
268             if line[start-1] == " ":
269                 start = start - 1
270             end = line.find(HTMLONLY_END)
271             if end > start:
272                 end = line.find(HTMLONLY_END) + len(HTMLONLY_END)
273                 line = line[:start] + line[end:]
274             #else:
275             # TODO: if HTMLONLY_END is not found, look on the next line
276             # TODO: in the current LyXAction.cpp there are no htmlonly fields which go over a line break
277         
278         # deal with \ but leave \n if at the end of the line
279         slash_idx = line.find("\\")
280         while slash_idx >= 0:
281             if slash_idx < len(line)-2 \
282             or slash_idx == len(line)-1:
283                 # true when the \ is not the last or second last char
284                 #      or when the slash is the last char of the line
285                 
286                 # slash must be interpreted literaly so swap it for a LYX_BACKSLASH
287                 line = line[:slash_idx] + LYX_BACKSLASH + line[slash_idx+1:]
288                 # skip the index ahead beyond the added text
289                 slash_idx = slash_idx + len(LYX_BACKSLASH)
290             elif line[slash_idx+1] != "n": # only evaluated if the line ends "\x" where 'x' != 'n'
291                 line = line[:slash_idx] + LYX_BACKSLASH + line[slash_idx+1:]
292                 # skip the index ahead beyond the added text
293                 slash_idx = slash_idx + len(LYX_BACKSLASH) 
294             # look for the next \
295             slash_idx = line.find("\\", slash_idx+1)
296             
297         # \n at the end of lines will not be processed by the above while loop
298         # so sort those out now
299         # sometime lines end " \n" so chop the space if its there
300         if line.endswith(" \\n"):
301             line = line[:len(line)-3] + LYX_NEWLINE
302         elif line.endswith("\\n"):
303             line = line[:len(line)-2] + LYX_NEWLINE
304         
305         # any references to other LFUNs need the # removing
306         # TODO: actually insert a cross-reference here
307         line = line.replace("#LFUN", "LFUN")
308         
309         # handle the few #lyx:: cases
310         line = line.replace("#lyx::", "lyx::")
311
312         # the first line might not have a field in it in which
313         # case the variable field won't have a value, so check
314         # to avoid an error
315         if field != "":
316             lfun[field] = lfun[field] + pre_space + line
317         
318         # TODO: sort out chopping lines of more that 80 chars in length
319         
320     return lfun
321
322 def write_fields(file, lfun):
323     """Writes the LFUN contained in the dict lfun to the file. Does not write a the file header or footer"""
324     # add lfun to LFUNs.lyx
325     file.write("\\begin_layout Subsection*\n")
326     file.write(lfun["name"] + "\n")
327     file.write("\\end_layout\n")
328     file.write("\n")
329     if lfun["action"] != "":
330         file.write("\\begin_layout Description\n")
331         file.write("Action " + lfun["action"] + "\n")
332         file.write("\\end_layout\n")
333         file.write("\n")
334     if lfun["notion"] != "":
335         file.write("\\begin_layout Description\n")
336         file.write("Notion " + lfun["notion"] + "\n")
337         file.write("\\end_layout\n")
338         file.write("\n")
339     if lfun["syntax"] != "":
340         file.write("\\begin_layout Description\n")
341         file.write("Syntax " + lfun["syntax"] + "\n")
342         file.write("\\end_layout\n")
343         file.write("\n")
344     if lfun["params"] != "":
345         file.write("\\begin_layout Description\n")
346         file.write("Params " + lfun["params"] + "\n")
347         file.write("\\end_layout\n")
348         file.write("\n")
349     if lfun["sample"] != "":
350         file.write("\\begin_layout Description\n")
351         file.write("Sample " + lfun["sample"] + "\n")
352         file.write("\\end_layout\n")
353         file.write("\n")
354     if lfun["origin"] != "":
355         file.write("\\begin_layout Description\n")
356         file.write("Origin " + lfun["origin"] + "\n")
357         file.write("\\end_layout\n")
358         file.write("\n")
359
360 def write_sections(file,lfuns):
361     """Write sections of LFUNs"""
362     sections = ["Layout", "Edit", "Math", "Buffer", "System", "Hidden"]
363     section_headings = {
364         "Layout":  "Layout Functions (Font, Layout and Textclass related)",
365         "Edit":  "Editing Functions (Cursor and Mouse Movement, Copy/Paste etc.)",
366         "Math":  "Math Editor Functions",
367         "Buffer":  "Buffer Fuctions (File and Window related)",
368         "System":  "System Functions (Preferences, LyX Server etc.)",
369         "Hidden":  "Hidden Functions (not listed for configuration)"
370         }
371         # write the lfuns to the file
372     for val in sections:
373         file.write("\\begin_layout Section\n")
374         file.write(section_headings[val] + "\n")
375         file.write("\\end_layout\n")
376         file.write("\n")
377         for lf in lfuns:
378             if lf["type"] == val:
379                 write_fields(file, lf)
380     
381 def main(argv):
382     # parse command line arguments
383     script_path, script_name = os.path.split(argv[0])
384     if len(argv) < 2:
385         error(usage(script_name))
386     # input file
387     lyxaction_path = argv[1]
388     if not os.path.isfile(lyxaction_path):
389         error(script_name + ": %s is not a valid path" % lyxaction_path)
390
391     # output file
392     if len(argv) == 3:
393         lfuns_path = argv[2]
394         if os.path.isdir(lfuns_path):
395             lfuns_path = lfuns_path + "LFUNs.lyx"
396         elif os.path.exists(lfuns_path):
397             error(script_name + ": %s already exists, delete it and rerun the script" % lfuns_path)
398         lfuns_file = open(lfuns_path, 'wb')
399     else:
400         lfuns_file = sys.stdout
401
402     sys.stderr.write(script_name + ": Start processing " + argv[1] + '\n')
403     # Read the input file and write the output file
404     lyxaction_file = open(lyxaction_path, 'rb')
405
406     lyxaction_text = lyxaction_file.read()
407
408     lfuns_file.write(LFUNS_HEADER)
409     
410     # An introductory section
411     lfuns_file.write(LFUNS_INTRO)
412
413     # seek to the important bit of LyXAction.cpp
414     try:
415         start = lyxaction_text.index("ev_item const items[] = {")
416     except ValueError:
417         lyxaction_file.close()
418         lfuns_file.close()
419         error(script_name + ": LFUNs not found in " + lyxaction_file)
420
421     done = count = 0
422
423     lfun_list_unsorted = []
424
425     while done == 0:
426         # look for a doxygen comment
427         start = lyxaction_text.find(DOXYGEN_START, start)
428         end = lyxaction_text.find(DOXYGEN_END, start) + len(DOXYGEN_END)
429         name = ""
430         atype = ""
431         snippet = lyxaction_text[start:end]
432         defline = snippet.replace("\n", "")
433         match = re.match(r'.*\s*\{\s*(.+),\s*"(.*)",\s*([\w\|\s]+),\s*(\w+)\s*\},.*$', defline)
434         if match:
435             name = match.group(2)
436             atype = match.group(4)
437         # parse the lfun if it is found
438         if start > 0:
439             if name:
440                 count = count + 1
441                 lfun = parse_lfun(snippet)
442                 lfun["name"] = name
443                 lfun["type"] = atype
444                 # save the lfun (we sort it before writing)
445                 lfun_list_unsorted.append(lfun)
446             # get the next one
447             start = end
448         else:
449             # if no more lfuns are found, EOF reached
450             done = 1
451
452     lfun_list = sorted(lfun_list_unsorted, key=lambda k: k['name'])
453     
454     # write the lfuns to the file
455     write_sections(lfuns_file, lfun_list)
456
457     sys.stderr.write(script_name + ": Created documentation for " + str(count) + " LFUNs\n")
458
459     # write the last part of LFUNs.lyx
460     lfuns_file.write(LFUNS_FOOTER)
461     
462     lyxaction_file.close()
463     lfuns_file.close()
464     
465     sys.stderr.write(script_name + ": Finished\n")
466     
467 if __name__ == "__main__":
468     main(sys.argv)