Full Code of goldsborough/ig for AI

master b7f9d88c7d1a cached
23 files
39.1 KB
10.0k tokens
34 symbols
1 requests
Download .txt
Repository: goldsborough/ig
Branch: master
Commit: b7f9d88c7d1a
Files: 23
Total size: 39.1 KB

Directory structure:
gitextract_jv0db8cj/

├── .gitignore
├── LICENSE
├── MANIFEST.in
├── Makefile
├── README.md
├── ig/
│   ├── __init__.py
│   ├── colors.py
│   ├── graph.py
│   ├── main.py
│   ├── paths.py
│   ├── serve.py
│   └── walk.py
├── scripts/
│   ├── __init__.py
│   └── bump.py
├── setup.cfg
├── setup.py
└── www/
    ├── graph.html
    ├── graph.js
    ├── sigma/
    │   ├── LICENSE.txt
    │   ├── README.md
    │   ├── sigma.canvas.edges.curvedArrow.js
    │   └── sigma.canvas.labels.def.js
    └── style.css

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
# Own
*.json


# Created by https://www.gitignore.io/api/python

### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
.hypothesis/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# pyenv
.python-version

# celery beat schedule file
celerybeat-schedule

# SageMath parsed files
*.sage.py

# dotenv
.env

# virtualenv
.venv
venv/
ENV/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# End of https://www.gitignore.io/api/python


================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2017 Peter Goldsborough

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


================================================
FILE: MANIFEST.in
================================================
include *.md
include LICENSE

recursive-include www *
recursive-include * Makefile
recursive-exclude * __pycache__
recursive-exclude * *.py[co]
recursive-exclude www/sigma/sigma.js/ *


================================================
FILE: Makefile
================================================
.PHONY: clean-pyc clean-build docs clean

help:
	@echo "clean - remove all build, test, coverage and Python artifacts."
	@echo "clean-build - remove build artifacts."
	@echo "clean-pyc - remove Python file artifacts."
	@echo "bump - bumps the version."
	@echo "test-register - register the project at TestPyPI."
	@echo "register - register the project at PyPI."
	@echo "test-upload - package and upload a releaes to TestPyPI."
	@echo "upload - package and upload a upload to PyPI."
	@echo "dist - package."
	@echo "install - install the package to the active Python's site-packages."

clean: clean-build clean-pyc

clean-build:
	rm -rf build/
	rm -rf dist/
	rm -rf .eggs/
	find . -name '*.egg-info' -exec rm -rf {} +
	find . -name '*.egg' -exec rm -f {} +

clean-pyc:
	find . -name '*.pyc' -exec rm -f {} +
	find . -name '*.pyo' -exec rm -f {} +
	find . -name '*~' -exec rm -f {} +
	find . -name '__pycache__' -exec rm -rf {} +

bump:
	python -m scripts.bump

dist: clean
	python setup.py sdist
	python setup.py bdist_wheel

test-register:
	python setup.py register -r test

register:
	python setup.py register

test-upload: dist
	twine upload -r test $(wildcard dist/*)

upload: dist
	twine upload $(wildcard dist/*)

install:
	python setup.py install


================================================
FILE: README.md
================================================
# :fireworks: ig

<p align="center">
  <img src="extra/graph.gif">
  <br><br>
  <code>ig</code> is a tool to interactively visualize include graphs for C++ projects
  <br><br>
  <img alt="license" src="https://img.shields.io/github/license/mashape/apistatus.svg"/>
</p>

## Overview

Point `ig` at any directory containing C++ source or header files and it will
construct a full graph of all includes, serve you a local website and visualize
the graph interactively with [sigma.js](http://sigmajs.org), for you to admire.

Usage is very easy:

```sh
$ ig -o include
```

will inspect the folder `include`, serve a website on `localhost:8080` and even
open your browser for you. The full set of options currently include:

```sh
usage: ig [-h] [--pattern PATTERNS] [-i PREFIXES] [-v] [-p PORT] [-o] [-j]
          [-d DIRECTORY] [--relation {includes,included-by}]
          [--min-degree MIN_DEGREE] [--group-granularity GROUP_GRANULARITY]
          [--full-path] [--colors COLORS] [--color-variation COLOR_VARIATION]
          [--color-alpha-min COLOR_ALPHA_MIN]
          directories [directories ...]

Visualize C++ include graphs

positional arguments:
  directories           The directories to inspect

optional arguments:
  -h, --help            show this help message and exit
  --pattern PATTERNS    The file (glob) patterns to look for
  -i PREFIXES, -I PREFIXES, --prefix PREFIXES
                        An include path for headers to recognize
  -v, --verbose         Turn on verbose output
  -p PORT, --port PORT  The port to serve the visualization on
  -o, --open            Open the webpage immediately
  -j, --json            Print the graph JSON and instead of serving it
  -d DIRECTORY, --dir DIRECTORY
                        The directory to store the served files in. If not
                        supplied, a temporary directory is created.
  --relation {includes,included-by}
                        The relation of edges in the graph
  --min-degree MIN_DEGREE
                        The initial minimum degree nodes should have to be
                        displayed
  --group-granularity GROUP_GRANULARITY
                        How coarse to group nodes (by folder)
  --full-path           If set, shows the full path for nodes
  --colors COLORS       The base RGB colors separated by commas
  --color-variation COLOR_VARIATION
                        The variation in RGB around the base colors
  --color-alpha-min COLOR_ALPHA_MIN
                        The minimum alpha value for colors
```

But does it scale? It scales quite well. The graph you see above is the include
graph for the entire LLVM and clang codebase, which spans more than 5,000 files
and 1.5M LOC. Note that the visualization also includes sliders to group nodes
by folder and filter out low-degree nodes.

## Installation

Get it with pip:

```sh
$ pip install ig-cpp
```

Works with Python 2 and 3.

## Examples

Who ever said C++ was an ugly language?

<p align="center">
  <img src="extra/llvm-adt.png">
  <br><br>
  <b>LLVM/ADT</b>
  <br><br>
</p>

<p align="center">
  <img src="extra/tf.png">
  <br><br>
  <b>TensorFlow</b>
  <br><br>
</p>

<p align="center">
  <img src="extra/libcxx.png">
  <br><br>
  <b>libc++ (the standard library)</b>
  <br><br>
</p>

## Authors

[Peter Goldsborough](http://goldsborough.me) + [cat](https://goo.gl/IpUmJn)
:heart:


================================================
FILE: ig/__init__.py
================================================
from datetime import date

__title__ = 'ig'
__url__ = "https://github.com/goldsborough/ig"
__version__ = '0.1.9'
__author__ = 'Peter Goldsborough'
__license__ = 'MIT'
__copyright__ = 'Copyright {0} Peter Goldsborough'.format(date.today().year)


================================================
FILE: ig/colors.py
================================================
import random


def random_color(base, variation):
    '''
    Returns a random, bounded color value.

    Args:
        base: Some base color component (between 0 and 255)
        variation: The degree of variation (around the color)

    Returns:
        A random color.
    '''
    color = base + (2 * random.random() - 1) * variation
    return max(8, min(int(color), 256))


class Colors(object):
    '''
    Aggregates information about the color scheme of the visualization.
    '''

    def __init__(self, base_colors):
        '''
        Constructor.

        Args:
            base_colors: The base colors around which to vary
        '''
        self.base = list(base_colors)
        self.variation = None
        self.alpha_min = None

    def generate(self):
        '''
        Generates a color.

        Returns:
            A new RGBA color value.
        '''
        rgba = [random_color(color, self.variation) for color in self.base]
        rgba.append(max(self.alpha_min, random.random()))
        return 'rgba({0})'.format(','.join(map(str, rgba)))


================================================
FILE: ig/graph.py
================================================
''' Defines the graph structure that stores the include relationships. '''

import logging
import os
import random


log = logging.getLogger(__name__)


class Graph(object):
    '''
    Stores nodes (files) and edges (includes).

    Nodes are stored in a map from absolute filename to a dictionary, which
    holds all the information required by sigma.js (e.g. ID and label). Edges
    are stored as a list of (id, source, target) objects, along with some
    additional information. The representation is not very compact or optimal,
    but processing even very large projects still seems instant.
    '''
    def __init__(self,
                 relation,
                 full_path,
                 colors,
                 group_granularity):
        '''
        Constructor.

        Args:
            relation: One of {'includes', 'included-by'}
            full_path: Whether to print node labels with their full path
            colors: A `Colors` object storing information about the color scheme
            group_granularity: The granularity setting for node groups
            directory: The directory from which to serve the visualization.
        '''
        assert relation in ('includes', 'included-by')

        self.edges = []
        self.nodes = {}
        self.is_included_by_relation = (relation == 'included-by')
        self.use_full_path = full_path
        self.colors = colors
        self.group_granularity = group_granularity

    def add(self, node_name, neighbors):
        '''
        Adds a new node to the graph, along with its adjacent neighbors.

        Args:
            node_name: The name of the node (i.e. current file)
            neighbors: A list of names of neighbors (included files)
        '''
        node = self._get_or_add_node(node_name)

        if not self.is_included_by_relation:
            node['size'] = len(neighbors)

        for neighbor_name in neighbors:
            neighbor = self._get_or_add_node(neighbor_name)
            if self.is_included_by_relation:
                neighbor['size'] += 1
            self._add_edge(node, neighbor)

    def to_json(self):
        '''
        Turns the graph into a dictionary (a.k.a. JSON).

        The format is {"nodes": list of node objects, "edges": array of edge
        objects}.

        Returns:
            A JSON (dictionary) representation of the graph.
        '''
        nodes = list(self.nodes.values())
        return dict(nodes=nodes, edges=self.edges)

    @property
    def is_empty(self):
        '''
        Returns:
            True if the graph has no nodes at all, else False.
        '''
        return len(self.nodes) == 0

    def _get_or_add_node(self, node_name):
        '''
        Returns a node and possibly adds it to the graph.

        Args:
            node_name: The name of the node to fetch

        Returns:
            The node entry for the given name.
        '''
        node = self.nodes.get(node_name)
        if node is None:
            node = self._add_node(node_name)
        return node

    def _add_node(self, node_name):
        '''
        Adds a node to the graph.

        Args:
            node_name: The name of the node to add

        Returns:
            The newly created node object.
        '''
        assert node_name not in self.nodes

        node = {}
        node['id'] = len(self.nodes)
        node['size'] = 1
        node['color'] = self.colors.generate()

        if self.use_full_path:
            node['label'] = node_name
        else:
            node['label'] = os.path.basename(node_name)

        # Take up to the last two directory names as the group
        directories = os.path.dirname(node_name).split(os.sep)
        begin = len(directories) - self.group_granularity
        node['group'] = os.sep.join(directories[begin:begin + 2])

        # Make the initial starting point random, but very small, so we get
        # an "explosion"/"expansion" effect.
        node['x'] = random.random() * 0.01
        node['y'] = random.random() * 0.01

        self.nodes[node_name] = node

        return node

    def _add_edge(self, source, target):
        '''
        Adds an edge to the graph.

        Args:
            source: The entry of the source node
            target: The entry of the target node

        Returns:
            The newly created edge object
        '''
        edge = {}
        edge['id'] = len(self.edges)
        edge['size'] = 10  # Make the arrows larger?
        edge['type'] = 'curvedArrow'

        # The natural direction is "includes", so swap if we want "included-by"
        if self.is_included_by_relation:
            source, target = target, source
        edge['source'] = source['id']
        edge['target'] = target['id']

        self.edges.append(edge)

        return edge

    def __repr__(self):
        '''
        Returns:
            A string representation of the graph.
        '''
        nodes = len(self.nodes)
        edges = len(self.edges)
        return '<Graph: nodes = {0}, edges = {1}>'.format(nodes, edges)


================================================
FILE: ig/main.py
================================================
'''Entry point and command line parsing for ig.'''

from __future__ import print_function

import argparse
import logging
import os
import sys

from ig import colors, graph, serve, walk


def setup_logging():
    '''Sets up the root logger.'''
    handler = logging.StreamHandler(sys.stderr)
    formatter = logging.Formatter('[%(levelname)s] %(message)s')
    handler.setFormatter(formatter)

    log = logging.getLogger(__package__)
    log.addHandler(handler)
    log.setLevel(logging.INFO)

    return log


def parse_arguments(args):
    '''
    Sets up the command line argument parser and parses arguments.

    Args:
        args: The list of argumnets passed to the command line

    Returns:
        The parsed arguments.
    '''
    parser = argparse.ArgumentParser(description='Visualize C++ include graphs')
    parser.add_argument('directories',
                        nargs='+',
                        help='The directories to inspect')
    parser.add_argument('--pattern',
                        action='append',
                        default=['*.[ch]pp', '*.[ch]'],
                        dest='patterns',
                        help='The file (glob) patterns to look for')
    parser.add_argument('-i', '-I', '--prefix',
                        action='append',
                        dest='prefixes',
                        default=[os.getcwd()],
                        help='An include path for headers to recognize')
    parser.add_argument('-v', '--verbose',
                        action='store_true',
                        help='Turn on verbose output')

    parser.add_argument('-p', '--port',
                        type=int,
                        default=8080,
                        help='The port to serve the visualization on')
    parser.add_argument('-o', '--open',
                        action='store_true',
                        help='Open the webpage immediately')
    parser.add_argument('-j', '--json',
                        action='store_true',
                        help='Print the graph JSON instead of serving it')
    parser.add_argument('-d', '--dir',
                        dest='directory',
                        help='The directory to store the served files in. If '
                             'not supplied, a temporary directory is created.')

    parser.add_argument('--relation',
                        choices=['includes', 'included-by'],
                        default='included-by',
                        help='The relation of edges in the graph')
    parser.add_argument('--min-degree',
                        type=float,
                        default=0.1,
                        help='The initial minimum degree nodes should have to '
                             'be displayed')
    parser.add_argument('--group-granularity',
                        type=int,
                        default=2,
                        help='How coarse to group nodes (by folder)')
    parser.add_argument('--full-path',
                        action='store_true',
                        help='If set, shows the full path for nodes')
    parser.add_argument('--colors',
                        type=lambda p: colors.Colors(map(int, p.split(','))),
                        default='234, 82, 77',
                        help='The base RGB colors separated by commas')
    parser.add_argument('--color-variation',
                        type=int,
                        default=200,
                        help='The variation in RGB around the base colors')
    parser.add_argument('--color-alpha-min',
                        type=float,
                        default=0.7,
                        help='The minimum alpha value for colors')

    args = parser.parse_args(args)

    # Necessary for standard includes
    args.prefixes.append('')

    if not (0 <= args.color_alpha_min <= 1):
        raise RuntimeError('--color-alpha-min must be in interval [0, 1]')

    args.colors.variation = args.color_variation
    args.colors.alpha_min = args.color_alpha_min

    return args


def make_json(args, graph_json):
    '''
    Creates the JSON payload for the visualization.

    Args:
        args: The command line arguments.
        graph_json: The JSON dict from the graph.

    Returns:
        The payload.
    '''
    if args.json:
        print(graph_json)
        sys.exit(0)

    # Additional settings to configure the visualization
    settings = dict(initialDegree=args.min_degree)

    return dict(settings=settings, graph=graph_json)


def main():
    log = setup_logging()

    args = parse_arguments(sys.argv[1:])
    if args.verbose:
        log.setLevel(logging.DEBUG)
    log.debug('Received arguments: %s', args)

    include_graph = graph.Graph(args.relation,
                                args.full_path,
                                args.colors,
                                args.group_granularity)
    walk.walk(include_graph, args)

    if include_graph.is_empty:
        log.debug('Could not find a single node, exiting')
        sys.exit(-1)

    json = make_json(args, include_graph.to_json())

    with serve.Server(args.directory) as server:
        server.write(json)
        server.run(args.open, args.port)

    log.info('Shutting down')

if __name__ == '__main__':
    main()


================================================
FILE: ig/paths.py
================================================
'''Handles searching for the WWW path and copying operations.'''

import logging
import os
import shutil
import tempfile

log = logging.getLogger(__name__)


WWW = os.path.join(os.path.dirname(os.path.realpath(__file__)),
                   os.pardir,
                   'www')

if not os.path.exists(WWW):
    message = 'Could not find www directory for ig: {0}'
    raise EnvironmentError(message.format(WWW))


def create_directory(directory):
    '''
    (Maybe) creates a directory and copies the `www` folder to it.

    Args:
        directory: Optionally, an existing directory to copy to.

    Returns:
        The path of the possibly created directory.
    '''
    if directory is None:
        directory = tempfile.mkdtemp(prefix='ig-')
        log.debug('Created temporary directory %s', directory)

    # Has to not exist for copytree
    if os.path.exists(directory):
        shutil.rmtree(directory)

    shutil.copytree(WWW, directory)

    log.debug('Copied contents of www folder to %s', directory)

    return directory


================================================
FILE: ig/serve.py
================================================
'''The server that serves the web visualization.'''

import json
import logging
import os
import shutil
import socket
import webbrowser

from ig import paths

try:
    import socketserver
    import http.server as http
except ImportError:
    import SocketServer as socketserver
    import SimpleHTTPServer as http


log = logging.getLogger(__name__)


class Server(object):
    def __init__(self, directory):
        '''
        Constructor.

        Args:
            directory: The directory to serve from.
        '''
        self.delete_directory = directory is None
        self.directory = paths.create_directory(directory)

    def run(self, open_immediately, port):
        '''
        Serves the `www` directory.

        Args:
            open_immediately: Whether to open the web browser immediately
            port: The port at which to serve the graph
        '''
        os.chdir(self.directory)
        handler = http.SimpleHTTPRequestHandler
        handler.extensions_map.update({
            '.webapp': 'application/x-web-app-manifest+json',
        })

        server = socketserver.TCPServer(('', port), handler)
        server.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

        address = 'http://localhost:{0}/graph.html'.format(port)
        log.info('Serving at %s', address)

        if open_immediately:
            log.debug('Opening webbrowser')
            webbrowser.open(address)

        server.serve_forever()

    def write(self, payload):
        '''
        Writes the given JSON representation to the served location.

        Args:
            payload: The playlod to JSONify and store.
        '''
        path = os.path.join(self.directory, 'graph.json')
        with open(path, 'w') as graph_file:
            graph_file.write(json.dumps(payload, indent=4))

        log.debug('Wrote graph file to {0}'.format(path))

    def cleanup(self):
        if self.delete_directory:
            assert self.directory is not None
            shutil.rmtree(self.directory, ignore_errors=True)
            log.debug('Deleted directory %s', self.directory)

    def __enter__(self):
        return self

    def __exit__(self, error_type, error_value, traceback):
        self.cleanup()
        if error_type == KeyboardInterrupt:
            return True  # Supresses the exception
        # Any other exception is propagated up


================================================
FILE: ig/walk.py
================================================
from __future__ import print_function

import fnmatch
import logging
import os
import re
import sys

log = logging.getLogger(__name__)


INCLUDE_PATTERN = re.compile(r'^#include ["<](.*)[">]$')


def try_prefixes(path, prefixes):
    '''
    Tries to prepend a list of prefixes to a path to see if any exists.

    This is necessary to ensure that we have unique file paths, even if the same
    file is specified differently (sometimes relative to one directory, then to
    another etc.).

    Args:
        path: The path to append to the prefixes
        perfixes: The prefixes to prepend to the path

    Returns:
        The best possible path.
    '''
    for prefix in prefixes:
        full_path = os.path.realpath(os.path.join(prefix, path))
        if os.path.exists(full_path):
            return full_path

    return path


def get_includes(filename, prefixes):
    '''
    Parses out the includes from a file.

    Args:
        filename: The name of the file to get includes for
        prefixes: The prefixes under which to search for includes

    Returns:
        A list of includes for the file.
    '''
    includes = set()
    with open(filename) as source:
        for line in source:
            match = INCLUDE_PATTERN.match(line)
            if match is not None:
                full_path = try_prefixes(match.group(1), prefixes)
                includes.add(full_path)

    return includes


def glob(directory, pattern):
    '''
    Globs for files patterns under a directory.

    There is a `glob` module, but its recursive variant only works in Python3.
    This is a short DIY version of recursive globbing.

    Args:
        directory: The root directory
        pattern: The pattern to glob for

    Yields:
        Any matching files (with absolute paths).
    '''
    for root, _, filenames in os.walk(directory):
        for filename in fnmatch.filter(filenames, pattern):
            yield os.path.join(root, filename)


def walk(graph, args):
    '''
    Walks the file tree, populating the graph.

    Args:
        graph: The empty graph to populate
        args: The arguments passed to the command line

    Returns:
        The (possibly) populated graph.
    '''
    for directory in args.directories:
        # Swap pattern <-> filename loops if too inefficient
        for pattern in args.patterns:
            path = os.path.realpath(directory)
            for filename in glob(path, pattern):
                if os.path.isdir(filename):
                    log.debug('%s is a directory, skipping', filename)
                    continue
                includes = get_includes(filename, [path] + args.prefixes)
                graph.add(filename, includes)

    log.debug('Resulting graph: %s', repr(graph))


================================================
FILE: scripts/__init__.py
================================================


================================================
FILE: scripts/bump.py
================================================
"""Version-bumping script."""

from __future__ import print_function

import os.path
import re

def bump(match):
    """Bumps the version"""
    before, old_version, after = match.groups()
    major, minor, patch = map(int, old_version.split('.'))
    patch += 1
    if patch == 10:
        patch = 0
        minor += 1
        if minor == 10:
            minor = 0
            major += 1
    new_version = '{0}.{1}.{2}'.format(major, minor, patch)

    print('{0} => {1}'.format(old_version, new_version))

    return before + new_version + after


def main():
    root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    project = os.path.split(root)[-1]
    init_path = os.path.join(root, project, '__init__.py')
    with open(init_path) as source:
        original = source.read()
    pattern = re.compile(r'^(\s*__version__\s*=\s*[\'"])([^\'"]*)([\'"])', re.M)
    replaced = re.sub(pattern, bump, original)
    if replaced == original:
        raise RuntimeError('Could not find version!')
    with open(init_path, 'w') as destination:
        destination.write(replaced)


if __name__ == '__main__':
    main()


================================================
FILE: setup.cfg
================================================
[wheel]
universal=1


================================================
FILE: setup.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
setup.py script for setuptools.
"""

import re

from setuptools import setup, find_packages

with open('ig/__init__.py') as init:
    text = init.read()
    match = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', text, re.M)
    version = match.group(1)

with open('README.md') as readme:
    long_description = readme.read()

setup(
    name='ig-cpp',
    version=version,

    description='A tool to visualize include graphs for C++ projects',
    long_description=long_description,

    url="https://github.com/goldsborough/ig",
    license='MIT',

    author='Peter Goldsborough',
    author_email='peter@goldsborough.me',

    classifiers=[
        'Development Status :: 5 - Production/Stable',

        'Intended Audience :: Developers',
        'Topic :: Software Development',

        'License :: OSI Approved :: MIT License',

        'Programming Language :: Python',
        'Programming Language :: Python :: 2',
        'Programming Language :: Python :: 2.6',
        'Programming Language :: Python :: 2.7',
        'Programming Language :: Python :: 3',
        'Programming Language :: Python :: 3.2',
        'Programming Language :: Python :: 3.3',
        'Programming Language :: Python :: 3.4',
        'Programming Language :: Python :: 3.5'
    ],
    keywords='visualization C++ tool',
    packages=find_packages(exclude=['www']),
    include_package_data=True,
    package_data=dict(ig=[
        '../README.md',
        '../Makefile',
        '../www/*',
        '../www/sigma/*'
    ]),

    entry_points=dict(console_scripts=['ig = ig.main:main'])
)


================================================
FILE: www/graph.html
================================================
<!DOCTYPE html>
<html>
<head>
	<link href='http://fonts.googleapis.com/css?family=Lato:300,700' rel='stylesheet' type='text/css'>
	<link href='style.css' rel='stylesheet' type='text/css'>
</head>
<body>
	<div id="container">
		</style>
		<div id="graph-container"></div>
		<div id="control-pane">
			<h2 class="underline">filters</h2>

			<div>
				<h3>degree <span id="min-degree-val">0</span></h3>
          0
          <input id="min-degree" type="range" min="0" max="0" value="0">
          <span id="max-degree-value">0</span>
          <br>
			</div>
			<span class="line"></span>
			<div>
				<h3>groups</h3>
				<select id="node-group">
          <option value="" selected>All</option>
        </select>
			</div>
		</div>
	</div>
	<script src="sigma/sigma.min.js"></script>
	<script src="sigma/sigma.layout.forceAtlas2.min.js"></script>
	<script src="sigma/sigma.parsers.json.min.js"></script>
	<script src="sigma/sigma.plugins.dragNodes.min.js"></script>
	<script src="sigma/sigma.plugins.filter.min.js"></script>
	<script src="sigma/sigma.canvas.edges.curvedArrow.js"></script>
	<script src="sigma/sigma.canvas.labels.def.js"></script>
	<script src="graph.js"></script>
</body>
</html>


================================================
FILE: www/graph.js
================================================
'use strict';

const $ = id => document.getElementById(id);

function createFilter(instance, settings) {
  // Initialize the Filter API
  const filter = new sigma.plugins.filter(instance);

  const maximumDegree = visualizePane(instance.graph, filter);

  function applyMinDegreeFilter(value) {
    if (typeof value === 'object') {
      value = value.target.value;
    }

    $('min-degree').value = value;
    $('min-degree-val').textContent = value;

    filter
      .undo('min-degree')
      .nodesBy(node => instance.graph.degree(node.id) >= value, 'min-degree')
      .apply();
  }

  function applyGroupFilter(element) {
    let group = element.target[element.target.selectedIndex].value;
    filter
      .undo('node-group')
      .nodesBy(node => !group || node.group === group, 'node-group')
      .apply();
  }

  // for Chrome and FF
  $('min-degree').addEventListener('input', applyMinDegreeFilter);
  // for IE10+, that sucks
  $('min-degree').addEventListener('change', applyMinDegreeFilter);
  $('node-group').addEventListener('change', applyGroupFilter);

  let degree = settings.initialDegree;
  if (degree < 1) {
    // Assume it's a fraction.
    degree = Math.ceil(maximumDegree * degree);
  }
  applyMinDegreeFilter(Math.min(maximumDegree, degree));
}

function visualizePane(graph, filter) {
  let maximumDegree = 0, categories = {};

  // Collect the maximum degree and categories.
  graph.nodes().forEach(node => {
    maximumDegree = Math.max(maximumDegree, graph.degree(node.id));
    categories[node.group] = true;
  })

  // Set the slider values.
  $('min-degree').max = maximumDegree;
  $('max-degree-value').textContent = maximumDegree;

  // Set up the node group combo box.
  const nodeGroup = $('node-group');
  Object.keys(categories).forEach(function(group) {
    if (group.length === 0) return;
    let option = document.createElement('option');
    option.text = group;
    nodeGroup.add(option);
  });

  return maximumDegree;
}

function visualize(json) {
  console.log(json);

  const instance = new sigma({
    graph: json.graph,
    renderer: {
      container: 'graph-container',
      type: 'canvas',
      skipErrors: true,
      labelThreshold: 0,
      labelSize: 'proportional'
    }
  });

  instance.startForceAtlas2({
    worker: true,
    barnesHutOptimize: true,
    adjustSizes: true,
    slowDown: 20,
    strongGravityMode: true
  });

  createFilter(instance, json.settings);

  const drag = sigma.plugins.dragNodes(instance, instance.renderers[0]);
  drag.bind('startdrag', event => {
    if (instance.isForceAtlas2Running()) {
      instance.killForceAtlas2()
    }
  });
}

const xhr = new XMLHttpRequest();
xhr.open('GET', 'graph.json');
xhr.onreadystatechange = () => {
  if (xhr.readyState === XMLHttpRequest.DONE) {
    visualize(JSON.parse(xhr.responseText));
  }
}

xhr.send();


================================================
FILE: www/sigma/LICENSE.txt
================================================
Copyright (C) 2013-2014, Alexis Jacomy, http://sigmajs.org

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.


================================================
FILE: www/sigma/README.md
================================================
[![Build Status](https://travis-ci.org/jacomyal/sigma.js.svg)](https://travis-ci.org/jacomyal/sigma.js)

sigma.js - v1.2.0
=================

Sigma is a JavaScript library dedicated to graph drawing, mainly developed by [@jacomyal](https://github.com/jacomyal) and [@Yomguithereal](https://github.com/Yomguithereal).

### Resources

[The website](http://sigmajs.org) provides a global overview of the project, and the documentation is available in the [Github Wiki](https://github.com/jacomyal/sigma.js/wiki).

Also, the `plugins` and `examples` directories contain various use-cases that might help you understand how to use sigma.

### How to use it

To use it, clone the repository:

```
git clone git@github.com:jacomyal/sigma.js.git
```

To build the code:

 - Install [Node.js](http://nodejs.org/).
 - Install [gjslint](https://developers.google.com/closure/utilities/docs/linter_howto?hl=en).
 - Use `npm install` to install sigma development dependencies.
 - Use `npm run build` to minify the code with [Uglify](https://github.com/mishoo/UglifyJS). The minified file `sigma.min.js` will then be accessible in the `build/` folder.

Also, you can customize the build by adding or removing files from the `coreJsFiles` array in `Gruntfile.js` before applying the grunt task.

### Contributing

You can contribute by submitting [issues tickets](http://github.com/jacomyal/sigma.js/issues) and proposing [pull requests](http://github.com/jacomyal/sigma.js/pulls). Make sure that tests and linting pass before submitting any pull request by running the command `grunt`.

The whole source code is validated by the [Google Closure Linter](https://developers.google.com/closure/utilities/) and [JSHint](http://www.jshint.com/), and the comments are written in [JSDoc](http://en.wikipedia.org/wiki/JSDoc) (tags description is available [here](https://developers.google.com/closure/compiler/docs/js-for-compiler)).


================================================
FILE: www/sigma/sigma.canvas.edges.curvedArrow.js
================================================
;(function() {
  'use strict';

  sigma.utils.pkg('sigma.canvas.edges');

  /**
   * This edge renderer will display edges as curves with arrow heading.
   *
   * @param  {object}                   edge         The edge object.
   * @param  {object}                   source node  The edge source node.
   * @param  {object}                   target node  The edge target node.
   * @param  {CanvasRenderingContext2D} context      The canvas context.
   * @param  {configurable}             settings     The settings function.
   */
  sigma.canvas.edges.curvedArrow =
    function(edge, source, target, context, settings) {
    var color = edge.color,
        prefix = settings('prefix') || '',
        edgeColor = settings('edgeColor'),
        defaultNodeColor = settings('defaultNodeColor'),
        defaultEdgeColor = settings('defaultEdgeColor'),
        cp = {},
        size = edge[prefix + 'size'] || 1,
        tSize = target[prefix + 'size'],
        sX = source[prefix + 'x'],
        sY = source[prefix + 'y'],
        tX = target[prefix + 'x'],
        tY = target[prefix + 'y'],
        aSize = Math.max(size * 2.5, settings('minArrowSize')),
        d,
        aX,
        aY,
        vX,
        vY;

    cp = (source.id === target.id) ?
      sigma.utils.getSelfLoopControlPoints(sX, sY, tSize) :
      sigma.utils.getQuadraticControlPoint(sX, sY, tX, tY);

    if (source.id === target.id) {
      d = Math.sqrt(Math.pow(tX - cp.x1, 2) + Math.pow(tY - cp.y1, 2));
      aX = cp.x1 + (tX - cp.x1) * (d - aSize - tSize) / d;
      aY = cp.y1 + (tY - cp.y1) * (d - aSize - tSize) / d;
      vX = (tX - cp.x1) * aSize / d;
      vY = (tY - cp.y1) * aSize / d;
    }
    else {
      d = Math.sqrt(Math.pow(tX - cp.x, 2) + Math.pow(tY - cp.y, 2));
      aX = cp.x + (tX - cp.x) * (d - aSize - tSize) / d;
      aY = cp.y + (tY - cp.y) * (d - aSize - tSize) / d;
      vX = (tX - cp.x) * aSize / d;
      vY = (tY - cp.y) * aSize / d;
    }

    if (!color)
      switch (edgeColor) {
        case 'source':
          color = source.color || defaultNodeColor;
          break;
        case 'target':
          color = target.color || defaultNodeColor;
          break;
        default:
          color = defaultEdgeColor;
          break;
      }

    context.strokeStyle = color;
    context.lineWidth = size;
    context.beginPath();
    context.moveTo(sX, sY);
    if (source.id === target.id) {
      context.bezierCurveTo(cp.x2, cp.y2, cp.x1, cp.y1, aX, aY);
    } else {
      context.quadraticCurveTo(cp.x, cp.y, aX, aY);
    }
    context.stroke();

    context.fillStyle = color;
    context.beginPath();
    context.moveTo(aX + vX, aY + vY);
    context.lineTo(aX + vY * 0.6, aY - vX * 0.6);
    context.lineTo(aX - vY * 0.6, aY + vX * 0.6);
    context.lineTo(aX + vX, aY + vY);
    context.closePath();
    context.fill();
  };
})();


================================================
FILE: www/sigma/sigma.canvas.labels.def.js
================================================
;(function(undefined) {
  'use strict';

  if (typeof sigma === 'undefined')
    throw 'sigma is not declared';

  // Initialize packages:
  sigma.utils.pkg('sigma.canvas.labels');

  /**
   * This label renderer will just display the label on the right of the node.
   *
   * @param  {object}                   node     The node object.
   * @param  {CanvasRenderingContext2D} context  The canvas context.
   * @param  {configurable}             settings The settings function.
   */
  sigma.canvas.labels.def = function(node, context, settings) {
    var fontSize,
        prefix = settings('prefix') || '',
        size = node[prefix + 'size'];

    // Hardcoded: always show labels
    // if (size < settings('labelThreshold'))
    //   return;

    if (!node.label || typeof node.label !== 'string')
      return;

    // Hardcoded: Make labels proportionally sized
    fontSize = settings('labelSizeRatio') * size;

    // fontSize = (settings('labelSize') === 'fixed') ?
    //   settings('defaultLabelSize') :
    //   settings('labelSizeRatio') * size;

    context.font = (settings('fontStyle') ? settings('fontStyle') + ' ' : '') +
      fontSize + 'px ' + settings('font');
    context.fillStyle = (settings('labelColor') === 'node') ?
      (node.color || settings('defaultNodeColor')) :
      settings('defaultLabelColor');

    context.fillText(
      node.label,
      Math.round(node[prefix + 'x'] + size + 3),
      Math.round(node[prefix + 'y'] + fontSize / 3)
    );
  };
}).call(this);


================================================
FILE: www/style.css
================================================
body {
	color: #333;
	font-size: 14px;
	font-family: Lato, sans-serif;
}

#graph-container {
	top: 0;
	bottom: 0;
	left: 0;
	right: 0;
	position: absolute;
	background: rgb(249, 247, 237); /* A light beige */
}

#control-pane {
	top: 10px;
	right: 10px;
	position: absolute;
	width: 230px;
	background-color: rgb(249, 247, 237);
	box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
}

#control-pane > div {
	margin: 10px;
	overflow-x: auto;
}

.line {
	clear: both;
	display: block;
	width: 100%;
	margin: 0;
	padding: 12px 0 0 0;
	border-bottom: 1px solid rgba(0, 0, 0, 0.1);
	background: transparent;
}

h2, h3 {
	padding: 0;
	text-transform: uppercase;
}

h2.underline {
	background: #f4f0e4;
	margin: 0;
	border-radius: 2px;
	padding: 8px 12px;
	font-weight: 700;
}

input[type=range] {
	width: 160px;
}
Download .txt
gitextract_jv0db8cj/

├── .gitignore
├── LICENSE
├── MANIFEST.in
├── Makefile
├── README.md
├── ig/
│   ├── __init__.py
│   ├── colors.py
│   ├── graph.py
│   ├── main.py
│   ├── paths.py
│   ├── serve.py
│   └── walk.py
├── scripts/
│   ├── __init__.py
│   └── bump.py
├── setup.cfg
├── setup.py
└── www/
    ├── graph.html
    ├── graph.js
    ├── sigma/
    │   ├── LICENSE.txt
    │   ├── README.md
    │   ├── sigma.canvas.edges.curvedArrow.js
    │   └── sigma.canvas.labels.def.js
    └── style.css
Download .txt
SYMBOL INDEX (34 symbols across 8 files)

FILE: ig/colors.py
  function random_color (line 4) | def random_color(base, variation):
  class Colors (line 19) | class Colors(object):
    method __init__ (line 24) | def __init__(self, base_colors):
    method generate (line 35) | def generate(self):

FILE: ig/graph.py
  class Graph (line 11) | class Graph(object):
    method __init__ (line 21) | def __init__(self,
    method add (line 45) | def add(self, node_name, neighbors):
    method to_json (line 64) | def to_json(self):
    method is_empty (line 78) | def is_empty(self):
    method _get_or_add_node (line 85) | def _get_or_add_node(self, node_name):
    method _add_node (line 100) | def _add_node(self, node_name):
    method _add_edge (line 136) | def _add_edge(self, source, target):
    method __repr__ (line 162) | def __repr__(self):

FILE: ig/main.py
  function setup_logging (line 13) | def setup_logging():
  function parse_arguments (line 26) | def parse_arguments(args):
  function make_json (line 112) | def make_json(args, graph_json):
  function main (line 133) | def main():

FILE: ig/paths.py
  function create_directory (line 20) | def create_directory(directory):

FILE: ig/serve.py
  class Server (line 23) | class Server(object):
    method __init__ (line 24) | def __init__(self, directory):
    method run (line 34) | def run(self, open_immediately, port):
    method write (line 60) | def write(self, payload):
    method cleanup (line 73) | def cleanup(self):
    method __enter__ (line 79) | def __enter__(self):
    method __exit__ (line 82) | def __exit__(self, error_type, error_value, traceback):

FILE: ig/walk.py
  function try_prefixes (line 15) | def try_prefixes(path, prefixes):
  function get_includes (line 38) | def get_includes(filename, prefixes):
  function glob (line 60) | def glob(directory, pattern):
  function walk (line 79) | def walk(graph, args):

FILE: scripts/bump.py
  function bump (line 8) | def bump(match):
  function main (line 26) | def main():

FILE: www/graph.js
  function createFilter (line 5) | function createFilter(instance, settings) {
  function visualizePane (line 47) | function visualizePane(graph, filter) {
  function visualize (line 72) | function visualize(json) {
Condensed preview — 23 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (43K chars).
[
  {
    "path": ".gitignore",
    "chars": 1262,
    "preview": "# Own\n*.json\n\n\n# Created by https://www.gitignore.io/api/python\n\n### Python ###\n# Byte-compiled / optimized / DLL files\n"
  },
  {
    "path": "LICENSE",
    "chars": 1084,
    "preview": "The MIT License (MIT)\nCopyright (c) 2017 Peter Goldsborough\n\nPermission is hereby granted, free of charge, to any person"
  },
  {
    "path": "MANIFEST.in",
    "chars": 184,
    "preview": "include *.md\ninclude LICENSE\n\nrecursive-include www *\nrecursive-include * Makefile\nrecursive-exclude * __pycache__\nrecur"
  },
  {
    "path": "Makefile",
    "chars": 1253,
    "preview": ".PHONY: clean-pyc clean-build docs clean\n\nhelp:\n\t@echo \"clean - remove all build, test, coverage and Python artifacts.\"\n"
  },
  {
    "path": "README.md",
    "chars": 3365,
    "preview": "# :fireworks: ig\n\n<p align=\"center\">\n  <img src=\"extra/graph.gif\">\n  <br><br>\n  <code>ig</code> is a tool to interactive"
  },
  {
    "path": "ig/__init__.py",
    "chars": 244,
    "preview": "from datetime import date\n\n__title__ = 'ig'\n__url__ = \"https://github.com/goldsborough/ig\"\n__version__ = '0.1.9'\n__autho"
  },
  {
    "path": "ig/colors.py",
    "chars": 1072,
    "preview": "import random\n\n\ndef random_color(base, variation):\n    '''\n    Returns a random, bounded color value.\n\n    Args:\n       "
  },
  {
    "path": "ig/graph.py",
    "chars": 5068,
    "preview": "''' Defines the graph structure that stores the include relationships. '''\n\nimport logging\nimport os\nimport random\n\n\nlog"
  },
  {
    "path": "ig/main.py",
    "chars": 5302,
    "preview": "'''Entry point and command line parsing for ig.'''\n\nfrom __future__ import print_function\n\nimport argparse\nimport loggin"
  },
  {
    "path": "ig/paths.py",
    "chars": 1040,
    "preview": "'''Handles searching for the WWW path and copying operations.'''\n\nimport logging\nimport os\nimport shutil\nimport tempfile"
  },
  {
    "path": "ig/serve.py",
    "chars": 2378,
    "preview": "'''The server that serves the web visualization.'''\n\nimport json\nimport logging\nimport os\nimport shutil\nimport socket\nim"
  },
  {
    "path": "ig/walk.py",
    "chars": 2759,
    "preview": "from __future__ import print_function\n\nimport fnmatch\nimport logging\nimport os\nimport re\nimport sys\n\nlog = logging.getLo"
  },
  {
    "path": "scripts/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "scripts/bump.py",
    "chars": 1132,
    "preview": "\"\"\"Version-bumping script.\"\"\"\n\nfrom __future__ import print_function\n\nimport os.path\nimport re\n\ndef bump(match):\n    \"\"\""
  },
  {
    "path": "setup.cfg",
    "chars": 20,
    "preview": "[wheel]\nuniversal=1\n"
  },
  {
    "path": "setup.py",
    "chars": 1636,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\nsetup.py script for setuptools.\n\"\"\"\n\nimport re\n\nfrom setuptools impor"
  },
  {
    "path": "www/graph.html",
    "chars": 1197,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n\t<link href='http://fonts.googleapis.com/css?family=Lato:300,700' rel='stylesheet' type='t"
  },
  {
    "path": "www/graph.js",
    "chars": 2847,
    "preview": "'use strict';\n\nconst $ = id => document.getElementById(id);\n\nfunction createFilter(instance, settings) {\n  // Initialize"
  },
  {
    "path": "www/sigma/LICENSE.txt",
    "chars": 1083,
    "preview": "Copyright (C) 2013-2014, Alexis Jacomy, http://sigmajs.org\n\nPermission is hereby granted, free of charge, to any person "
  },
  {
    "path": "www/sigma/README.md",
    "chars": 1912,
    "preview": "[![Build Status](https://travis-ci.org/jacomyal/sigma.js.svg)](https://travis-ci.org/jacomyal/sigma.js)\n\nsigma.js - v1.2"
  },
  {
    "path": "www/sigma/sigma.canvas.edges.curvedArrow.js",
    "chars": 2858,
    "preview": ";(function() {\n  'use strict';\n\n  sigma.utils.pkg('sigma.canvas.edges');\n\n  /**\n   * This edge renderer will display edg"
  },
  {
    "path": "www/sigma/sigma.canvas.labels.def.js",
    "chars": 1507,
    "preview": ";(function(undefined) {\n  'use strict';\n\n  if (typeof sigma === 'undefined')\n    throw 'sigma is not declared';\n\n  // In"
  },
  {
    "path": "www/style.css",
    "chars": 798,
    "preview": "body {\n\tcolor: #333;\n\tfont-size: 14px;\n\tfont-family: Lato, sans-serif;\n}\n\n#graph-container {\n\ttop: 0;\n\tbottom: 0;\n\tleft:"
  }
]

About this extraction

This page contains the full source code of the goldsborough/ig GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 23 files (39.1 KB), approximately 10.0k tokens, and a symbol index with 34 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!