Example #1
0
def dataviews(module, args):
    """Generate a set of data views."""

    out = []

    parts = re.match(ur'^(.*?)(?:\s*<-\s*(.*?))?$', args.project)
    (appname, epname) = parts.groups()

    app = module.apps.get(appname)
    if epname is not None:
        endpts = [app.endpoints.get(epname)]
    else:
        endpts = app.endpoints.itervalues()

    out_fmt = diagutil.parse_fmt(args.output)

    for endpt in endpts:
        types = []

        for stmt in endpt.stmt:
            appname = stmt.action.action
            for (name, typespec) in module.apps.get(appname).types.iteritems():
                if typespec.WhichOneof('type') == 'tuple':
                    types.append((appname, name, typespec))

        args.output = out_fmt(appname=appname,
                              epname=endpt.name,
                              eplongname=endpt.long_name,
                              **diagutil.attr_fmt_vars(app.attrs, endpt.attrs))

        out.append(_generate_view(module, appname, types))

        diagutil.output_plantuml(args, out[-1])

    return out
Example #2
0
            def new_var(var, name):
                """Outputs a new definition when VarManager makes a new variable."""

                app = module.apps[name]
                write(
                    '[{}] as {}{}',
                    appfmt(appname=name_map.get(name, name),
                           **diagutil.attr_fmt_vars(app.attrs)).replace(
                               '\n', r'\n'), var,
                    ' <<highlight>>' if name in highlights else '')
Example #3
0
            def new_var(var, name):
                """Outputs a new definition when VarManager makes a new variable."""

                #TODO dodgy, should be using context (look at syslseqs)
                if istoplevel:
                    template = 'state "{}" as X{}{} {{'
                else:
                    template = '  state "{}" as {}{}'
                    (_, _, name) = name.partition(' : ')

                app = module.apps[name]
                write(
                    template,
                    appfmt(appname=name,
                           **diagutil.attr_fmt_vars(app.attrs)).replace(
                               '\n', r'\n'), var,
                    ' <<highlight>>' if name in highlights else '')
Example #4
0
 def new_var(var, appname):
     """Outputs a new definition when VarManager makes a new variable."""
     app = module.apps[appname]
     has_category = syslx.patterns(
         app.attrs) & {'human', 'cron', 'db', 'external', 'ui'}
     assert len(has_category) <= 1
     (order, agent) = {
         'human': (0, 'actor'),
         'ui': (1, 'boundary'),
         'cron': (2, 'control'),
         'db': (4, 'database'),
         'external': (5, 'control'),
     }.get(''.join(has_category), (3, 'control'))
     label = params.appfmt(appname=appname,
                           **diagutil.attr_fmt_vars(app.attrs)).replace(
                               u'\n', ur'\n')
     var_names.append(
         ((order, int(var[1:])), u'{} "{}" as {}'.format(agent, label,
                                                         var)))
Example #5
0
def integration_views(module, deps, args):
    """Generate an integration view."""
    def find_matching_apps(integrations):
        """Yield all apps that match the integrations."""
        exclude = set(args.exclude)
        app_re = re.compile(r'^(?:{})(?: *::|$)'.format(
            '|'.join(integrations)))
        for ((app1, _), (app2, _)) in deps:
            if not ({app1, app2} & exclude):
                for app in [app1, app2]:
                    if app_re.match(app) and 'human' not in syslx.patterns(
                            module.apps[app].attrs):
                        yield app

    def find_apps(matching_apps):
        """Yield all apps that are relevant to this view."""
        exclude = set(args.exclude)
        for ((app1, _), (app2, _)) in deps:
            if not ({app1, app2} & exclude) and ({app1, app2} & matching_apps):
                for app in [app1, app2]:
                    if 'human' not in syslx.patterns(module.apps[app].attrs):
                        yield app

    out_fmt = diagutil.parse_fmt(args.output)

    # The "project" app that specifies the required view of the integrations
    app = module.apps[args.project]

    out = []

    # Interate over each endpoint within the selected project
    for endpt in app.endpoints.itervalues():

        # build the set of excluded items
        exclude_attr = endpt.attrs.get('exclude')
        exclude = set(e.s
                      for e in exclude_attr.a.elt) if exclude_attr else set()

        integrations = []

        # endpt.stmt's "action" will conatain the "apps" whose integration is to be drawn
        # each one of these will be placed into the "integrations" list
        for s in endpt.stmt:
            assert s.WhichOneof('stmt') == 'action', str(s)
            integrations.append(s.action.action)

        # include the requested "app" and all the apps upon which the requested "app"
        # depends in the app set
        matching_apps = set(find_matching_apps(integrations))
        apps = set(find_apps(matching_apps)) - exclude

        # the meaning of integrations is overloaded in this context: above it means
        # it is the list of apps whose integration is to be drawn, here it means the
        # actual integrations between the apps that are in the "apps" set and are not excluded
        def find_integrations():
            """Return all integrations between relevant apps."""
            return {((app1, ep1), (app2, ep2))
                    for ((app1, ep1), (app2, ep2)) in deps
                    if ({app1, app2} <= apps and not ({app1, app2} & exclude))
                    and not ({ep1, ep2} & {'.. * <- *'})}

        args.output = out_fmt(appname=args.project,
                              epname=endpt.name,
                              eplongname=endpt.long_name,
                              **diagutil.attr_fmt_vars(app.attrs, endpt.attrs))

        if args.filter and not re.match(args.filter, args.output):
            continue

        print args.project, matching_apps
        out.append(
            _generate_view(module, args, find_integrations(), matching_apps,
                           app, apps, endpt))

        diagutil.output_plantuml(args, out[-1])

    return out
Example #6
0
    def generate_state_view():
        def make_varmgr(istoplevel=False):
            """Return a variable manager instance."""
            appfmt = syslx.View(module.apps.get(
                args.project).attrs)['appfmt'].s
            appfmt = diagutil.parse_fmt(appfmt)

            def new_var(var, name):
                """Outputs a new definition when VarManager makes a new variable."""

                #TODO dodgy, should be using context (look at syslseqs)
                if istoplevel:
                    template = 'state "{}" as X{}{} {{'
                else:
                    template = '  state "{}" as {}{}'
                    (_, _, name) = name.partition(' : ')

                app = module.apps[name]
                write(
                    template,
                    appfmt(appname=name,
                           **diagutil.attr_fmt_vars(app.attrs)).replace(
                               '\n', r'\n'), var,
                    ' <<highlight>>' if name in highlights else '')

            return diagutil.VarManager(new_var)

        def yield_call_statements(statements):
            for (_, stmt) in enumerate(statements):
                field = stmt.WhichOneof('stmt')
                if field == 'call':
                    yield stmt
                elif field == 'alt':
                    for (_, choice) in enumerate(stmt.alt.choice):
                        for next_stmt in yield_call_statements(choice.stmt):
                            yield next_stmt
                elif field in {'cond', 'loop', 'loop_n', 'foreach', 'group'}:
                    for next_stmt in yield_call_statements(
                            getattr(stmt, field).stmt):
                        yield next_stmt

        with write.uml():

            if diagram_title:
                write('title ' + diagram_title)

            write('left to right direction')
            write('scale max 16384 height')
            write('skinparam nodesep 10')
            #TODO[kirkpatg]: this should probably scale up & down based on nodes & connections
            write('skinparam ranksep 300')
            #write('skinparam linetype polyline')
            write('hide empty description')
            write('skinparam state {{')
            write('  BackgroundColor FloralWhite')
            write('  BorderColor Black')
            write('  ArrowColor Crimson')
            if highlight_color:
                write('  BackgroundColor<<highlight>> ' + highlight_color)
            if arrow_color:
                write('  ArrowColor ' + arrow_color)
            if indirect_arrow_color and indirect_arrow_color != 'none':
                write('  ArrowColor<<indirect>> ' + indirect_arrow_color)
                write('  ArrowColor<<internal>> ' + indirect_arrow_color)
            write('}}')

            var_name = make_varmgr()
            tl_var_name = make_varmgr(True)

            clusters = {}

            # group end points and build the declarations
            for (app_a, ep_a), (app_b, ep_b) in integrations:
                if app_a not in clusters: clusters[app_a] = set()
                if app_b not in clusters: clusters[app_b] = set()

                # create clients in the calling app
                clusters[app_a].add(ep_a)
                if app_a != app_b and not module.apps[app_a].endpoints[
                        ep_a].is_pubsub:
                    clusters[app_a].add(ep_b + " client")

                clusters[app_b].add(ep_b)

            for cluster in clusters:
                tl_var_name(cluster)
                for member in clusters[cluster]:
                    var_name(cluster + ' : ' + member)
                write('}}')

            processed = []
            for ((app_a, ep_a), (app_b, ep_b)) in integrations:
                direct = {app_a, app_b} & highlights

                # build the label
                label = ''
                needs_int = app_a != app_b
                # if 'App1 Event' in ep_a: import pdb; pdb.set_trace()

                # import pdb; pdb.set_trace()
                for stmt in yield_call_statements(
                        module.apps[app_a].endpoints[ep_a].stmt):

                    app_b_name = ' :: '.join(part
                                             for part in stmt.call.target.part)

                    if app_b == app_b_name and ep_b == stmt.call.endpoint:
                        fmt_vars = diagutil.attr_fmt_vars(stmt.attrs)
                        ptrns = syslx.patterns(
                            module.apps[app_b].endpoints[ep_b].attrs)
                        label = diagutil.parse_fmt(app.attrs["epfmt"].s)(
                            needs_int=needs_int,
                            patterns=', '.join(sorted(ptrns)),
                            **fmt_vars)
                        break

                flow = ".".join([app_a, ep_b, app_b, ep_b])
                is_pubsub = module.apps[app_a].endpoints[ep_a].is_pubsub
                ep_b_client = ep_b + " client"
                if app_a != app_b:
                    if is_pubsub:
                        write('{} -{}> {}{}', var_name(app_a + ' : ' + ep_a),
                              '[#black]', var_name(app_b + ' : ' + ep_b),
                              ' : ' + label if label else '')
                    else:
                        write(
                            '{} -{}> {}', var_name(app_a + ' : ' + ep_a),
                            '[#' + indirect_arrow_color +
                            ']-' if indirect_arrow_color else '[#silver]-',
                            var_name(app_a + ' : ' + ep_b_client))
                        if flow not in processed:
                            write('{} -{}> {}{}',
                                  var_name(app_a + ' : ' + ep_b_client),
                                  '[#black]', var_name(app_b + ' : ' + ep_b),
                                  ' : ' + label if label else '')
                            processed.append(flow)

                else:
                    write(
                        '{} -{}> {}{}', var_name(app_a + ' : ' + ep_a),
                        '[#' + indirect_arrow_color +
                        ']-' if indirect_arrow_color else '[#silver]-',
                        var_name(app_b + ' : ' + ep_b),
                        ' : ' + label if label else '')
Example #7
0
    def cmd(args):
        """Handle subcommand."""
        (module, _, _) = syslloader.load(args.modules, args.validations,
                                         args.root)

        def output_sd(args, params):
            """Generate and output a sequence diagram."""
            # pdb.set_trace()
            out = sequence_diag(module, params)

            diagutil.output_plantuml(args, out)

        epfilters = os.getenv('SYSL_SD_FILTERS', '*').split(',')

        # TODO: Find a cleaner way to trigger multi-output.
        if '%(epname)' in args.output:
            out_fmt = diagutil.parse_fmt(args.output)
            for appname in args.app:
                app = module.apps[appname]

                bbs = [[e.s for e in bbox.a.elt]
                       for bbox in syslx.View(app.attrs)['blackboxes'].a.elt]

                seqtitle = diagutil.parse_fmt(
                    syslx.View(app.attrs)['seqtitle'].s or args.seqtitle)
                epfmt = diagutil.parse_fmt(
                    syslx.View(app.attrs)['epfmt'].s or args.endpoint_format)
                appfmt = diagutil.parse_fmt(
                    syslx.View(app.attrs)['appfmt'].s or args.app_format)

                for (name, endpt) in sorted(app.endpoints.iteritems(),
                                            key=lambda kv: kv[1].name):
                    if not any(
                            fnmatch.fnmatch(name, filt) for filt in epfilters):
                        continue

                    attrs = {
                        u'@' + name: value.s
                        for (name, value) in endpt.attrs.iteritems()
                    }
                    args.output = out_fmt(appname=appname,
                                          epname=name,
                                          eplongname=endpt.long_name,
                                          **attrs)

                    if args.filter and not re.match(args.filter, args.output):
                        continue

                    bbs2 = [[
                        e.s for e in bbox.a.elt
                    ] for bbox in syslx.View(endpt.attrs)['blackboxes'].a.elt]

                    varrefs = diagutil.attr_fmt_vars(
                        app.attrs,
                        endpt.attrs,
                        epname=endpt.name,
                        eplongname=endpt.long_name)

                    out = sequence_diag(
                        module,
                        SequenceDiagParams(endpoints=[
                            ' :: '.join(s.call.target.part) + ' <- ' +
                            s.call.endpoint for s in endpt.stmt
                            if s.WhichOneof('stmt') == 'call'
                        ],
                                           epfmt=epfmt,
                                           appfmt=appfmt,
                                           activations=args.activations,
                                           title=seqtitle(**varrefs).replace(
                                               '\n', r'\n'),
                                           blackboxes=bbs + bbs2))
                    diagutil.output_plantuml(args, out)

        else:
            out = sequence_diag(
                module,
                SequenceDiagParams(
                    endpoints=[i[0] for i in args.endpoint
                               ],  # -s builds list of lists (idkw).
                    epfmt=diagutil.parse_fmt(args.endpoint_format),
                    appfmt=diagutil.parse_fmt(args.app_format),
                    activations=args.activations,
                    title=args.title,
                    blackboxes=args.blackboxes))
            diagutil.output_plantuml(args, out)
Example #8
0
    def visit_endpoint(from_app,
                       appname,
                       epname,
                       uptos,
                       sender_patterns,
                       stmt=None,
                       deactivate=None):
        """Recursively visit an endpoint."""
        if from_app:
            sender = var_name(syslx.fmt_app_name(from_app.name))
        else:
            sender = '['
        agent = var_name(appname)
        app = module.apps.get(appname)
        endpt = app.endpoints.get(epname)
        assert endpt

        def visit_stmts(stmts, deactivate, last_parent_stmt):
            """Recursively visit a stmt list."""
            def block(last_stmt, block_stmts, fmt, *args):
                """Output a compound block."""
                write(fmt, *args)
                with write.indent():
                    return visit_stmts(block_stmts, deactivate, last_stmt)

            def block_with_end(last_stmt, block_stmts, fmt, *args):
                """Output a compound block, including the 'end' clause."""
                payload = block(last_stmt, block_stmts, fmt, *args)
                write('end')
                return payload

            payload = None

            for (i, stmt) in enumerate(stmts):
                last_stmt = last_parent_stmt and i == len(stmts) - 1
                if stmt.HasField('call'):
                    with write.indent():
                        payload = visit_endpoint(
                            app, syslx.fmt_app_name(stmt.call.target),
                            stmt.call.endpoint, uptos, app_patterns, stmt,
                            last_stmt and deactivate)
                elif stmt.HasField('action'):
                    write('{0} -> {0} : {1}', agent,
                          r'\n'.join(textwrap.wrap(stmt.action.action, 40)))
                elif stmt.HasField('cond'):
                    payload = block_with_end(last_stmt, stmt.cond.stmt,
                                             'opt {}', stmt.cond.test)
                elif stmt.HasField('loop'):
                    payload = block_with_end(
                        last_stmt, stmt.loop.stmt, 'loop {} {}',
                        stmt.loop.Mode.Name(stmt.loop.mode),
                        stmt.loop.criterion)
                elif stmt.HasField('loop_n'):
                    payload = block_with_end(last_stmt, stmt.loop_n.stmt,
                                             'loop {} times',
                                             stmt.loop_n.count)
                elif stmt.HasField('foreach'):
                    payload = block_with_end(last_stmt, stmt.foreach.stmt,
                                             'loop for each {}',
                                             stmt.foreach.collection)
                elif stmt.HasField('group'):
                    payload = block_with_end(last_stmt, stmt.group.stmt,
                                             'group {}', stmt.group.title)
                elif stmt.HasField('alt'):
                    prefix = 'alt'
                    for (j, choice) in enumerate(stmt.alt.choice):
                        last_alt_stmt = last_stmt and j == len(
                            stmt.alt.choice) - 1
                        payload = block(last_alt_stmt, choice.stmt, '{} {}',
                                        prefix, choice.cond)
                        prefix = 'else'
                    write('end')
                elif stmt.HasField('ret'):
                    write('{}<--{} : {}', sender, agent, stmt.ret.payload)
                else:
                    raise Exception('No statement!')

            return payload

        app_patterns = syslx.patterns(app.attrs)
        target_patterns = syslx.patterns(endpt.attrs)

        patterns = target_patterns

        human = 'human' in app_patterns
        human_sender = 'human' in sender_patterns
        cron = 'cron' in sender_patterns
        needs_int = not (human or human_sender or cron) and sender != agent
        # pdb.set_trace()
        label = re.sub(ur'^.*? -> ', u' ⬄ ',
                       r'\n'.join(textwrap.wrap(unicode(epname), 40)))

        cron = 'cron' in app_patterns

        if stmt:
            assert stmt.HasField('call')

            # pdb.set_trace()
            patterns = syslx.patterns(stmt.attrs) or patterns

            label = params.epfmt(
                epname=label,
                human='human' if human else '',
                human_sender='human sender' if human_sender else '',
                needs_int='needs_int' if needs_int else '',
                args=', '.join(p.name for p in stmt.call.arg),
                patterns=', '.join(sorted(patterns)),
                **diagutil.attr_fmt_vars(stmt.attrs)).replace('\n', r'\n')

        if not ((human and sender == '[') or cron):
            ep_patterns = syslx.patterns(endpt.attrs)

            icon = '<&timer> ' if 'cron' in ep_patterns else ''
            # pdb.set_trace()
            write('{}->{} : {}{}', sender, agent, icon, label)
            if log_integration and stmt:
                log_integration(app=from_app, stmt=stmt, patterns=patterns)

        payload = syslalgo.return_payload(endpt.stmt)
        calling_self = from_app and syslx.fmt_app_name(
            from_app.name) == appname
        if not calling_self and not payload and deactivate:
            deactivate()

        if len(endpt.stmt):
            hit_blackbox = False
            for (upto, comment) in itertools.chain(uptos.iteritems(),
                                                   already_visited.keys()):
                # Compare the common prefix of the current endpt and upto.
                upto_parts = upto.split(' <- ')
                if [appname, epname][:len(upto_parts)] == upto_parts:
                    hit_blackbox = True
                    if payload:
                        write.activate(agent)
                        if comment is not None:
                            write('note over {}: {}', agent, comment
                                  or 'see below')
                    else:
                        if comment is not None:
                            write('note {}: {}',
                                  'left' if sender > agent else 'right',
                                  comment or 'see below')
                    if payload:
                        write('{}<--{} : {}', sender, agent, payload)
                        write.deactivate(agent)
                    break

            if not hit_blackbox:
                with write.activated(agent, human or cron) as deactivate:
                    visiting = (appname + ' <- ' + epname, None)  #'see above')
                    already_visited[visiting] += 1
                    try:
                        return visit_stmts(endpt.stmt, deactivate, True)
                    finally:
                        already_visited[visiting] -= 1
                        if not already_visited[visiting]:
                            del already_visited[visiting]