Class: CrossPlane::Parser
- Inherits:
-
Object
- Object
- CrossPlane::Parser
- Defined in:
- lib/crossplane/parser.rb
Instance Attribute Summary collapse
-
#analyzer ⇒ Object
Returns the value of attribute analyzer.
-
#catch_errors ⇒ Object
Returns the value of attribute catch_errors.
-
#combine ⇒ Object
Returns the value of attribute combine.
-
#comments ⇒ Object
Returns the value of attribute comments.
-
#filename ⇒ Object
Returns the value of attribute filename.
-
#ignore ⇒ Object
Returns the value of attribute ignore.
-
#included ⇒ Object
Returns the value of attribute included.
-
#includes ⇒ Object
Returns the value of attribute includes.
-
#lexer ⇒ Object
Returns the value of attribute lexer.
-
#payload ⇒ Object
Returns the value of attribute payload.
-
#single ⇒ Object
Returns the value of attribute single.
-
#strict ⇒ Object
Returns the value of attribute strict.
Instance Method Summary collapse
- #_handle_error(parsing, e, onerror: nil) ⇒ Object
- #_parse(parsing, tokens, ctx: [], consume: false) ⇒ Object
- #_prepare_if_args(stmt) ⇒ Object
-
#initialize(*args) ⇒ Parser
constructor
A new instance of Parser.
- #parse(*args, onerror: nil) ⇒ Object
Constructor Details
#initialize(*args) ⇒ Parser
Returns a new instance of Parser.
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
# File 'lib/crossplane/parser.rb', line 26 def initialize(*args) args = args[0] || {} required = ['filename'] conflicts = [] requires = {} valid = { 'params' => [ 'catch_errors', 'combine', 'comments', 'filename', 'ignore', 'single', 'strict', ] } content = CrossPlane.utils.validate_constructor(client: self, args: args, required: required, conflicts: conflicts, requires: requires, valid: valid) self.catch_errors = (content[:catch_errors] && content[:catch_errors] == true) ? true : false self.combine = (content[:combine] && content[:combine] == true) ? true : false self.comments = (content[:comments] && content[:comments] == true) ? true : false self.filename = content[:filename] self.ignore = content[:ignore] ? content[:ignore] : [] self.single = (content[:single] && content[:single] == true) ? true : false self.strict = (content[:strict] && content[:strict] == true) ? true : false self.analyzer = CrossPlane::Analyzer.new() self.lexer = CrossPlane::Lexer.new(filename: self.filename) end |
Instance Attribute Details
#analyzer ⇒ Object
Returns the value of attribute analyzer.
13 14 15 |
# File 'lib/crossplane/parser.rb', line 13 def analyzer @analyzer end |
#catch_errors ⇒ Object
Returns the value of attribute catch_errors.
14 15 16 |
# File 'lib/crossplane/parser.rb', line 14 def catch_errors @catch_errors end |
#combine ⇒ Object
Returns the value of attribute combine.
15 16 17 |
# File 'lib/crossplane/parser.rb', line 15 def combine @combine end |
#comments ⇒ Object
Returns the value of attribute comments.
16 17 18 |
# File 'lib/crossplane/parser.rb', line 16 def comments @comments end |
#filename ⇒ Object
Returns the value of attribute filename.
17 18 19 |
# File 'lib/crossplane/parser.rb', line 17 def filename @filename end |
#ignore ⇒ Object
Returns the value of attribute ignore.
18 19 20 |
# File 'lib/crossplane/parser.rb', line 18 def ignore @ignore end |
#included ⇒ Object
Returns the value of attribute included.
19 20 21 |
# File 'lib/crossplane/parser.rb', line 19 def included @included end |
#includes ⇒ Object
Returns the value of attribute includes.
20 21 22 |
# File 'lib/crossplane/parser.rb', line 20 def includes @includes end |
#lexer ⇒ Object
Returns the value of attribute lexer.
21 22 23 |
# File 'lib/crossplane/parser.rb', line 21 def lexer @lexer end |
#payload ⇒ Object
Returns the value of attribute payload.
22 23 24 |
# File 'lib/crossplane/parser.rb', line 22 def payload @payload end |
#single ⇒ Object
Returns the value of attribute single.
23 24 25 |
# File 'lib/crossplane/parser.rb', line 23 def single @single end |
#strict ⇒ Object
Returns the value of attribute strict.
24 25 26 |
# File 'lib/crossplane/parser.rb', line 24 def strict @strict end |
Instance Method Details
#_handle_error(parsing, e, onerror: nil) ⇒ Object
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/crossplane/parser.rb', line 80 def _handle_error(parsing, e, onerror: nil) file = parsing['file'] error = e.to_s line = e.respond_to?('lineno') ? e.lineno : nil parsing_error = {'error' => error, 'line' => line} payload_error = {'file' => file, 'error' => error, 'line' => line} if not onerror.nil? and not onerror.empty? payload_error['callback'] = onerror(e) end parsing['status'] = 'failed' parsing['errors'].push(parsing_error) self.payload['status'] = 'failed' self.payload['errors'].push(payload_error) end |
#_parse(parsing, tokens, ctx: [], consume: false) ⇒ Object
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 |
# File 'lib/crossplane/parser.rb', line 98 def _parse(parsing, tokens, ctx:[], consume: false) fname = parsing['file'] parsed = [] begin while tuple = tokens.next token, lineno = tuple # we are parsing a block, so break if it's closing break if token == '}' if consume == true if token == '{' _parse(parsing, tokens, consume: true) end end directive = token if self.combine stmt = { 'file' => fname, 'directive' => directive, 'line' => lineno, 'args' => [], } else stmt = { 'directive' => directive, 'line' => lineno, 'args' => [], } end # if token is comment if directive.start_with?('#') if self.comments stmt['directive'] = '#' stmt['comment'] = token[1..-1].lstrip parsed.push(stmt) end next end # TODO: add external parser checking and handling # parse arguments by reading tokens args = stmt['args'] token, _ = tokens.next while not ['{', '}', ';'].include?(token) stmt['args'].push(token) token, _ = tokens.next end # consume the directive if it is ignored and move on if self.ignore.include?(stmt['directive']) # if this directive was a block consume it too if token == '{' _parse(parsing, tokens, consume: true) end next end # prepare arguments if stmt['directive'] == 'if' _prepare_if_args(stmt) end begin # raise errors if this statement is invalid self.analyzer.analyze( fname, stmt, token, ctx, self.strict ) rescue NgxParserDirectiveError => e if self.catch_errors _handle_error(parsing, e) # if it was a block but shouldn't have been then consume if e.strerror.end_with(' is not terminated by ";"') if token != '}' _parse(parsing, tokens, consume: true) else break end end next else raise e end end # add "includes" to the payload if this is an include statement if not self.single and stmt['directive'] == 'include' pattern = args[0] p = Pathname.new(args[0]) if not p.absolute? pattern = File.join(config_dir, args[0]) end stmt['includes'] = [] # get names of all included files # ruby needs a python glob.has_magic equivalent if pattern =~ /\*/ fnames = Dir.glob(pattern) else begin open(pattern).close fnames = [pattern] rescue Exception => e f = CrossPlane::NgxParserIncludeError.new(fname, stmt['line'], e.) fnames = [] if self.catch_errors _handle_error(parsing, f) else raise f end end end fnames.each do |fname| # the included set keeps files from being parsed twice # TODO: handle files included from multiple contexts if not self.included.include?(fname) self.included[fname] = self.includes.length self.includes.push([fname, ctx]) end index = self.included[fname] stmt['includes'].push(index) end end # if this statement terminated with '{' then it is a block if token == '{' inner = self.analyzer.enter_block_ctx(stmt, ctx) # get context for block stmt['block'] = _parse(parsing, tokens, ctx: inner) end parsed.push(stmt) end return parsed rescue StopIteration return parsed end end |
#_prepare_if_args(stmt) ⇒ Object
69 70 71 72 73 74 75 76 77 78 |
# File 'lib/crossplane/parser.rb', line 69 def _prepare_if_args(stmt) args = stmt['args'] if args and args[0].start_with?('(') and args[-1].end_with?(')') args[0] = args[0][1..-1] # left strip this args[-1] = args[-1][0..-2].rstrip s = args[0].empty? ? 1 : 0 e = args.length - (args[-1].empty? ? 1 : 0) args = args[s..e] end end |
#parse(*args, onerror: nil) ⇒ Object
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 |
# File 'lib/crossplane/parser.rb', line 57 def parse(*args, onerror:nil) config_dir = File.dirname(self.filename) self.payload = { 'status' => 'ok', 'errors' => [], 'config' => [], } self.includes = [[self.filename, []]] # stores (filename, config context) tuples self.included = {self.filename => 0} # stores {filename: array index} hash def _prepare_if_args(stmt) args = stmt['args'] if args and args[0].start_with?('(') and args[-1].end_with?(')') args[0] = args[0][1..-1] # left strip this args[-1] = args[-1][0..-2].rstrip s = args[0].empty? ? 1 : 0 e = args.length - (args[-1].empty? ? 1 : 0) args = args[s..e] end end def _handle_error(parsing, e, onerror: nil) file = parsing['file'] error = e.to_s line = e.respond_to?('lineno') ? e.lineno : nil parsing_error = {'error' => error, 'line' => line} payload_error = {'file' => file, 'error' => error, 'line' => line} if not onerror.nil? and not onerror.empty? payload_error['callback'] = onerror(e) end parsing['status'] = 'failed' parsing['errors'].push(parsing_error) self.payload['status'] = 'failed' self.payload['errors'].push(payload_error) end def _parse(parsing, tokens, ctx:[], consume: false) fname = parsing['file'] parsed = [] begin while tuple = tokens.next token, lineno = tuple # we are parsing a block, so break if it's closing break if token == '}' if consume == true if token == '{' _parse(parsing, tokens, consume: true) end end directive = token if self.combine stmt = { 'file' => fname, 'directive' => directive, 'line' => lineno, 'args' => [], } else stmt = { 'directive' => directive, 'line' => lineno, 'args' => [], } end # if token is comment if directive.start_with?('#') if self.comments stmt['directive'] = '#' stmt['comment'] = token[1..-1].lstrip parsed.push(stmt) end next end # TODO: add external parser checking and handling # parse arguments by reading tokens args = stmt['args'] token, _ = tokens.next while not ['{', '}', ';'].include?(token) stmt['args'].push(token) token, _ = tokens.next end # consume the directive if it is ignored and move on if self.ignore.include?(stmt['directive']) # if this directive was a block consume it too if token == '{' _parse(parsing, tokens, consume: true) end next end # prepare arguments if stmt['directive'] == 'if' _prepare_if_args(stmt) end begin # raise errors if this statement is invalid self.analyzer.analyze( fname, stmt, token, ctx, self.strict ) rescue NgxParserDirectiveError => e if self.catch_errors _handle_error(parsing, e) # if it was a block but shouldn't have been then consume if e.strerror.end_with(' is not terminated by ";"') if token != '}' _parse(parsing, tokens, consume: true) else break end end next else raise e end end # add "includes" to the payload if this is an include statement if not self.single and stmt['directive'] == 'include' pattern = args[0] p = Pathname.new(args[0]) if not p.absolute? pattern = File.join(config_dir, args[0]) end stmt['includes'] = [] # get names of all included files # ruby needs a python glob.has_magic equivalent if pattern =~ /\*/ fnames = Dir.glob(pattern) else begin open(pattern).close fnames = [pattern] rescue Exception => e f = CrossPlane::NgxParserIncludeError.new(fname, stmt['line'], e.) fnames = [] if self.catch_errors _handle_error(parsing, f) else raise f end end end fnames.each do |fname| # the included set keeps files from being parsed twice # TODO: handle files included from multiple contexts if not self.included.include?(fname) self.included[fname] = self.includes.length self.includes.push([fname, ctx]) end index = self.included[fname] stmt['includes'].push(index) end end # if this statement terminated with '{' then it is a block if token == '{' inner = self.analyzer.enter_block_ctx(stmt, ctx) # get context for block stmt['block'] = _parse(parsing, tokens, ctx: inner) end parsed.push(stmt) end return parsed rescue StopIteration return parsed end end self.includes.each do |fname, ctx| tokens = self.lexer.lex().to_enum parsing = { 'file' => fname, 'status' => 'ok', 'errors' => [], 'parsed' => [], } begin parsing['parsed'] = _parse(parsing, tokens.to_enum, ctx: ctx) rescue Exception => e _handle_error(parsing, e, onerror: onerror) end self.payload['config'].push(parsing) end if self.combine return _combine_parsed_configs(payload) else return self.payload end end |