Integrate basic UCI config file validation support
[openwrt.git] / package / base-files / files / lib / config / parse_spec.awk
1 # AWK file for parsing uci specification files
2 #
3 # Copyright (C) 2006 by Fokus Fraunhofer <carsten.tittel@fokus.fraunhofer.de>
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 # General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 #
19 #
20 # general: unfortunately, the development was done using gawk providing
21 #  a different match() functions than e.g. mawk on debian systems
22 #  - therefore, the script was changed to run on most awk's 
23 #  - even things like [:space:] are not used
24 #
25 # - script  parses the config section definition contained in one 
26 #   specification file
27 # global variables:
28 # * section  - contains the current config section name
29 # * var      - contains the name of the current config option
30 # * type     - contains the type of the current config option
31 # * required - contains the requirements of the current config option
32 # * optional - contains the optional scope of the current config option
33 # * vars[]  - array, contains the name of all config options valid within
34 #             a certain config section, format: csv
35 #
36 # XXX todo: more than one config option with the same in different section
37 # will clash for the following tables
38 # * types[] - contains the type of a config option
39 # * reqs[]  - contains the requirements of a config option
40 # * opts[]  - contains the optional scope of a config option
41 #
42 BEGIN {
43         section_count=1
44         section = ""
45         simple_types = "int|ip|netmask|string|wep|hostname|mac|port|ports|wpapsk"
46 }
47
48 # function print_specification
49 # - prints all information about the created tables containing the
50 #   specification 
51 function print_specification() {
52         for (section in vars) {
53                 printf("%s\n",section);
54                 split(vars[section],arr,",")
55                 for (idx in arr) {
56                         printf("\t%s[%s]",arr[idx],types[section "_" arr[idx]]); 
57                         if (length(reqs[section "_" arr[idx]])) {
58                                 if (reqs[section "_" arr[idx]]==1) {
59                                         printf(",req");
60                                 }else{
61                                         printf(", req(%s)", reqs[section "_" arr[idx]]);
62                                 }
63                         }
64                         if (length(opts[section "_" arr[idx]])) {
65                                 printf(", opt(%s)", opts[section "_" arr[idx]]);
66                         }
67                         printf("\n");
68                 }
69         }
70 }
71
72
73 function reset_option() {
74         # just set global variables parsed on one line back to defaults
75         var = ""
76         type = ""
77         required = ""
78         optional = ""
79         found = 0
80 }
81
82 function store_option() {
83         # save all information about a config option parsed from the spec file
84         # to the relevant tables for future use
85
86         # first check minimum requirements for storing information
87         if (!length(section)) {
88                 print STDERR "line " NR ": section definition missing"
89                 exit 1
90         }
91         if (!length(var)) {
92                 print STDERR "line " NR ": invalid config option name"
93                 exit 1
94         }
95         if (!length(type)) {
96                 print STDERR "line " NR ": invalid config option type"
97                 exit 1
98         }
99
100         # add config options to the names of options available for this
101         # section
102         if (exists[section]!=1) {
103                 section_names[section_count] = section
104                 section_count++
105                 exists[section] = 1
106                 vars[section] = var
107         } else {
108                 vars[section] = vars[section] "," var
109         }
110         
111         # save the type, the requirements and the optional scope of the 
112         # config option
113         types[section "_" var] = type
114         reqs[section "_" var] = required
115         opts[section "_" var] = optional
116 }
117
118 /^declare -x|^export/ { 
119         sub(/^declare -x /,"")
120         sub(/^export /,"")
121         split($0,arr,"=")
122         val=substr(arr[2],2,length(arr[2])-2)
123         ENVIRON[arr[1]] = val
124         next
125 }
126
127 # main parsing function
128 # this is done in one function block to allow multiple semicolon separated
129 # definitions on one line
130 {
131         # replace leading/trailing white space
132         gsub("^[ \t\n]+","");
133         gsub("[ \t\n]+$","");
134
135         # comments are removed
136         # XXX todo: check for quoted comments??
137         if (match($0,/[^#]*/)) {
138                 rest=substr($0,RSTART,RLENGTH)
139         } else {
140                 rest=$0
141         }
142
143         # match the config section "<section> {"
144         if (match(rest,/^[^ \t\n{]+[ \t\n]*\{/)) {
145                 match(rest,/^[^ \t\n{]+/)
146                 section = substr(rest,RSTART,RLENGTH)
147                 rest=substr($0,RSTART+RLENGTH);
148                 match(rest,/[ \t\n]*\{/)
149                 rest=substr(rest,RSTART+RLENGTH)
150                 # check for array indication
151                 if (match(section,/\[[ \t\n]*\]/)) {
152                         section=substr(section,1,RSTART-1)
153                         multiple[section] = 1
154                 } else {
155                         multiple[section] = 0
156                 }
157         }
158
159         reset_option()
160
161         # parse the remaing line as long as there is something to parse
162         while (rest ~ "[^ \t\n}]+") {
163                 found = 0
164
165                 # get option name and option type
166                 # first, check for "simple" datatype definitions
167                 if (match(rest,"[^: \t\n]+[ \t\n]*:[ \t\n]*(" \
168                                         simple_types ")")){
169                         match(rest,"[^: \t\n]+")
170                         var=substr(rest,RSTART,RLENGTH)
171                         rest=substr(rest,RSTART+RLENGTH)
172                         match(rest,"[ \t\n]*:[ \t\n]*")
173                         rest=substr(rest,RSTART+RLENGTH)
174                         match(rest,"(" simple_types ")")
175                         type=substr(rest,RSTART,RLENGTH)
176                         rest = substr(rest,RSTART+RLENGTH)
177                         found = 1
178                 # next, check for enum definitions
179                 } else if (match(rest,/[^: \t\n]+[ \t\n]*:[ \t\n]*enum\([^\)]+\)/ )) {
180                         match(rest,"[^: \t\n]+")
181                         var=substr(rest,RSTART,RLENGTH)
182                         rest=substr(rest,RSTART+RLENGTH)
183                         match(rest,/[ \t\n]*:[ \t\n]*enum\(/)
184                         rest=substr(rest,RSTART+RLENGTH)
185                         match(rest,/[^\)]+/)
186                         type="enum," substr(rest,RSTART,RLENGTH)
187                         rest = substr(rest,RSTART+RLENGTH+1)
188                         found=1
189                 }                       
190
191                 # after the name and the type, 
192                 # get the option requirements/scope
193                 if (match(rest,/[^,]*,[ \t\n]*required\[[^]]+\]/)) {
194                         match(rest,"[^,]*")
195                         save=substr(rest,RSTART,RLENGTH)
196                         rest=substr(rest,RSTART+RLENGTH)
197                         match(rest,/,[ \t\n]*required\[/);
198                         rest=substr(rest,RSTART+RLENGTH)
199                         match(rest,/[^]]+\]/)
200                         required=substr(rest,RSTART,RLENGTH-1)
201                         save=save substr(rest,RSTART+RLENGTH)
202                         rest=save
203                         found=1
204                 } else if (match(rest,/[^,]*,[ \t\n]*required/)) {
205                         match(rest,"[^,]*")
206                         save=substr(rest,RSTART,RLENGTH)
207                         rest=substr(rest,RSTART+RLENGTH)
208                         match(rest,",[ \t\n]*required");
209                         rest=substr(rest,RSTART+RLENGTH)
210                         required=1
211                         save=save substr(rest,RSTART+RLENGTH)
212                         rest=save
213                         found=1
214                 }
215                 if (match(rest,/[^,]*,[ \t\n]*optional\[[^]]+\]/)) {
216                         match(rest,"[^,]*")
217                         save=substr(rest,RSTART,RLENGTH)
218                         rest=substr(rest,RSTART+RLENGTH)
219                         match(rest,/,[ \t\n]*optional\[/);
220                         rest=substr(rest,RSTART+RLENGTH)
221                         match(rest,/[^]]+\]/)
222                         optional=substr(rest,RSTART,RLENGTH-1)
223                         save=save substr(rest,RSTART+RLENGTH)
224                         rest=save
225                         found=1
226                 }
227         
228                 # if the remaining line contains a semicolon, complete the
229                 # specification of the config options
230                 if (match(rest, "^[ \t\n]*;(.*)")) {
231                         match(rest,"^[ \t\n]*;")
232                         rest=substr(rest,RSTART+RLENGTH)
233                         if (found==1) {
234                                 store_option()
235                         }
236                         reset_option()
237
238                 # if nothing matched on this line, clear the rest
239                 } else if (!found) {
240                         rest = ""
241                 }
242         }
243
244         # after the line is pared, store the configuration option in the
245         # table if any has been defined
246         if (length(var)) {
247                 store_option()
248                 reset_option()
249         }
250         # close the section if the line contained a closing section bracket, 
251         # XXX todo: check if this has to be done more intelligent
252         if ($0 ~ /\}/) {
253                 section=""
254         }
255 }