class CrossPlane::Parser
Attributes
analyzer[RW]
catch_errors[RW]
combine[RW]
comments[RW]
filename[RW]
ignore[RW]
included[RW]
includes[RW]
lexer[RW]
payload[RW]
single[RW]
strict[RW]
Public Class Methods
new(*args)
click to toggle source
# 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
Public Instance Methods
_handle_error(parsing, e, onerror: nil)
click to toggle source
# 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)
click to toggle source
# 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.message) 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)
click to toggle source
# 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)
click to toggle source
# 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.message) 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