· 8 years ago · Jan 07, 2018, 01:34 PM
1--
2-- parseCTags.lua
3-- takes a ctags file and parse its contents to a respective SciTE .properties and .api file
4-- License: BSD3Clause / Author: Thorsten Kani / eMail:Marcedo@habMalNeFrage.de
5-- version: 0.9: Commandline help / progress Indicator
6--
7-- Usage: lua parseCTags.lua ctagsfile.tags [true]
8--
9local DEBUG=0 --1: Trace Mode 2: Verbose Mode
10
11cTagAPI={} -- projectAPI functions(param)
12local cTagNames=""
13local cTagFunctions=""
14local cTagClass=""
15local cTagModules =""
16local cTagENUMs=""
17local cTagOthers=""
18local cTagAllTogether="{"
19local projectFilePath, cTagsFileName, odo
20local fs=io
21
22--
23-- Deal with different Path Separators o linux/win
24--
25local function dirSep()
26 return("\\")
27end
28
29--
30-- returns if a given fileNamePath exists
31--
32local function file_exists(name)
33 local f=fs.open(name,"r")
34 if f~=nil then fs.close(f) return true else return false end
35end
36
37-- read args
38local cTagsFilePath =arg[1]
39local smallerFile =arg[2]
40local projectName =arg[3]
41
42print("> ["..arg[0].."] [Thorsten Kani / Dezember 2017 / eMail:Marcedo@HabMalNeFrage.de]")
43
44if not arg[1] then
45 print ("> ["..arg[0].."] [File] (cTags Filename) [True] (strip Datastructures)")
46 odo=false
47else
48 odo=true
49 if smallerFile=="1" then smallerFile=true end
50
51 -- Think that the file is local when theres no filepath given.
52 if cTagsFilePath:match(dirSep())==nil then
53 cTagsFileName=cTagsFilePath
54 cTagsFilePath="."..dirSep()..cTagsFileName
55 end
56
57 print ("> cTagsFilePath: "..tostring(cTagsFilePath).." | smallerFile: "..tostring(smallerFile).." | projectName: "..tostring(projectName))
58 projectFilePath, cTagsFileName =cTagsFilePath:match("(.*[%"..dirSep().."]+)%/?(.*)$")
59 if not projectName then projectName=cTagsFileName end
60end
61
62--~~~~~~~~~~~~~~~~~~~~~~~~~~~~
63--
64-- appendCTags(apiNames,projectFilePath,projectName)
65-- Parse a ctag File, write filtered tagNames to predefined Vars.
66-- Takes: apiNames: table, FullyQualified projectFilePath,cTagsFileName, optional projectName
67-- Returns: uniqued tagNames written to apiNames
68--
69-- Optimized lua version. Gives reasonable Speed even with bigger cTags Files.
70--
71--~~~~~~~~~~~~~~~~~~~~~~~~~~~~
72
73function appendCTags(apiNames,projectFilePath,cTagsFileName,projectName)
74 local cTagsFilePath=projectFilePath..cTagsFileName
75 local cTagsAPIPath=projectFilePath..cTagsFileName..".api"
76 local cTagItems=""
77 -- catches not otherwise matched Stuff for Highlitghtning. Turn on for testing.
78 local doFullSync="0"
79
80
81 if file_exists(cTagsFilePath) then
82 if DEBUG>=1 then print("ac>appendCtags" ,cTagsFilePath,projectName) end
83 io.stdout:write("> parse: "..cTagsFilePath .." ")
84 local lastEntry="" -- simple DupeCheck
85 local apiFile= io.open(cTagsAPIPath,"w") -- projects APICalltips file
86
87 cTagsFile=io.input(cTagsFilePath)
88 fileSize=cTagsFile:seek("end")
89 cTagsFile:seek("set")
90
91 -- a poorMans exuberAnt cTag Tokenizer :)) --
92 -- Gibt den LemmingAmeisen was sinnvolles zu tun(tm) --
93
94 for entry in cTagsFile:lines() do
95
96 filePos=cTagsFile:seek()
97 percent_before= percent
98 percent=math.floor(filePos*100/fileSize)
99 if percent~=percent_before then
100 io.stdout:write(string.format("%02d",percent).."% ")
101 io.stdout:write('\b'..'\b'..'\b'..'\b')
102 end
103
104 local isFunction=false isClass=false isConst=false isModule=false isENUM=false isOther=false
105 local skipper=false
106 local name =""
107 local params="" -- match function parameters for Calltips
108 -- Mark Constants and Vars (matches "[tab]v)
109 local tmp = entry:match("%\"\t[v]")
110 if not smallerFile and (tmp=="\"\tv") then
111 name= entry:match("([%w_]+)") or ""
112 isConst=true
113 skipper=true
114 end
115 -- Mark Classes & Namespaces (matches "[tab]c/n)
116 if not skipper then
117 local tmp = entry:match("%\"\t[cn]")
118 if tmp=="\"\tc" or tmp=="\"\tn" then
119 name= entry:match("([%w_]+)") or ""
120 isClass=true
121 skipper=true
122 end
123 end
124 -- Mark Functions
125 name= entry:match("(~?[%w_]+)") or ""
126 patType="%/^([%s%w_:~]+ ?)" -- INTPTR
127 patClass="([%w_]+).*" -- SciteWin (::)
128 patFunc="(%(.*%))" -- AbbrevDlg(...)
129 strTyp, strClass, strFunc= entry:match(patType..patClass..patFunc..".*")
130 if strFunc then params=params..strFunc end
131 if strClass and string.len(strClass)==1 then strTyp=strTyp..strClass strClass="" end
132 if strClass then params=params..strClass end
133 if strTyp then strTyp=string.gsub(strTyp,name,"") params=params..strTyp end
134 if string.len(params)>0 then skipper=true isFunction=true end
135 -- Mark ENUMS, STRUCTs, typedefs and unions (matches "[tab]g/s/t/u/e)
136 if not smallerFile and not skipper then
137 if entry:match("%\"\t[geust]") then
138 name= entry:match("([%w_]+)") or ""
139 isENUM=true
140 skipper=true
141 end
142 end
143 -- Mark Modules / defines wo params (matches "[tab]m "[tab]d )
144 if not skipper and entry:match("%\"\tm")=="\"\tm" or entry:match("%\"\td")=="\"\td" then
145 strCls, name= entry:match("^([%w_]+)[%.]?([%w_]+).*")
146 if name and string.len(name)==1 then name=strCls..name end
147 isModule=true
148 skipper=true
149 end
150 -- Handle Tag entries that were not tokenized before.
151 -- This should normally stay empty but can be handy for new languages.
152 local cTagOther=""
153 if not smallerFile and not skipper and name and name..params~=lastEntry and doFullSync=="1" then
154 if string.len(name)>1 then
155 cTagOther= entry:match("(.*%s)")
156 if DEBUG==1 then print("other: "..entry) end
157 isOther=true;
158 end
159 end
160 -- publish collected Data. (Dupe checked) Prefer the className over the functionName
161 if name and name..params~=lastEntry and not isfunction then
162 ---- Highlitening use String concatination, because its faster for onSave ( theres no dupe checking.)
163 if DEBUG==2 then print (name,"isFunction",isFunction,"isConst:",isConst,"isModule:",isModule,"isClass:",isClass,"isENUM:",isENUM) end
164 if isFunction then cTagFunctions=cTagFunctions.." "..name end
165 if isConst then cTagNames=cTagNames.." "..name end
166 if isModule then cTagModules=cTagModules.." "..name end
167 if isClass then cTagClass=cTagClass.." "..name end
168 if isENUM then cTagENUMs=cTagENUMs.." "..name end
169 if isOther then cTagOthers=cTagOthers.." "..cTagOther end
170 if smallerFile==true then
171 -- if isFunction then cTagItems=cTagItems..","..name.."=true" end -- gets concatenated to table cTagAllTogether
172 else
173 -- if skipper then cTagItems=cTagItems..name.."=true," end
174 end
175 lastname=name
176 -- publish Function Descriptors to Project APIFile.(calltips)
177 lastEntry=name..params
178 if isFunction and string.len(params)>2 then -- Optionally Filter internals and COM Objects
179 if smallerFile and (lastEntry:match("^(_)") or lastEntry:match("_Proxy") or lastEntry:match("_Stub") or lastEntry:match("Vtbl") ) then
180 entry=""
181 else
182 apiFile:write(lastEntry.."\n")
183 end
184 end -- faster then using a full bulkWrite?!
185 end
186 end
187 ---- AutoComplete List entries
188 cTagAllTogether=cTagAllTogether..cTagItems.."}"
189 cTagAPI=cTagAllTogether
190 io.close(apiFile)
191 writeProps(projectName, projectFilePath) --> Let a Helper apply the generated Data.
192 cTagsUpdate="0"
193 end
194
195 -- cTagsUpdate=0 so already done. Using the cached Version
196 return cTagAPI
197end
198
199--~~~~~~~~~~~~~~~~~~~~~~~~~~~~
200--
201-- writeProps(projectName, projectFilePath)
202-- publish cTag extrapolated Api Data -
203-- reads above cTag.* vars
204-- write them to SciTEs properties
205-- probably should return something useful.
206--
207--~~~~~~~~~~~~~~~~~~~~~~~~~~~~
208function writeProps(projectName, projectFilePath)
209
210-- write what we got until here.
211 propFile=io.open(projectFilePath..cTagsFileName..".api.properties","w")
212 propFile= io.output(propFile)
213 io.output(propFile)
214 io.write(projectName..".cTagOthers="..cTagOthers.."\n")
215 io.write(projectName..".cTagENUMs="..cTagENUMs.."\n")
216 io.write(projectName..".cTagNames="..cTagNames.."\n")
217 io.write(projectName..".cTagFunctions="..cTagFunctions.."\n")
218 io.write(projectName..".cTagModules="..cTagModules.."\n")
219 io.write(projectName..".cTagClasses="..cTagClass.."\n")
220-- io.write(projectName..".cTagAllTogether="..cTagAllTogether.."\n") --: Table formatted
221 io.close(propFile)
222
223-- Show some stats
224 print("")
225 print("> cTagENUMs: ("..string.len(cTagENUMs).." bytes)" )
226 print("> cTagNames: ("..string.len(cTagNames).." bytes)" )
227 print("> cTagFunctions: ("..string.len(cTagFunctions).." bytes)" )
228 print("> cTagModules: ("..string.len(cTagModules).." bytes)" )
229 print("> cTagClass: ("..string.len(cTagClass).." bytes)" )
230 print("> cTagOthers: ("..string.len(cTagOthers).." bytes)" )
231end
232
233--~~~~~~~~~~~~~~~~~~~~~~~~~~~~
234--DeDupeAPI()
235--Removes Dupes by storing entries as TableKeys
236--
237--~~~~~~~~~~~~~~~~~~~~~~~~~~~~
238
239function DeDupeAPI(APIFilePath)
240nameTable={}
241outline=""
242print("> deDupeing.."..APIFilePath)
243
244 for entry in io.lines(APIFilePath) do
245 name =entry:match("^([%w_]+)")
246 params=entry:match("[%w_]+(%(.*%))")
247 retval=entry:match("%)(.*)")
248 if not params then params="()" end
249 if not retval then retval="()" end
250
251 if name then nameTable[name..params]=retval end
252 end
253
254 -- Store the Result
255 ResultFile=io.open(APIFilePath,"w")
256 ResultFile= io.output(APIFilePath)
257 io.output(APIFilePath)
258 for key,val in pairs(nameTable) do
259 io.write(key..val.."\n")
260 end
261
262 io.close(ResultFile)
263 -- os.remove(APIFilePath)
264 -- os.rename("ResultFile",APIFilePath)
265
266end
267--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
268
269if odo then
270 APIFilePath=projectFilePath..cTagsFileName..".api"
271 finFileNamePath=os.getenv("tmp")..dirSep().."project.ctags.fin"
272 lockFileNamePath=os.getenv("tmp")..dirSep().."project.ctags.lock"
273
274 -- create a lock file
275 os.remove(finFileNamePath)
276 lockFile=io.open(lockFileNamePath,"w")
277 lockFile= io.output(lockFileNamePath)
278 io.output(lockFile)
279 io.write(tostring(os.date))
280 io.close(lockFile)
281
282 -- do!
283 appendCTags({},projectFilePath,cTagsFileName,projectName)
284 if file_exists(APIFilePath) then
285 DeDupeAPI(APIFilePath)
286 print("> FIN!")
287 else
288 print("> Error: "..APIFilePath.." was not found")
289 end
290
291 -- create the fin file
292 os.remove(lockFileNamePath)
293 finFile=io.open(finFileNamePath,"w")
294 finFile= io.output(finFileNamePath)
295 io.output(finFile)
296 io.write(tostring(os.date))
297 io.close(finFile)
298end