PK!dada/__init__.pyPK!ػzjjdada/config/base.tomlkind-keys = [ 'category', 'template_dir' ] keys = [ 'title', 'shortcut', 'kind', 'dev-url', 'browser', 'pdf-viewer', # command 'build', 'serve', 'edit', 'edit-dir', 'edit-config', # path 'path', 'config-path', 'code-path', 'web-path', 'staging-path', 'edit-path', ] PK!::dada/config/categories.toml[web] taskrunner = 'grunt' [document] taskrunner = 'make'PK!qS$[[dada/config/kinds.backup.toml[invoice] category = 'document' taskrunner = 'make' root = '/Users/dominic/business/invoices/open' [invoice.makefile] template = 'documents/invoice/makefile' path = 'makefile' [mkdocs] category = 'web' server = 'mkdocs serve' local_url = 'http://127.0.0.1:8000' [bolt] category = 'web' mamp = true taskrunner = 'grunt' [bolt.project_config] path = '.project.toml' template = 'misc/project/bolt.yml' [harp] category = 'web' local_url = 'http://localhost:9000' taskrunner = 'grunt' [harp.reset] path = 'public/styles/_reset.styl' template = 'web/stylus/_reset.styl' [harp.index] path = 'public/index.jade' template = 'web/jade/index.jade' [harp.gruntfile] path = 'gruntfile.coffee' template = 'web/harp/gruntfile.coffee' [harp.packagejson] path = 'package.json' template = 'web/harp/package.json' PK!dada/config/kinds2.backup.tomlPK!ݚ77dada/projects.pyfrom dataclasses import dataclass from os import environ from pathlib import Path from shutil import copy, copytree, move from subprocess import run, Popen, DEVNULL from typing import List import toml from click import secho, confirm, echo, launch PROJECT_CONFIG_FILENAME = '.dadaproject.toml' KIND_CONFIG_FILENAME = 'config.toml' APP_CONFIG_DIR = Path(__file__).parent / 'config' APP_BASE_CONFIG_PATH = APP_CONFIG_DIR / 'base.toml' APP_KINDS_CONFIG_PATH = APP_CONFIG_DIR / 'kinds.toml' APP_KINDS_DIR = Path(__file__).parent / 'kinds' USER_CONFIG_DIR = environ.get('$XDG_CONFIG_HOME', Path.home() / '.config' / 'dada') if not USER_CONFIG_DIR.exists(): USER_CONFIG_DIR.mkdir(parents=True) USER_BASE_CONFIG_PATH = USER_CONFIG_DIR / 'base.toml' if not USER_BASE_CONFIG_PATH.exists(): USER_BASE_CONFIG_PATH.touch() USER_KINDS_DIR = USER_CONFIG_DIR / 'kinds' STARTER_SYMBOL = '==> ' PRIMARY_COLOR = 'yellow' class BaseConfig: with APP_BASE_CONFIG_PATH.open() as f: base_config = toml.load(f) if USER_BASE_CONFIG_PATH.exists(): with USER_BASE_CONFIG_PATH.open() as f: user_base_config = toml.load(f) else: USER_BASE_CONFIG_PATH.touch() user_base_config = {} @classmethod def keys(cls): app_config_keys = list(cls.base_config) user_config_keys = list(cls.user_base_config) return app_config_keys + user_config_keys @classmethod def get(cls, key: str): if key in cls.user_base_config: return cls.user_base_config[key] elif key in cls.base_config: return cls.base_config[key] @classmethod def print(cls): for key in cls.keys(): print(f'{key}: {cls.get(key)}') class Project: store = {} ready = False @classmethod def init(cls): config_paths = cls.project_config_paths() for path in config_paths: cls.add_from_config_path(path) cls.ready = True @classmethod def project_config_paths(cls): if not BaseConfig.get('dadapath'): print_warning('Could not look for projects because no project directories are defined.') return [] config_paths = [] dirs = [Path(dir).expanduser() for dir in BaseConfig.get('dadapath')] for dir in dirs: for config in dir.glob(f'*/{PROJECT_CONFIG_FILENAME}'): config_paths.append(config) return config_paths @staticmethod def local(): potential_project_dirs = (Path('.'), Path('..')) for dir in potential_project_dirs: config_file = dir / PROJECT_CONFIG_FILENAME if config_file.exists(): with config_file.open() as f: config_dict = toml.load(f) return Project(config_dict, path=dir) return None @classmethod def from_shortcut(cls, shortcut): if shortcut == 'local': return Project.local() else: return cls.store.get(shortcut, None) @classmethod def shortcuts(cls): return cls.store.keys() @classmethod def add_from_config_path(cls, config_path: Path): with config_path.open() as f: try: dictionary = toml.load(f) except Exception: print(f'Could not read config file {config_path}.') return project_path = config_path.parent project = Project(dictionary, project_path) shortcut = project.get_config('shortcut') Project.store[shortcut] = project @classmethod def all(cls): return Project.store.values() @classmethod def from_path(cls, path: Path): with open(path / PROJECT_CONFIG_FILENAME) as f: dictionary = toml.load(f) return Project(dictionary, path=path) def __init__(self, config_dictionary: dict, path: Path): if not config_dictionary: echo(f'Problem with reading project config from {path}') return else: self._config_dictionary = config_dictionary if 'kind' in config_dictionary: self.kind = Kind.get(config_dictionary['kind']) else: self.kind = 'undefined' self.path = path.absolute() if self.get_config('code-path'): self.code_path = self.path / self.get_config('code-path') else: self.code_path = self.path if self.get_config('web-path'): self.web_path = self.path / self.get_config('web-path') else: self.web_path = self.path self.config_path = self.path / PROJECT_CONFIG_FILENAME self.title = config_dictionary.get('title', None) def get_config(self, key): if key in self._config_dictionary: return self._config_dictionary[key] elif hasattr(self, 'kind') and self.kind and self.kind.get_config(key): return self.kind.get_config(key) elif BaseConfig.get(key): return BaseConfig.get(key) else: return None def build(self): build_commands = self.get_config('build').split(' ') if 'grunt' in build_commands: run(['dkill', 'grunt']) # todo: change Popen(build_commands, cwd=self.code_path, stdout=DEVNULL) def serve(self): if self.kind == 'harp': run(['dkill', 'harp']) # todo: change command = self.get_config('serve').split(' ') + [str(self.web_path)] url = self.get_config('de-url') print_info(f'Serving {self.title} with command', f'{command}', f'Development server at {url}') Popen(command, stdout=DEVNULL) def show_in_browser(self): old = self.get_config('dev-url') if not old: print_error('No local development URL defined. Cannot show page.') return elif old.startswith('htt'): url = old else: url = 'http://' + old browser = self.get_config('browser') if browser: run(['open', '-a', browser, url]) # todo: only macos else: launch(url) def open_document(self): document_path = self.path / 'document.pdf' print(document_path) if self.get_config('pdf-viewer'): command = ['open', '-a', self.get_config('pdf-viewer'), document_path] print_info(command) run(command) else: launch(str(document_path)) def update(self, key: str): component = self.kind.component(key) source = self.kind.template_dir / component.template destination = self.path / component.path if destination.exists(): if confirm('Do you want to override existing files?'): move(destination, destination.with_suffix('.backup' + destination.suffix)) else: print_info('Dada does nothing but watching the sun') exit() copy(source, destination) def upstream(self, key: str): component = self.kind.component(key) installed = self.path / component.path template = self.kind.template_dir / component.template if confirm(f'Do you want to override {template}?'): copy(template, template.with_suffix('.backup'+ template.suffix)) copy(installed, template) def edit(self): workspaces = list(self.path.glob('*.code-workspace')) edit = self.get_config('edit') if edit == 'code' and workspaces: path = workspaces[0] elif self.get_config('edit-path'): path = self.code_path / self.get_config('edit-path') else: path = self.code_path if edit: print_info(f'Edit {path} with configured editor {edit}') run([edit, path]) else: print_info(f'Edit {path} with standard editor') launch(str(path)) def start(self): self.edit() self.build() if self.kind.category == 'web': self.serve() self.show_output() def show_output(self): if self.kind.category == 'web': self.show_in_browser() elif self.kind.category == 'document': self.open_document() else: raise Exception('') @dataclass class Component(): path: Path template: Path @staticmethod def from_dict(dictionary: dict): return Component(**dictionary) class Kind: _store = {} ready = False @classmethod def init(cls): for kind_dir in USER_KINDS_DIR.iterdir(): key = kind_dir.name kind_config = kind_dir / KIND_CONFIG_FILENAME if kind_config.exists(): with kind_config.open() as f: config_dict = toml.load(f) else: config_dict = {} kind = Kind(key=key, config_dictionary=config_dict) cls._store[key] = kind cls.ready = True @staticmethod def get(key): return Kind._store.get(key, None) @classmethod def all(cls): return cls._store.values() def __init__(self, config_dictionary: dict, key: str): self.key = key self._config_dict = config_dictionary if 'category' in config_dictionary: category_key = config_dictionary['category'] self.category = Category.get(category_key) self.config_dir = USER_KINDS_DIR / key self.template_dir = self.config_dir / 'template' self.config_file = self.config_dir / KIND_CONFIG_FILENAME def __eq__(self, other): return str(self) == str(other) def __str__(self): return self.key def get_config(self, key): if key in self._config_dict: return self._config_dict[key] elif hasattr(self, 'category') and self.category.get_config(key): return self.category.get_config(key) else: return None def component(self, key): if 'components' in self._config_dict: if key in self._config_dict['components']: component_dict = self._config_dict['components'][key] return Component.from_dict(component_dict) else: return None def edit(self): edit = BaseConfig.get('dir-edit') if not edit: print_error('No dir-edit defined: command to edit directories') run([edit, self.config_dir]) class Category: _store = {} ready = False @classmethod def init(cls): with USER_BASE_CONFIG_PATH.open() as f: config = toml.load(f) categories_dict = config['categories'] for key, category_dict in categories_dict.items(): cls._store[key] = Category(dictionary=category_dict, key=key) cls.ready = True @classmethod def all(cls): return cls._store.values() @classmethod def get(cls, key): if key in cls._store: return cls._store[key] else: raise KeyError def __init__(self, dictionary: dict, key: str): self._config_dictionary = dictionary self.key = key def get_config(self, key): if key in self._config_dictionary: return self._config_dictionary[key] else: return None def __eq__(self, other): return str(self) == str(other) def __str__(self): return self.key def create_project(kind: str, title: str): kind = Kind.get(kind) root = Path('.') directory_name = title.replace(' ', '-') template_path = kind.template_dir project_path = root / directory_name copytree(template_path, project_path) if hasattr(kind, 'subkinds'): subkinds = kind.subkinds for key, config in subkinds.items(): subkind = Kind.get(key) source: Path = subkind.template_dir / config['template-path'] destination = project_path / config['project-path'] if source.is_dir(): copytree(source, destination) else: copy(source, destination) save_project_config(path=project_path / PROJECT_CONFIG_FILENAME, kind_key=kind.key, title=title) secho(STARTER_SYMBOL, nl=False, fg=PRIMARY_COLOR) echo(f'Dada created a new project of kind {kind} for you!') def save_project_config(path: Path, kind_key: str = None, title: str = None, shortcut: str = None): data = {} if kind_key: data['kind'] = kind_key if title: data['title'] = title if shortcut: data['shortcut'] = shortcut with path.open('w') as f: toml.dump(data, f) def print_info(*strings): for string in strings: secho(STARTER_SYMBOL, fg='green', nl=False) echo(string) def print_error(string, color='red'): secho(STARTER_SYMBOL + 'PROBLEM: ', fg=color, nl=False) echo(string) def print_warning(string): secho(STARTER_SYMBOL + ' Warning: ', fg='blue', nl=False) echo(string) def print_summary(object, additional_keys: List[str] = None, primary_color=PRIMARY_COLOR): secho('------------------------------------------------------------------------', fg=primary_color) if hasattr(object, 'title') and object.title: secho('title: ', nl=False, fg=primary_color) secho(object.title.upper(), bold=True) if additional_keys: keys = additional_keys + BaseConfig.get('keys') else: keys = BaseConfig.get('keys') for key in keys: if key in ['title']: continue snake_cased_key = key.replace('-', '_') if hasattr(object, snake_cased_key) and getattr(object, snake_cased_key) \ and not callable(getattr(object, snake_cased_key)): value = getattr(object, snake_cased_key) elif object.get_config(key): value = object.get_config(key) else: continue secho(f'{key.replace("_", "-")}: ', nl=False, fg=primary_color) echo(f'{value}') secho('------------------------------------------------------------------------', fg=primary_color) for cls in (Category, Kind, Project): if not cls.ready: cls.init() PK!dada/scripts/__init__.pyPK!i*)IIdada/scripts/dada.py from pathlib import Path from subprocess import run import toml from click import echo, group, prompt, argument, option, secho, edit, style, Choice, launch, pass_context from dada import projects from dada.projects import Project, Kind, create_project, PROJECT_CONFIG_FILENAME, BaseConfig, print_summary, Category, \ save_project_config, USER_BASE_CONFIG_PATH, print_error, print_info, print_warning from pick import pick PRIMARY_COLOR = 'blue' @group() def cli(): pass @cli.command() @argument('shortcut', required=False) def docs(shortcut): project = get_project(shortcut) documentation = project.get_config('documentation') if documentation: launch(documentation) else: print_warning('No documentation defined for {project}') @cli.group(invoke_without_command=True) @pass_context def config(context): if context.invoked_subcommand is None: local_config_path = Path('.', PROJECT_CONFIG_FILENAME) if local_config_path.is_file(): project = Project.local() config_path = project.path / PROJECT_CONFIG_FILENAME edit_command = BaseConfig.get('config-edit-command') run([edit_command, config_path]) else: print(f'Creating new project config file {PROJECT_CONFIG_FILENAME}') converter = lambda kind: str(kind) kind_key = prompt(style('Kind', fg=PRIMARY_COLOR), type=Choice(Kind.all()), default=None, value_proc=converter) title = prompt('Title?') shortcut = prompt('Shortcut?') save_project_config(path=local_config_path, title=title, kind_key=kind_key, shortcut=shortcut) @config.command() @argument('shortcut', required=False) def project(shortcut): local_config_path = Path('.', PROJECT_CONFIG_FILENAME) if shortcut or local_config_path.is_file(): project = get_project(shortcut) config_path = project.path / PROJECT_CONFIG_FILENAME edit_command = BaseConfig.get('config-edit-command') run([edit_command, config_path]) else: print(f'Creating new project config file {PROJECT_CONFIG_FILENAME}') converter = lambda kind: str(kind) kind_key = prompt(style('Kind', fg=PRIMARY_COLOR), type=Choice(Kind.all()), default=None, value_proc=converter) title = prompt('Title?') shortcut = prompt('Shortcut?') save_project_config(path=local_config_path, title=title, kind_key=kind_key, shortcut=shortcut) @config.command() def list(): BaseConfig.print() @config.command() @argument('key', required=False) def kind(key): if key: kind = Kind.get(key) else: project = Project.local() kind = project.kind editor = BaseConfig.get('edit-config') run([editor, kind.config_file]) @config.command() def base(): path = USER_BASE_CONFIG_PATH editor = BaseConfig.get('edit-config') if editor: run([editor, path]) else: launch(str(path)) @cli.command() @argument('shortcut', required=False) def output(shortcut): project = get_project(shortcut) project.show_output() @cli.command() @argument('kind') @option('--title', prompt=True) def new(kind, title): create_project(kind=kind, title=title) @cli.command() @argument('shortcut') def debug(shortcut): project = get_project(shortcut) print(project.__dict__) @cli.command() @argument('shortcut', required=False) def build(shortcut): project = get_project(shortcut) project.build() @cli.command() @argument('shortcut', required=False) def serve(shortcut): project = get_project(shortcut) project.serve() @cli.group(invoke_without_command=True) @pass_context def edit(context): if context.invoked_subcommand is None: Project.local().edit() @edit.command() @argument('shortcut', required=False) def project(shortcut): project = get_project(shortcut) project.edit() @edit.command() @argument('key', required=False) def kind(key): if key: kind = Kind.get(key) else: project = Project.local() kind = project.kind kind.edit() @cli.command() @argument('shortcut', required=False) def start(shortcut): project = get_project(shortcut) project.start() @cli.command() @argument('component') def update(component): project = Project.local() if not project: print_error('No project config file found in current directory') exit() print_info(f'Updating component "{component}" of project {project.title}') project.update(component) @cli.command() @argument('component') def upstream(component): project = Project.local() project.upstream(component) @cli.group(invoke_without_command=True) @pass_context def info(context): if context.invoked_subcommand is None: project = Project.local() print_summary(project) @info.command() @argument('shortcut', required=False) def project(shortcut): project = get_project(shortcut) print_summary(project) @info.command() @argument('key') def kind(key): kind = Kind.get(key) print_summary(kind, additional_keys=BaseConfig.get('kind-keys')) @cli.command() def init(): title = prompt(text='Title') shortcut = prompt(text='Shortcut') kind, _ = pick([str(kind) for kind in Kind.all()], 'Please choose kind of project:') data = { 'title': title, 'shortcut': shortcut, 'kind': kind } with Path(projects.PROJECT_CONFIG_FILENAME).open('w') as f: toml.dump(data, f) @cli.group(invoke_without_command=True) @pass_context def list(context): if context.invoked_subcommand is None: for project in Project.all(): shortcut = project.get_config('shortcut') if shortcut: echo(f'{shortcut}\t{project.title}') @list.command() def kinds(): for kind in Kind.all(): print(kind) @list.command() def categories(): for category in Category.all(): print(category) def get_project(shortcut): if shortcut: return Project.from_shortcut(shortcut) elif Project.local(): return Project.local() else: print_error(f'No project found') exit() PK!HBG&.%dada-0.2.0.dist-info/entry_points.txtN+I/N.,()JILIzP!=*9' PK!YC..dada-0.2.0.dist-info/LICENSEMIT License Copyright (c) 2018 Dominic Looser 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.PK!HڽTUdada-0.2.0.dist-info/WHEEL A н#Z;/"d&F[xzw@Zpy3Fv]\fi4WZ^EgM_-]#0(q7PK!H~dada-0.2.0.dist-info/METADATATr0)XN[0CI:C44"o47ށ7IX_KvWk"^D_:N Hv6?m6.\M}r¯?A@o8c7rQ9ZQL~ĆJbp=/0:W06G ɳ~\RYt$wZ7 La+̴ɱ0—"]ff!PY+LuQn[9 E1/怯ξr>)nu-yyVN!XxsӅvEQy|cdg,LNm垿X[)LJ)i n\Z6k$^R)8)f3sؠp^dX;;sE+ -2 c)FLeRWy$W PJc%%͜hHUhֳ(|AUѡH O>tNHMU,\&=N4%qln+lQl^eDj۷ cWN?|e͕_:IMmRQA8Aj4t.V0CŵMC=jt\୐˰ԩU :z=mvOd|P4MVY|!=,ưObWI¸'OhFQG(am b,/isݏ5RL@+̉Jһڊg o{ #hSbywi߇}vcZZn9ȶd9I *'mޭ[`A."ɻϫcV};Ϋ?tNy{l5,- dada/config/kinds.backup.tomlPK!dada/config/kinds2.backup.tomlPK!ݚ77dada/projects.pyPK!0>dada/scripts/__init__.pyPK!i*)IIf>dada/scripts/dada.pyPK!HBG&.%Vdada-0.2.0.dist-info/entry_points.txtPK!YC..JWdada-0.2.0.dist-info/LICENSEPK!HڽTU[dada-0.2.0.dist-info/WHEELPK!H~>\dada-0.2.0.dist-info/METADATAPK!HMB1L_dada-0.2.0.dist-info/RECORDPK a