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