Skip to content
Snippets Groups Projects
Select Git revision
  • bc63438c23cae6d876b4c424cfbe5f37b3468475
  • master default protected
  • hpke
  • ppc-chacha-4core
  • delete-internal-name-mangling
  • master-updates
  • ppc-gcm
  • ppc-chacha-2core
  • refactor-ecc-mod
  • ppc-chacha-core
  • use-mpn_cnd-functions
  • optimize-ecc-invert
  • default-m4-quote-char
  • power-asm-wip
  • test-fat
  • chacha-3core-neon
  • x86_64-salsa20-2core
  • salsa20-2core-neon
  • bcrypt
  • arm-salsa20-chacha-vsra
  • test-shlib-dir
  • nettle_3.6_release_20200429
  • nettle_3.6rc3
  • nettle_3.6rc2
  • nettle_3.6rc1
  • nettle_3.5.1_release_20190627
  • nettle_3.5_release_20190626
  • nettle_3.5rc1
  • nettle_3.4.1_release_20181204
  • nettle_3.4.1rc1
  • nettle_3.4_release_20171119
  • nettle_3.4rc2
  • nettle_3.4rc1
  • nettle_3.3_release_20161001
  • nettle_3.2_release_20160128
  • nettle_3.1.1_release_20150424
  • nettle_3.1_release_20150407
  • nettle_3.1rc3
  • nettle_3.1rc2
  • nettle_3.1rc1
  • nettle_3.0_release_20140607
41 results

install-sh

Blame
  • Forked from Nettle / nettle
    Source project has a limited visibility.
    __main__.py 11.26 KiB
    """New, better, entry point."""
    
    import argparse
    import os
    import os.path
    from dataclasses import dataclass
    import hashlib
    from jinja2 import (
        Environment,
        # PackageLoader,
        FileSystemLoader,
    )
    import pathlib
    import json
    from typing import (
        Any,
        TypeVar,
        Callable,
        TypedDict,
        NotRequired,
    )
    from collections.abc import (
        Iterable,
        Sequence,
    )
    
    from .cache import Cache
    from .puppet.strings import puppet_strings
    from .format import (
        format_class,
        format_type_alias,
    )
    from .lookup import lookup, Ref
    from commonmark import commonmark
    
    jinja = Environment(
        loader=FileSystemLoader('templates'),
        autoescape=False,
    )
    
    parser = argparse.ArgumentParser(
            prog='puppet-doc configure',
            description='Sets up puppet doc')
    
    parser.add_argument('--env', action='store')
    
    args = parser.parse_args()
    
    env = args.env or '/etc/puppetlabs/code/modules'
    
    cache = Cache('/home/hugo/.cache/puppet-doc')
    
    
    @dataclass
    class ModuleEntry:
        """
        One entry in a module.
    
        Parameters:
            name - local name of the module, should always be the basename
                   of path
            path - Absolute path in the filesystem where the module can be
                   found.
            strings_output - output of `puppet strings`.
        """
    
        name: str
        path: str
        strings_output: bytes
        metadata: dict[str, Any]
    
        def file(self, path: str) -> str:
            """Return the absolute path of a path inside the module."""
            return os.path.join(self.path, path)
    
    
    def get_puppet_strings(path: str) -> bytes:
        """
        Run puppet string, but check cache first.
    
        The cache uses the contents of metadata.json as its key,
        so any updates without an updated metadata.json wont't be
        detected.
    
        Hashing the entire contents of the module was tested, but was to
        slow.
        """
        try:
            with open(os.path.join(path, 'metadata.json'), 'rb') as f:
                data = f.read()
                key = 'puppet-strings' + hashlib.sha1(data).hexdigest()
                if parsed := cache.get(key):
                    result = parsed
                else:
                    result = puppet_strings(path)
                    cache.put(key, result)
                return result
        except FileNotFoundError:
            # TODO actually run puppet strings again.
            # This is just since without a metadata.json we always get a
            # cache miss, which is slow.
            # return puppet_strings(path)
            return b''
    
            # try:
            #     with open(module.file('.git/FETCH_HEAD')) as f:
            #         st = os.stat(f.fileno())
            #         st.st_mtime
            # except FileNotFoundError:
            #     pass
    
    
    def get_modules(dir: str) -> list[ModuleEntry]:
        """
        Enumerate modules in directory.
    
        The directory should be the modules subdirectory of an environment,
        e.g. /etc/puppetlabs/code/environments/production/modules.
        """
        modules: list[ModuleEntry] = []
    
        for entry in sorted(list(os.scandir(dir)), key=lambda d: d.name):
            # TODO Logging
            # print('- entry', entry, file=sys.stderr)
            name = entry.name
            path = os.path.join(env, entry)
            strings_data = get_puppet_strings(path)
    
            try:
                with open(os.path.join(path, 'metadata.json')) as f:
                    metadata = json.load(f)
            except FileNotFoundError:
                metadata = {}
    
            modules.append(ModuleEntry(name, path, strings_data, metadata))
    
        return modules
    
    # --------------------------------------------------
    
    
    pathlib.Path('output').mkdir(exist_ok=True)
    
    
    T = TypeVar('T')
    U = TypeVar('U')
    
    
    def group_by(proc: Callable[[T], U], seq: Sequence[T]) -> dict[U, list[T]]:
        """
        Group elements in seq by proc.
    
        Return a dictionary mapping the result of proc onto lists of each
        element which evaluated to that key.
        """
        d: dict[U, list[T]] = {}
        for item in seq:
            key = proc(item)
            d[key] = (d.get(key) or []) + [item]
        return d
    
    
    def isprivate(entry: dict[str, Any]) -> bool:
        """
        Is the given puppet declaration marked private.
    
        Assumes input is a dictionary as returned by puppet strings, one
        of the entries in (for example) 'puppet_classes'.
    
        Currently only checks for an "@api private" tag.
        """
        if ds := entry.get('docstring'):
            if tags := ds.get('tags'):
                for tag in tags:
                    if tag.get('tag_name') == 'api' and \
                       tag.get('text') == 'private':
                        return True
        return False
    
    
    def setup_index(base: str, modules: list[ModuleEntry]) -> None:
        """Create the main index.html file."""
        template = jinja.get_template('index.html')
        with open(os.path.join(base, 'index.html'), 'w') as f:
            f.write(template.render(modules=modules))
    
    
    class IndexItem(TypedDict):
        """A single list entry in a module index page."""
    
        name: str
        file: str
        summary: NotRequired[str]
    
    
    class IndexSubcategory(TypedDict):
        """A subheading on an index page."""
    
        title: str
        list: Iterable[IndexItem]
    
    
    class IndexCategory(TypedDict):
        """A top heading on an index page."""
    
        title: str
        list: Iterable[IndexSubcategory]
    
    
    def class_index(class_list: list) -> IndexCategory:
        """Prepage class index list."""
        groups = group_by(isprivate, class_list)
    
        lst: list[IndexSubcategory] = []
    
        if publics := groups.get(False):
            # print(publics[0]['docstring']['tags'])
            sublist: list[IndexItem] = []
            for i in publics:
                name = i['name']
                summary = lookup(i) \
                    .ref('docstring') \
                    .ref('tags') \
                    .find(Ref('tag_name') == 'summary') \
                    .ref('text') \
                    .value()
    
                obj: IndexItem = {
                    'file': os.path.splitext(i['file'])[0],
                    'name': name,
                }
    
                if summary:
                    obj['summary'] = commonmark(summary)
    
                sublist.append(obj)
    
            lst.append({
                'title': 'Public Classes',
                'list': sublist,
            })
    
        if privates := groups.get(True):
            lst.append({
                'title': 'Private Classes',
                'list': ({'name': i['name'],
                          'file': os.path.splitext(i['file'])[0]}
                         for i in privates),
            })
    
        return {
            'title': 'Classes',
            'list': lst
        }
    
    
    def defined_types_index(defined_list: list) -> IndexCategory:
        """
        Prepare defined types index list.
    
        These are puppet types introduces by puppet code.
        Each only has one implemenattion.
        """
        groups = group_by(isprivate, defined_list)
    
        lst: list[IndexSubcategory] = []
    
        if publics := groups.get(False):
            lst.append({
                'title': 'Public Defined Types',
                'list': ({'name': i['name'],
                          'file': os.path.splitext(i['file'])[0]}
                         for i in publics),
            })
    
        if privates := groups.get(True):
            lst.append({
                'title': 'Private Defined Types',
                'list': ({'name': i['name'],
                          'file': os.path.splitext(i['file'])[0]}
                         for i in privates),
            })
    
        return {
            'title': 'Defined Types',
            'list': lst
        }
    
    
    def type_aliases_index(alias_list: list) -> IndexCategory:
        """Prepare type alias index list."""
        groups = group_by(isprivate, alias_list)
        lst: list[IndexSubcategory] = []
        if publics := groups.get(False):
            lst.append({
                'title': 'Public Type Aliases',
                'list': ({'name': i['name'],
                          'file': os.path.splitext(i['file'])[0]}
                         for i in publics),
            })
    
        if privates := groups.get(True):
            lst.append({
                'title': 'Private Type Aliases',
                'list': ({'name': i['name'],
                          'file': os.path.splitext(i['file'])[0]}
                         for i in privates),
            })
    
        return {
            'title': 'Type Aliases',
            'list': lst,
        }
    
    
    # def resource_types_index(resource_list: list) -> IndexCategory:
    #     """
    #     Prepare resource type index list.
    #
    #     These are the resource types introduced through ruby. Each can
    #     have multiple implementations.
    #     """
    #     return {}
    
    
    def setup_module_index(base: str, module: ModuleEntry, data: dict[str, Any]) -> None:
        """Create the index file for a specific module."""
        template = jinja.get_template('module_index.html')
    
        content = []
    
        content.append(class_index(data['puppet_classes']))
    
        data['data_types']
    
        content.append(type_aliases_index(data['data_type_aliases']))
    
        content.append(defined_types_index(data['defined_types']))
    
        data['resource_types']
        data['providers']
        data['puppet_functions']
        data['puppet_tasks']
        data['puppet_plans']
    
        with open(os.path.join(base, 'index.html'), 'w') as f:
            f.write(template.render(module_name=module.name,
                                    content=content))
    
    
    def setup_module(base: str, module: ModuleEntry) -> None:
        """
        Create all output files for a puppet module.
    
        Will generate a directory under base for the module.
        """
        path = os.path.join(base, module.name)
        pathlib.Path(path).mkdir(exist_ok=True)
        if not module.strings_output:
            return
        data = json.loads(module.strings_output)
    
        setup_module_index(path, module, data)
    
        for puppet_class in data['puppet_classes'] + data['defined_types']:
            # localpath = puppet_class['name'].split('::')
            localpath, _ = os.path.splitext(puppet_class['file'])
            dir = os.path.join(path, localpath)
            pathlib.Path(dir).mkdir(parents=True, exist_ok=True)
            # puppet_class['docstring']
            # puppet_class['defaults']
    
            # TODO option to add .txt extension (for web serverse which
            # treat .pp as application/binary)
            with open(os.path.join(dir, 'source.pp.txt'), 'w') as f:
                f.write(puppet_class['source'])
    
            with open(os.path.join(dir, 'source.json'), 'w') as f:
                json.dump(puppet_class, f, indent=2)
    
            # with open(os.path.join(dir, 'source.pp.html'), 'w') as f:
            #     f.write(format_class(puppet_class))
    
            with open(os.path.join(dir, 'index.html'), 'w') as f:
                template = jinja.get_template('code_page.html')
                f.write(template.render(content=format_class(puppet_class)))
    
            # puppet_class['file']
            # puppet_class['line']
    
        for type_alias in data['data_type_aliases']:
            localpath, _ = os.path.splitext(type_alias['file'])
            dir = os.path.join(path, localpath)
            pathlib.Path(dir).mkdir(parents=True, exist_ok=True)
    
            with open(os.path.join(dir, 'source.pp.txt'), 'w') as f:
                f.write(type_alias['alias_of'])
    
            with open(os.path.join(dir, 'source.json'), 'w') as f:
                json.dump(type_alias, f, indent=2)
    
            template = jinja.get_template('code_page.html')
            with open(os.path.join(dir, 'index.html'), 'w') as f:
                f.write(template.render(content=format_type_alias(type_alias)))
    
        os.system("cp -r static output")
    
        # data['data_type_aliases']
        # data['defined_types']
        # data['resource_types']
    
    
    def __main() -> None:
        modules = get_modules(env)
        setup_index('output', modules)
    
        for module in modules:
            # print(module)
            setup_module('output', module)
    
    
    if __name__ == '__main__':
        __main()