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