import click from markdown import markdown from dataclasses import asdict, replace from jinja2 import Environment, FileSystemLoader from uuid import uuid4 from grocy import RestService from grocy.models import (Stock, Battery, Shopping, Recipe) from pkg_resources import iter_entry_points import yaml from sys import exit from shutil import copy from os import path, chmod, makedirs from taskw import TaskWarriorShellout import logging logger = logging.getLogger(__name__) APP_NAME = 'grocy-cli' SAMPLE_CONFIG_FILE = 'sample.config.yml' TMP_DIR = '/tmp/grocy' CONFIG_FILE = 'config.yml' CONFIG_DIR = click.get_app_dir(APP_NAME) PROJ_DIR = path.join(path.dirname(path.realpath(__file__))) TEMPLATE_LOADER = Environment(loader = FileSystemLoader('templates'), trim_blocks=True, lstrip_blocks=True) def __validate_token(cfg): # Validate token if hasattr(cfg, 'token') or cfg['token'] is None: click.echo('No token was found. Please add your token to the config file', err=True) logger.error('No token was found. Please add your token') exit(1) def __create_config_file(): user_cfg_file = path.join(CONFIG_DIR, CONFIG_FILE) sample_cfg_file = path.join(PROJ_DIR, '..', SAMPLE_CONFIG_FILE) if not path.exists(CONFIG_DIR): click.echo('Config {} director does not exist, create...'.format(CONFIG_DIR)) makedirs(CONFIG_DIR) copy(sample_cfg_file, user_cfg_file) click.echo('Copying sample config to {}'.format(sample_cfg_file, user_cfg_file)) chmod(user_cfg_file, 0o664) return user_cfg_file @click.group() @click.pass_context def main(ctx): cfg = get_config_file() # Get logger if 'logger' in cfg: log_cfg = cfg['logger'] else: log_cfg = path.join(click.get_app_dir(APP_NAME), 'grocy.log') log_level = 'DEBUG' if 'level' not in log_cfg else log_cfg['level'] log_filename = 'log' if 'file_location' not in log_cfg else log_cfg['file_location'] logging.basicConfig(level=log_level, filename=log_filename) cfg['logger'] = log_cfg __validate_token(cfg) ctx.ensure_object(dict) ctx.obj['cfg'] = cfg def get_config_file(): no_config_msg = 'A config file was not found' no_config_msg+= ' and will be created under {}. Is that Ok?' no_config_msg = no_config_msg.format(click.get_app_dir(APP_NAME)) cfg_file = path.join(click.get_app_dir(APP_NAME), 'config.yml') if not path.exists(cfg_file): create_config_app_dir = click.confirm(no_config_msg) if create_config_app_dir: cfg_file = __create_config_file() exit(0) fd = open(cfg_file) parse_cfg_file = yaml.safe_load(fd) return parse_cfg_file @main.command() @click.pass_context def stock(ctx): cfg = ctx.obj['cfg'] stock = Stock(**cfg) stock_entries = stock.get_entries() click.echo(stock_entries) @main.command() @click.pass_context def shopping(ctx): cfg = ctx.obj['cfg'] shopping = Shopping(**cfg) shopping_list = shopping.get_list() click.echo(shopping_list) @main.group() @click.pass_context def recipe(ctx): if ctx.invoked_subcommand is None: cfg = ctx.obj['cfg'] receipe = Recipe(id=None, **cfg) recipes = receipe.get_list() click.echo(recipes) @recipe.command('edit') @click.argument('recipe_id') @click.pass_context def edit(ctx, recipe_id): cfg = ctx.obj['cfg'] logger = cfg['logger'] try: if recipe_id: recipe = Recipe(id=recipe_id, **cfg) recipe.get(include_products=True) loaded_template = TEMPLATE_LOADER.get_template('recipe.yml') edited_recipe = click.edit(loaded_template.render(recipe.toJSON())) if edited_recipe is not None: parsed_edited_recipe = yaml.safe_load(edited_recipe) parsed_edited_recipe['description'] = markdown(parsed_edited_recipe['description']) recipe.__dict__.update(parsed_edited_recipe) recipe.update() except Exception as e: logger.error(e) logger.error('Could not edit recipe {}'.format(recipe_id)) #@main.command() #@click.pass_context #def chore(ctx): # cfg = ctx.obj['cfg'] # # chore = Chore(**cfg) # chores = chore.get_list() # click.echo(chores) # #@main.command() #@click.pass_context #def task(ctx): # cfg = ctx.obj['cfg'] # # task = Task(**cfg) # tasks = task.get_list() # click.echo(tasks)