]> git.lyx.org Git - lyx.git/blob - development/tools/gen_lfuns.py
Allow output to pipe.
[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
16 import os.path
17 from datetime import date
18
19 def error(message):
20     sys.stderr.write(message + '\n')
21     sys.exit(1)
22
23 def usage(prog_name):
24     return "Usage: %s <path/to/LyXAction.cpp> [<where/to/save/LFUNs.lyx>]" % prog_name
25
26 DOXYGEN_START = "/*!"
27 DOXYGEN_END = "*/"
28
29 LYX_NEWLINE = "\n\\begin_inset Newline newline\n\\end_inset\n\n"
30 LYX_BACKSLASH = "\n\\backslash\n"
31
32 HTMLONLY_START = "\\htmlonly"
33 HTMLONLY_END = "\\endhtmlonly"
34 LFUN_NAME_ID = "\\var lyx::FuncCode lyx::"
35 LFUN_ACTION_ID = "\\li Action: "
36 LFUN_NOTION_ID = "\\li Notion: "
37 LFUN_SYNTAX_ID = "\\li Syntax: "
38 LFUN_PARAMS_ID = "\\li Params: "
39 LFUN_SAMPLE_ID = "\\li Sample: "
40 LFUN_ORIGIN_ID = "\\li Origin: "
41 LFUN_ENDVAR = "\\endvar"
42
43 ID_DICT = dict(name=LFUN_NAME_ID, action=LFUN_ACTION_ID, notion=LFUN_NOTION_ID, 
44                 syntax=LFUN_SYNTAX_ID, params=LFUN_PARAMS_ID, sample=LFUN_SAMPLE_ID, origin=LFUN_ORIGIN_ID)
45
46 LFUNS_HEADER = """# gen_lfuns.py generated this file. For more info see http://www.lyx.org/
47 \\lyxformat 345
48 \\begin_document
49 \\begin_header
50 \\textclass amsart
51 \\use_default_options false
52 \\begin_modules
53 theorems-ams
54 \\end_modules
55 \\language english
56 \\inputencoding auto
57 \\font_roman default
58 \\font_sans default
59 \\font_typewriter default
60 \\font_default_family default
61 \\font_sc false
62 \\font_osf false
63 \\font_sf_scale 100
64 \\font_tt_scale 100
65
66 \\graphics default
67 \\paperfontsize default
68 \\use_hyperref false
69 \\papersize default
70 \\use_geometry true
71 \\use_amsmath 1
72 \\use_esint 1
73 \\cite_engine basic
74 \\use_bibtopic false
75 \\paperorientation portrait
76 \\leftmargin 2.5cm
77 \\topmargin 2cm
78 \\rightmargin 3cm
79 \\bottommargin 1cm
80 \\secnumdepth 3
81 \\tocdepth 3
82 \\paragraph_separation indent
83 \\defskip medskip
84 \\quotes_language english
85 \\papercolumns 1
86 \\papersides 1
87 \\paperpagestyle default
88 \\tracking_changes false
89 \\output_changes false
90 \\author "" 
91 \\author "" 
92 \\end_header
93
94 \\begin_body
95
96 \\begin_layout Section*""" + "\nLFUNs documentation automatically generated " + str(date.today()) + """
97 \\end_layout
98
99 \\begin_layout Standard
100 \\begin_inset ERT
101 status collapsed
102
103 \\begin_layout Plain Layout
104
105
106 \\backslash
107 thispagestyle{empty}
108 \\end_layout
109
110 \\end_inset
111
112
113 \\begin_inset VSpace 1cm
114 \\end_inset
115
116
117 \\end_layout
118 """
119 LFUNS_FOOTER = """\\end_body
120 \\end_document
121 """
122
123 def parse_lfun(str):
124     """Takes a comment block (str) and parses it for fields describing the LFUN. Returns a dict containing the fields."""
125     
126     lfun = dict(name="", action="", notion="", syntax="", params="", sample="", origin="")
127     field = ""
128     lines = str.splitlines()
129     # strip leading whitespace and * from the lines of the comment to get 
130     # rid of unimportant characters
131     for i in range(0, len(lines)):
132         lines[i] = lines[i].strip(" *")
133     
134     for i in range(0, len(lines) - 1):
135         # work out what field is being read if none of these is found, the line will be added
136         #     to the last field edited
137         # since the field identifier is not included skip it out if it's found, otherwise skip
138         #     nothing as an existing field is being added to
139         # if a field id is found, then its the first line of the field so set the pre_space to ""
140         #     so that the first line isn't prespaced
141         if lines[i].startswith(LFUN_NAME_ID):
142             field = "name"
143             pre_space = ""
144             skip = len(ID_DICT[field])
145         elif lines[i].startswith(LFUN_ACTION_ID):
146             field = "action"
147             pre_space = ""
148             skip = len(ID_DICT[field])
149         elif lines[i].startswith(LFUN_NOTION_ID):
150             field = "notion"
151             pre_space = ""
152             skip = len(ID_DICT[field])
153         elif lines[i].startswith(LFUN_SYNTAX_ID):
154             field = "syntax"
155             pre_space = ""
156             skip = len(ID_DICT[field])
157         elif lines[i].startswith(LFUN_PARAMS_ID):
158             field = "params"
159             pre_space = ""
160             skip = len(ID_DICT[field])
161         elif lines[i].startswith(LFUN_SAMPLE_ID):
162             field = "sample"
163             pre_space = ""
164             skip = len(ID_DICT[field])
165         elif lines[i].startswith(LFUN_ORIGIN_ID):
166             field = "origin"
167             pre_space = ""
168             skip = len(ID_DICT[field])
169         elif lines[i].startswith(LFUN_ENDVAR):
170             break
171         else:
172             skip = 0
173             # if a manual line break was found last line, don't prespace this line
174             if i > 1 and lines[i-1].endswith("\\n"):
175                 pre_space = ""
176             else:
177                 pre_space = " "
178         
179         # add the line to the field, processing it for \ characters and \n
180         # which, if occurring at the end of a line, must become a LYX_NEWLINE
181         line = lines[i][skip:]
182         
183         # deal with \htmlonly
184         # TODO: convert chars found in htmlonly to unicode
185         start = line.find(HTMLONLY_START)
186         if start > 0:
187             # if removing the htmlonly element leaves a double space, go back one to remove it
188             if line[start-1] == " ":
189                 start = start - 1
190             end = line.find(HTMLONLY_END)
191             if end > start:
192                 end = line.find(HTMLONLY_END) + len(HTMLONLY_END)
193                 line = line[:start] + line[end:]
194             #else:
195             # TODO: if HTMLONLY_END is not found, look on the next line
196             # TODO: in the current LyXAction.cpp there are no htmlonly fields which go over a line break
197         
198         # deal with \ but leave \n if at the end of the line
199         slash_idx = line.find("\\")
200         while slash_idx >= 0:
201             if slash_idx < len(line)-2 \
202             or slash_idx == len(line)-1:
203                 # true when the \ is not the last or second last char
204                 #      or when the slash is the last char of the line
205                 
206                 # slash must be interpreted literaly so swap it for a LYX_BACKSLASH
207                 line = line[:slash_idx] + LYX_BACKSLASH + line[slash_idx+1:]
208                 # skip the index ahead beyond the added text
209                 slash_idx = slash_idx + len(LYX_BACKSLASH)
210             elif line[slash_idx+1] != "n": # only evaluated if the line ends "\x" where 'x' != 'n'
211                 line = line[:slash_idx] + LYX_BACKSLASH + line[slash_idx+1:]
212                 # skip the index ahead beyond the added text
213                 slash_idx = slash_idx + len(LYX_BACKSLASH) 
214             # look for the next \
215             slash_idx = line.find("\\", slash_idx+1)
216             
217         # \n at the end of lines will not be processed by the above while loop
218         # so sort those out now
219         # sometime lines end " \n" so chop the space if its there
220         if line.endswith(" \\n"):
221             line = line[:len(line)-3] + LYX_NEWLINE
222         elif line.endswith("\\n"):
223             line = line[:len(line)-2] + LYX_NEWLINE
224         
225         # any references to other LFUNs need the # removing
226         # TODO: actually insert a cross-reference here
227         line = line.replace("#LFUN", "LFUN")
228         
229         # handle the few #lyx:: cases
230         line = line.replace("#lyx::", "lyx::")
231
232         # the first line might not have a field in it in which
233         # case the variable field won't have a value, so check
234         # to avoid an error
235         if field != "":
236             lfun[field] = lfun[field] + pre_space + line
237         
238         # TODO: sort out chopping lines of more that 80 chars in length
239         
240     return lfun
241
242 def write_fields(file, lfun):
243     """Writes the LFUN contained in the dict lfun to the file. Does not write a the file header or footer"""
244     # add lfun to LFUNs.lyx
245     file.write("\\begin_layout Subsection*\n")
246     file.write(lfun["name"] + "\n")
247     file.write("\\end_layout\n")
248     #file.write("\n")
249     if lfun["action"] != "":
250         file.write("\\begin_layout Description\n")
251         file.write("Action " + lfun["action"] + "\n")
252         #file.write("\n")
253         file.write("\\end_layout\n")
254         #file.write("\n")
255     if lfun["notion"] != "":
256         file.write("\\begin_layout Description\n")
257         file.write("Notion " + lfun["notion"] + "\n")
258         file.write("\\end_layout\n")
259         #file.write("\n")
260     if lfun["syntax"] != "":
261         file.write("\\begin_layout Description\n")
262         file.write("Syntax " + lfun["syntax"] + "\n")
263         file.write("\\end_layout\n")
264         #file.write("\n")
265     if lfun["params"] != "":
266         file.write("\\begin_layout Description\n")
267         file.write("Params " + lfun["params"] + "\n")
268         file.write("\\end_layout\n")
269         #file.write("\n")
270     if lfun["sample"] != "":
271         file.write("\\begin_layout Description\n")
272         file.write("Sample " + lfun["sample"] + "\n")
273         file.write("\\end_layout\n")
274         #file.write("\n")
275     if lfun["origin"] != "":
276         file.write("\\begin_layout Description\n")
277         file.write("Origin " + lfun["origin"] + "\n")
278         file.write("\\end_layout\n")
279         #file.write("\n")
280     file.write("\n")        
281     
282 def main(argv):
283     # parse command line arguments   
284     script_path, script_name = os.path.split(argv[0])
285     if len(argv) < 2:
286         error(usage(script_name))
287     # input file
288     lyxaction_path = argv[1]
289     if not os.path.exists(lyxaction_path):
290         error(script_name + ": %s is not a valid path" % lyxaction_path, usage(argv[0]))
291
292     # output file
293     if len(argv) == 3:
294         lfuns_path = argv[2]
295         if os.path.isdir(lfuns_path):
296             lfuns_path = lfuns_path + "LFUNs.lyx"
297         elif os.path.exists(lfuns_path):
298             error(script_name + ": %s already exists, delete it and rerun the script" % lfuns_path)
299         lfuns_file = open(lfuns_path, 'wb')
300     else:
301         lfuns_file = sys.stdout
302
303     sys.stderr.write(script_name + ": Start processing " + argv[1] + '\n')
304     # Read the input file and write the output file
305     lyxaction_file = open(lyxaction_path, 'rb')
306         
307     lyxaction_text = lyxaction_file.read()
308         
309     lfuns_file.write(LFUNS_HEADER)
310         
311         # seek to the important bit of LyXAction.cpp
312     try:
313         start = lyxaction_text.index("ev_item const items[] = {")
314     except ValueError:
315         lyxaction_file.close()
316         lfuns_file.close()
317         error(script_name + ": LFUNs not found in " + lyxaction_file)
318
319     done = count = 0
320
321     while done == 0:
322         # look for a doxygen comment
323         start = lyxaction_text.find(DOXYGEN_START, start)
324         end = lyxaction_text.find(DOXYGEN_END, start) + len(DOXYGEN_END)
325         # parse the lfun if it is found
326         if start > 0:
327             count = count + 1
328             lfun = parse_lfun(lyxaction_text[start:end])
329             # write the lfun to the file
330             write_fields(lfuns_file, lfun)
331             # done adding current lfun to LFUNs.lyx so get the next one
332             start = end
333         else:
334             # if no more lfuns are found, EOF reached
335             done = 1
336             
337     sys.stderr.write(script_name + ": Created documentation for " + str(count) + " LFUNs\n")
338     
339     # write the last part of LFUNs.lyx
340     lfuns_file.write(LFUNS_FOOTER)
341     
342     lyxaction_file.close()
343     lfuns_file.close()
344     
345     sys.stderr.write(script_name + ": Finished\n")
346     
347 if __name__ == "__main__":
348     main(sys.argv)