Пример #1
0
 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
Пример #2
0
 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))
Пример #3
0
    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))
Пример #4
0
    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}
Пример #5
0
    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
            },
        }
Пример #6
0
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
Пример #7
0
    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
Пример #8
0
    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
Пример #9
0
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))
Пример #10
0
    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
Пример #11
0
    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},
        }
Пример #12
0
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()
Пример #13
0
            # 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()
Пример #14
0
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.')
Пример #15
0
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))