def create_parser(self, filename, data): def opener(fn): assert fn == filename, (fn, filename) return NamedBytesIO(filename, data) parser = FileDialplanParser(opener=opener) parser.include(filename) return parser
def handle_args(self, args): MessageDefManager.muted = True # no messages to stderr parser = FileDialplanParser() parser.include(args.dialplan) dialplan = next(iter(parser)) print(dialplan.format_as_dialplan_show( reverse=args.reverse))
def handle_args(self, args): # No messages to stderr, but do collect the E_APP_MISSING and # E_FUNC_MISSING errors. aggregator = Aggregator() MessageDefManager.muted = True # Load func_odbc functions if requested. load_func_odbc_functions(args.func_odbc, args.dialplan) parser = FileDialplanParser() parser.include(args.dialplan) dialplan = next(iter(parser)) del dialplan apploader = AppLoader() funcloader = FuncLoader() self.print_modules_used(apploader, funcloader) self.print_load_statements( set(apploader.used_modules) | set(funcloader.used_modules)) self.print_unknowns(aggregator) return int( bool(aggregator.unknown_apps) or bool(aggregator.unknown_funcs))
def on_post(self, req, resp): # Reading POSTed FILEs: https://github.com/falconry/falcon/issues/825 filename = 'extensions.conf' # {"files": {"FILENAME": "FILEDATA"}} doc = req.context['doc'] filedata = doc['files'][filename].encode('utf-8') opener = UploadedFileOpener(filename, filedata) parser = FileDialplanParser(opener=opener) parser.include(filename) dialplan = next(iter(parser)) dialplan.walk_jump_destinations() del dialplan issues = {filename: []} msgs = collect_messages() for msg in msgs: formatted = msg.message.format(**msg.fmtkwargs) issues[msg.where.filename].append({ 'line': msg.where.lineno, 'class': msg.__class__.__name__, 'desc': formatted, }) req.context['result'] = {'results': issues}
def on_post(self, req, resp): # Reading POSTed FILEs: https://github.com/falconry/falcon/issues/825 filename = 'extensions.conf' # {"files": {"FILENAME": "FILEDATA"}} doc = req.context['doc'] filedata = doc['files'][filename].encode('utf-8') opener = UploadedFileOpener(filename, filedata) parser = FileDialplanParser(opener=opener) parser.include(filename) dialplan = next(iter(parser)) dialplan.walk_jump_destinations() del dialplan issues = {filename: []} msgs = collect_messages() for msg in msgs: formatted = msg.message.format(**msg.fmtkwargs) issues[msg.where.filename].append({ 'line': msg.where.lineno, 'class': msg.__class__.__name__, 'desc': formatted, }) req.context['result'] = { 'results': issues, 'asterisklint': { 'version': version_str }, }
def main(args, envs): parser = argparse.ArgumentParser( description=( 'Do sanity checks on dialplan. Suppress comma separated ' 'error classes through the ALINT_IGNORE environment variable. ' 'Returns 1 if any issue was reported.')) parser.add_argument( 'dialplan', metavar='EXTENSIONS_CONF', nargs='?', default='./extensions.conf', help='path to extensions.conf') parser.add_argument( '--func-odbc', metavar='FUNC_ODBC_CONF', action=UniqueStore, help="path to func_odbc.conf, will be read automatically if found " "in same the same dir as extensions.conf; set empty to disable") args = parser.parse_args(args) # Load func_odbc functions if requested. load_func_odbc_functions(args.func_odbc, args.dialplan) parser = FileDialplanParser() parser.include(args.dialplan) dialplan = next(iter(parser)) dialplan.walk_jump_destinations() del dialplan # MessageDefManager.raised is a dict of messages ordered by message # type. All message types share the same muted flag, so we need only # examine the first. if any(not i[0].muted for i in MessageDefManager.raised.values()): return 1
def handle_args(self, args): # Load func_odbc functions if requested. load_func_odbc_functions(args.func_odbc, args.dialplan) parser = FileDialplanParser() parser.include(args.dialplan) dialplan = next(iter(parser)) dialplan.walk_jump_destinations() del dialplan # MessageDefManager.raised is a dict of messages ordered by message # type. All message types share the same muted flag, so we need only # examine the first. if any(not i[0].muted for i in MessageDefManager.raised.values()): return 1
def main(args, envs): parser = argparse.ArgumentParser( description=( 'Shows the dialplan like Asterisk does with the CLI command ' '"dialplan show". Useful for testing whether asterisklint ' 'parser the input properly.')) parser.add_argument( 'dialplan', metavar='EXTENSIONS_CONF', nargs='?', default='./extensions.conf', help='path to extensions.conf') parser.add_argument( '--reverse', action='store_true', help="some versions of Asterisk output the dialplan file in reverse") args = parser.parse_args(args) MessageDefManager.muted = True # no messages to stderr parser = FileDialplanParser() parser.include(args.dialplan) dialplan = next(iter(parser)) print(dialplan.format_as_dialplan_show( reverse=args.reverse))
def handle_args(self, args): MessageDefManager.muted = True loader = VarLoader() parser = FileDialplanParser() parser.include(args.dialplan) dialplan = next(iter(parser)) contexts_by_name = list(sorted( (context.name for context in dialplan.contexts), key=(lambda x: x.lower()))) # TODO: dialplan.all_labels is not a public interface.. labels_by_name = list(sorted( dialplan.all_labels, key=(lambda x: x.lower()))) # TODO: loader._variables is *not* a public interface.. varlist_by_name = list(sorted( loader._variables.items(), key=(lambda x: x[0].lower()))) if args.verbose: self.print_contexts(contexts_by_name) self.print_labels(labels_by_name) self.print_variables(varlist_by_name) # Calculate Levenshtein distance if available. Complain if # module wasn't loaded and the user did not ask for verbose # printing either. if editdistance: identifiers = ( set(contexts_by_name) | set(labels_by_name) | set([i[0] for i in varlist_by_name])) ret = self.print_distance(identifiers) elif args.verbose: ret = 0 else: raise ImportError( 'Loading editdistance failed. Using this command without ' 'the editdistance and without verbose mode is a no-op.') return ret
def on_post(self, req, resp): # Reading POSTed FILEs: # https://falcon.readthedocs.io/en/stable/user/faq.html # #how-can-i-access-posted-files # Reading JSON: # https://falcon.readthedocs.io/en/stable/api/api.html # #falcon.RequestOptions # > A dict-like object that allows you to configure the # > media-types that you would like to handle. By default, a # > handler is provided for the application/json media type. filename = 'extensions.conf' # {"files": {"FILENAME": "FILEDATA"}} filedata = req.media['files'][filename].encode('utf-8') opener = UploadedFileOpener(filename, filedata) parser = FileDialplanParser(opener=opener) parser.include(filename) dialplan = next(iter(parser)) dialplan.walk_jump_destinations() del dialplan issues = {filename: []} msgs = collect_messages() for msg in msgs: formatted = msg.message.format(**msg.fmtkwargs) issues[msg.where.filename].append({ 'line': msg.where.lineno, 'class': msg.__class__.__name__, 'desc': formatted, }) resp.media = { 'results': issues, 'asterisklint': {'version': version_str}, }
class MakePatternsCanonicalMutator(FileMutatorBase): def process_issue(self, issue, inline, outfile): # Split between "exten =>" and the rest. linehead, linetail = inline.split(b'=', 1) if linetail[0] == b'>': linehead += b'>' linetail = linetail[1:] # We can safely use replace-1 here. If we were able # to parse your config, we should not be looking # directly at a pattern first. linetail = linetail.replace(issue.needle, issue.replacement, 1) outline = linehead + b'=' + linetail # Write new line. outfile.write(outline) MessageDefManager.muted = True # no messages to stderr aggregator = Aggregator() parser = FileDialplanParser() parser.include(sys.argv[1]) print('Making dialplan patterns into canonical form.') print('Parsing current config...') dialplan = next(iter(parser)) print() mutator = MakePatternsCanonicalMutator(aggregator.issues_per_file) mutator.request_permission_and_process()
# parsed data, so it may not stringify back into the # original... needle = str(issue.data).encode('utf-8') replacement = '{}?{}'.format(issue.cond, app_with_args) replacement = replacement.encode('utf-8') outline = inline.replace(needle, replacement, 1) if inline == outline: raise AssertionError( "We did not replace anything in {!r} " "for issue {!r} and data '{}'".format( inline, issue, issue.data)) outfile.write(outline) else: raise NotImplementedError(issue) MessageDefManager.muted = True # no messages to stderr aggregator = Aggregator() parser = FileDialplanParser() parser.include(sys.argv[1]) print('Converting dialplan from 1.4 to 11.') print('Parsing current config...') dialplan = next(iter(parser)) print() mutator = ConvertDp104To1100Mutator(aggregator.issues_per_file) mutator.request_permission_and_process()
def main(args, envs): parser = argparse.ArgumentParser( description=( 'Report similarly named contexts, labels and variables. ' 'All parse errors are suppressed. Returns 1 if any potential ' 'issue was reported.')) parser.add_argument( '-v', '--verbose', action='store_true', help='list all identifiers first before reporting similarities') parser.add_argument( 'dialplan', metavar='EXTENSIONS_CONF', nargs='?', default='./extensions.conf', help='path to extensions.conf') args = parser.parse_args(args) MessageDefManager.muted = True loader = VarLoader() parser = FileDialplanParser() parser.include(args.dialplan) dialplan = next(iter(parser)) contexts_by_name = list(sorted( (context.name for context in dialplan.contexts), key=(lambda x: x.lower()))) # TODO: dialplan.all_labels is not a public interface.. labels_by_name = list(sorted( dialplan.all_labels, key=(lambda x: x.lower()))) # TODO: loader._variables is *not* a public interface.. varlist_by_name = list(sorted( loader._variables.items(), key=(lambda x: x[0].lower()))) if args.verbose: print('Contexts encountered:') for context in contexts_by_name: print(' {}'.format(context)) print() print('Labels encountered:') for label in labels_by_name: print(' {}'.format(label)) print() print('Variables encountered:') for variable, occurrences in varlist_by_name: print(' {:32} [{} times in {} files]'.format( variable, len(occurrences), len(set(i.filename for i in occurrences)))) print() # Calculate Levenshtein distance. if editdistance: identifiers = set() identifiers.update(contexts_by_name) identifiers.update(labels_by_name) identifiers.update([i[0] for i in varlist_by_name]) id_by_name = sorted(identifiers, key=(lambda x: x.lower())) id_by_len = sorted(identifiers, key=(lambda x: (len(x), x.lower()))) similar = defaultdict(set) for id_list in (id_by_name, id_by_len): prev = None for cur in id_list: if prev: lodiff = editdistance.eval(prev.lower(), cur.lower()) if (lodiff == 0 and ( (prev.isupper() and cur.islower()) or (prev.islower() and cur.isupper()))): pass elif (prev.startswith('ARG') and cur.startswith('ARG') and prev[3:].isdigit() and cur[3:].isdigit()): # ARG1..n as passed to a Gosub() routine. pass elif (lodiff <= 2 and (lodiff + 1) < len(prev) and (lodiff + 1) < len(cur)): similar[prev].add(cur) similar[cur].add(prev) prev = cur if similar: print('Identifiers with similar names include:') for identifier in sorted( similar.keys(), key=(lambda x: x.lower())): similar_to = ', '.join(sorted(similar[identifier])) print(' {:32} [similar to: {}]'.format( identifier, similar_to)) print() return 1 elif not args.verbose: raise ImportError( 'Loading editdistance failed. Using this command without ' 'the editdistance and without verbose mode is a no-op.')
def main(args, envs): parser = argparse.ArgumentParser( description=( "Show which modules, apps and functions are used by the dialplan. " "Useful when you use autoload=no in your modules.conf. Beware " "that you do need more modules than just these listed.")) parser.add_argument( 'dialplan', metavar='EXTENSIONS_CONF', nargs='?', default='./extensions.conf', help='path to extensions.conf') parser.add_argument( '--func-odbc', metavar='FUNC_ODBC_CONF', action=UniqueStore, help="path to func_odbc.conf, will be read automatically if found " "in same the same dir as extensions.conf; set empty to disable") args = parser.parse_args(args) # No messages to stderr, but do collect the E_APP_MISSING and # E_FUNC_MISSING errors. aggregator = Aggregator() MessageDefManager.muted = True # Load func_odbc functions if requested. load_func_odbc_functions(args.func_odbc, args.dialplan) parser = FileDialplanParser() parser.include(args.dialplan) dialplan = next(iter(parser)) del dialplan apploader = AppLoader() funcloader = FuncLoader() all_modules = set() for what, used_items, used_modules in ( ('Application', apploader.used_apps, apploader.used_modules), ('Function', funcloader.used_funcs, funcloader.used_modules)): all_modules.update(used_modules) used_items_per_module = defaultdict(list) for item in used_items: used_items_per_module[item.module].append(item) print('; {} providing modules used:'.format(what)) for module in used_modules: items = used_items_per_module[module][:] item_lines = [''] while items: next_ = items.pop(0) if item_lines[-1]: item_lines[-1] += ', ' formatted = '{}()'.format(next_.name) if len(item_lines[-1]) + len(formatted) > 52: item_lines.append(formatted) else: item_lines[-1] += formatted # Output. print('; {:20s} {}'.format( module, item_lines[0].strip())) for item_line in item_lines[1:]: print('; {:20s} {}'.format( '', item_line.strip())) print(';') print('; modules.conf') for module in sorted(all_modules): if module != '<builtin>': print('load => {}.so'.format(module)) print() if aggregator.unknown_apps: print('; WARNING: The following unknown applications were seen:') print('; {}'.format(', '.join(sorted(aggregator.unknown_apps)))) print(';') if aggregator.unknown_funcs: print('; WARNING: The following unknown functions were seen:') print('; {}'.format(', '.join(sorted(aggregator.unknown_funcs)))) print(';') return int(bool(aggregator.unknown_apps) or bool(aggregator.unknown_funcs))