Fix #7134 - parsing optional cfg
[roojspacker] / roojspacker / DocTag.vala
1
2
3 namespace JSDOC 
4 {
5         public enum DocTagTitle
6         {
7                 NO_VALUE,
8                 PARAM,
9                 PROPERTY,
10                 CFG,
11                 EXAMPLE,
12                 SINGLETON,
13                 AUTHOR,
14                 METHOD,
15                 DESC,
16                 OVERVIEW,
17                 SINCE,
18                 CONSTANT,
19                 VERSION,
20                 DEPRECATED,
21  
22                 SEE,
23                 CLASS,
24                 NAMESPACE,
25                 CONSTRUCTOR,
26                 STATIC,
27  
28                 
29                 INNER,
30                 FIELD,
31                 FUNCTION,
32                 EVENT,
33                 NAME,
34                 RETURN,
35                 THROWS,
36                 REQUIRES,
37                 TYPE,
38                 PRIVATE,
39                 IGNORE,
40                 ARGUMENTS,
41                 EXTENDS,
42                 DEFAULT,
43                 MEMBEROF,
44                 PUBLIC,
45                 SCOPE,
46                 SCOPEALIAS,
47                 
48                 // these are some we have added for creating trees etc..
49                 CHILDREN, // what classes can be added as child in a tree
50                 PARENT,  // restrict what the class can be added to.
51                 ABSTRACT, // is the class abstract
52                 BUILDER_TOP // can the element be used as a top level in the gui builder
53   
54   
55         }
56         
57         errordomain DocTagException {
58                 NO_TITLE,
59                 INVALID_TITLE,
60                 INVALID_NAME,
61                 INVALID_TYPE
62         }
63
64
65         public class DocTag : Object 
66         {
67
68                 public DocTagTitle title = DocTagTitle.NO_VALUE;
69                 public string type = "";  // eg.. boolean / string etc..., may be xxxx|bbbb - eg. optional types
70                 public string name = ""; // eg. "title" << a property name etc...
71                 public bool isOptional = false;
72                 public string defaultValue = "";
73                 public string desc = "";
74                 public Gee.ArrayList<string> optvalues;
75                 public string memberOf = ""; // set by add addMember..
76
77                 public string asString()
78                 {
79                         return "DocTag: title=%s name=%s type=%s  desc=%s".printf(
80                                 this.title.to_string(),
81                                 this.name,
82                                 this.type,
83                                 this.desc
84                         );
85                 }
86         
87                 public Json.Object toJson()
88                 {
89                         var ret = new Json.Object();
90                         ret.set_string_member("title", this.title.to_string());
91                         ret.set_string_member("type", this.type);
92                         ret.set_string_member("name", this.name);
93                         ret.set_string_member("defaultValue", this.defaultValue);
94                         ret.set_string_member("desc", this.desc);
95                         ret.set_string_member("memberOf", this.memberOf);
96                         ret.set_boolean_member("isOptional", this.isOptional);
97                         var ar = new Json.Array();
98                         foreach(var ov in this.optvalues) {
99                                 ar.add_string_element(ov);
100                         }
101                         ret.set_array_member("optvalues", ar);
102                         return ret;
103                 }
104         
105         
106                 public DocTag (string in_src)
107                 {
108                     
109                     GLib.debug("Parsing Tag: %s", in_src);
110                     
111                      
112                     
113                     
114                     this.optvalues = new Gee.ArrayList<string>();
115                     
116                     var src = in_src;
117                         
118             try {
119                 src = this.nibbleTitle(src);
120                 
121                 src = this.nibbleType(src);
122                 
123
124                 // only some tags are allowed to have names.
125                 if (
126                         this.title == DocTagTitle.PARAM ||
127                         this.title == DocTagTitle.PROPERTY || 
128                         this.title == DocTagTitle.CFG) { // @config is deprecated << not really?
129                     src = this.nibbleName(src);
130                 }
131             }
132             catch(DocTagException e) {
133                 GLib.debug("Failed to parse tag: '%s' = error = %s", in_src, e.message);
134                 // only throw if in 'strict'??
135                 //throw e;
136                 return;
137             }
138             
139             // if type == @cfg, and matches (|....|...)
140             
141             src = src.strip();
142             
143             // our code uses (Optional) - but we really want to ignore this.
144             src = /\(Optional\)/.replace(src, src.length, 0,  "").strip();
145             
146  
147             MatchInfo mi = null;
148             
149             
150             
151             if (this.title ==  DocTagTitle.CFG && /^\([^)]+\)/.match_all(src, 0, out mi )) {
152                     
153                                 var ms = mi.fetch(0);
154                                 GLib.debug("Got Opt list: %s", ms);
155                                 
156                                 ms = ms.substring(1,ms.length-2);
157                                 GLib.debug("clan to: %s", ms);
158                                 if (ms.contains("|")) {
159                                         var ar = ms.split("|");
160                                 GLib.debug("split to: %d", ar.length);
161                                         for (var i =0 ; i < ar.length;i++) {
162                             GLib.debug("Add optvalue: %s",ar[i].strip());
163                                                 this.optvalues.add(ar[i].strip());
164                                         }
165                                         src = src.substring(ms.length, src.length - (ms.length+2)).strip();
166                     GLib.debug("SRC NOW: %s",src);
167                 } 
168                 
169             }
170             if (this.title ==  DocTagTitle.CFG &&  /\[required\]/.match(src)) {
171                 this.isOptional = false;
172                 src = /\[required\]/.replace(src, src.length, 0,  "").strip();
173                 }
174             this.desc = src; // whatever is left
175             
176             // example tags need to have whitespace preserved
177             if (this.title != DocTagTitle.EXAMPLE) {
178                         this.desc = this.desc.strip();
179                 }
180             
181
182                 
183         
184
185                 }
186         
187         
188                 /**
189                     Find and shift off the title of a tag.
190                     @param {string} src
191                     @return src
192                  */
193                 private string nibbleTitle (string src) throws DocTagException
194                 {
195                     //GLib.debug("nibbleTitle: %s", src);
196                     MatchInfo mi;
197                      
198                     if(! /^\s*(\S+)\s*(?:\s([\s\S]*))?$/.match_full(src, src.length, 0, 0, out mi) || 
199                             mi.get_match_count() < 2)  {
200                                 throw new DocTagException.NO_TITLE("missing title");
201                                 return src;
202                     }
203                     
204                     // convert the @xxx to a DocTagTitle
205                     // wonder if caching this as a GeeHashmap would be quicker?
206                     
207                     EnumClass enumc = (EnumClass) typeof (DocTagTitle).class_ref ();
208
209                     unowned EnumValue? eval = enumc.get_value_by_name(
210                         //       "JSDOC_DOC_TAG_TITLE_"+  mi.fetch(1).up()
211                                  "JSDOC_DOC_TAG_TITLE_"+  mi.fetch(1).up().replace("-", "_")
212                  );
213                     if (eval == null) {
214                                 throw new DocTagException.INVALID_TITLE("title not supported ??");
215                                 return src;
216                     }
217                     this.title = (DocTagTitle) eval.value;
218                     return mi.get_match_count() > 2 ? mi.fetch(2) : "";
219
220                 }
221                  
222                   /**
223             Find and shift off the type of a tag.
224             @requires frame/String.js
225             @param {string} src
226             @return src
227          */
228         private string nibbleType(string src) 
229         {
230                     MatchInfo mi;
231             if(! /^\s*\{/.match_all(src, 0, out mi)) {
232                    return src;
233             }
234             int start;
235             int stop;
236               
237                         this.balance(src,'{', '}', out start, out stop);
238                         //GLib.debug("nibble type: %s %d, %d", src, start,stop);
239             if (stop == -1) {
240                 throw new DocTagException.INVALID_TYPE("Malformed comment tag ignored. Tag type requires an opening { and a closing }: ") ;
241                 return src;
242             }
243             this.type = src.substring(start+1,stop-1).strip();
244             this.type = this.type.replace(",", "|"); // multiples can be separated by , or |
245             return src.substring(stop+1, -1);
246             
247         }
248          
249          
250          
251         /**
252             Find and shift off the name of a tag.
253             @requires frame/String.js
254             @param {string} src
255             @return src
256          */
257                 private string nibbleName( string in_src) throws DocTagException
258         {
259
260            
261             var src = in_src.strip();
262             //GLib.debug("nibbleName: %s", in_src);
263             
264             // is optional?
265             if (src.get(0) == '[') {
266                         int start, stop;
267                  this.balance(src,'[', ']', out start, out stop);
268                 if (stop == -1) {
269                     throw new  DocTagException.INVALID_NAME("Malformed comment tag ignored. Tag optional name requires an opening [ and a closing ]: ");
270                     return src;
271                 }
272                 this.name = src.substring(start+1, stop).strip();
273                 this.isOptional = true;
274                 
275                 src = src.substring(stop+1);
276                 
277                 // has default value?
278                 var nameAndValue = this.name.split("=");
279                 if (nameAndValue.length > 1) {
280                         var oname = this.name;
281                     this.name = nameAndValue[0].strip();
282
283                     this.defaultValue = oname.substring( nameAndValue[0].length + 1 , nameAndValue[0].length + 1 - oname.length); /// what about
284                 }
285                 GLib.debug("got name %s", this.name);                
286                 return src.substring(stop+1, stop+1-src.length);
287             }
288                         // not encased with [ ]
289
290                     MatchInfo mi;
291
292             if (/^(\S+)(?:\s([\s\S]*))?$/.match_full(src, src.length, 0, 0,  out mi)) {
293                         this.name = mi.fetch(1);
294                         GLib.debug("got name %s", this.name);
295                                 return mi.get_match_count() > 2 ? mi.fetch(2) : "";
296             }
297                 
298
299             return src;
300         }
301          
302          
303         private void balance(string str, char open, char close, out int start, out int stop) {
304             start = 0;
305             stop  =-1;
306             while (str.get(start) != open) {
307                 if (start == str.length) {
308                         return;
309                         }
310                 start++;
311             }
312             
313             stop = start +1;
314             var balance = 1;
315             while (stop < str.length) {
316                 if (str.get(stop) == open) balance++;
317                 if (str.get(stop) == close) balance--;
318                 if (balance == 0) break;
319                 stop++;
320                 if (stop == str.length) {
321                         stop = -1;
322                         return;
323                         }
324             }
325             
326
327                 }
328                 
329                 public Json.Array optvalue_as_json_array()
330                 {
331                         var ret = new Json.Array();
332                         foreach (var str in this.optvalues ) {
333                                 ret.add_string_element(str);
334                         }
335                         return ret;
336                         
337                         
338                 }
339                 public Json.Object toPropertyJSON (Symbol parent)
340                 {
341                         
342                         var add = new Json.Object();
343                         add.set_string_member("name",this.name);
344                         add.set_string_member("type",this.type);
345                         add.set_string_member("desc",this.desc);
346                         add.set_string_member("memberOf", this.memberOf == parent.alias ? "" : this.memberOf);
347                         add.set_boolean_member("isOptional", this.isOptional);
348                         var ar = new Json.Array();
349                         foreach(var ov in this.optvalues) {
350                                 ar.add_string_element(ov);
351                         }
352                         add.set_array_member("optvalues", ar);
353                         
354                     return add;
355             }   
356                 
357                 
358         }
359 }
360         
361