grocy-cli/grocy/cli.py

398 lines
13 KiB
Python

import click
from markdown import markdown
from html2text import html2text
from jinja2 import Environment, FileSystemLoader
from grocy.meta import Meta
from grocy.conf import Configuration
from grocy.util import Util
from grocy.recipe import Recipe
from grocy.table import Table
from grocy.entity import Entity
from grocy.stock import Stock
import yaml
from sys import exit
from os import path
import logging
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)
class GrocyGroup(click.Group):
def parse_args(self, ctx, args):
is_subcommand = True if args[0] in self.commands else False
if is_subcommand:
ctx.forward(self.commands[args[0]], args[1:])
super(GrocyGroup, self).parse_args(ctx, args)
@click.group()
def main():
cfg = Configuration()
if not cfg.exists:
no_config_msg = 'A config file was not found. A sample configuration file'
no_config_msg += ' will be created under {}. Is that Ok?'.format(cfg.CONFIG_DIR)
create_config_app_dir = click.confirm(no_config_msg)
user_cfg_options = {}
if create_config_app_dir:
user_cfg_options['logger_level'] = click.prompt('Enter logger level',
default='DEBUG')
user_cfg_options['logger_file_location'] = click.prompt('Enter location for logger',
default=path.expanduser('~/.config/grocy/log'))
user_cfg_options['api'] = click.prompt('Enter the grocy api url',
default='https://demo-en.grocy.info/api')
user_cfg_options['token'] = click.prompt('Enter the grocy token ',
default='')
user_cfg_options['col_format'] = click.prompt('Enter the col position for rendering tables',
default='col')
user_cfg_options['table_format'] = click.prompt('Enter the table format',
default='simple')
cfg.create(user_cfg_options)
else:
exit(0)
else:
cfg.load()
logging.basicConfig(level=cfg.logger_level, filename=cfg.logger_file_location)
@main.command()
@click.pass_context
def stock(ctx):
logger = logging.getLogger('cli.stock')
if ctx.invoked_subcommand is None:
try:
stock = Stock()
table = Table(stocks=stock.products)
click.echo(table.stock)
except Exception as e:
logger.error(e)
raise e
@main.group()
def product():
pass
@product.command()
@click.pass_context
@click.argument('product_id', required=False)
def view(ctx, product_id):
logger = logging.getLogger('cli.product')
try:
if product_id:
stock = Stock()
product = stock.get_product(product_id)
table = Table(entry=product)
click.echo(table.product)
else:
click.echo(ctx.get_help())
except Exception as e:
logger.error(e)
raise e
@product.command()
@click.option('--name', '-n', 'name')
@click.argument('product_id', required=False)
def edit(product_id, name):
logger = logging.getLogger('cli.product.edit')
try:
cfg = Configuration()
util = Util(cfg=cfg)
cfg.load()
loaded_template = cfg.templates('product/edit')
entity = Entity(name='products')
if product_id:
product = entity.get(id=product_id)
edited_product = click.edit(loaded_template.render(product))
edited_product.update()
elif name:
# Convert name args to a single string
string_name_arg = ' '.join(name) if type(name) == list else name
products = entity.find({'name': string_name_arg})
if products is None:
click.echo('Could not find product')
return
edited_products = click.edit(loaded_template.render(products=products), extension='.yml')
if edited_products is None:
return
parsed_edited_products = Util.load_yaml(edited_products)
schema = entity.schema
for index, edited_product in enumerate(parsed_edited_products):
edited_product['id'] = products[index]['id']
Util.verify_integrity(edited_product, schema)
entity.update(edited_product, id=products[index]['id'])
else:
raise click.BadParameter('Missing PRODUCT_ID or QUERY')
except Exception as e:
logger.error(e)
raise e
@product.command()
@click.option('--name', '-n', 'name')
@click.option('-t', 'template')
def list(name, template):
logger = logging.getLogger('cli.product.list')
cfg = Configuration()
cfg.load()
product_entity = Entity(name='products')
qu_entity = Entity(name='quantity_units')
location_entity = Entity(name='locations')
product_group_entity = Entity(name='product_groups')
try:
if name:
# Convert name args to a single string
string_name_arg = ' '.join(name) if type(name) == list else name
products = product_entity.find({'name': string_name_arg})
else:
products = product_entity.get()
entries = {}
entries['quantity_units'] = qu_entity.get()
entries['product_groups'] = product_group_entity.get()
entries['locations'] = location_entity.get()
entries['products'] = products
table = Table(entries=entries)
click.echo_via_pager(table.products)
except Exception as e:
logger.error(e)
raise e
@product.command()
@click.argument('product_id')
def browse(product_id):
logger = logging.getLogger('cli.product.browse')
try:
cfg = Configuration()
cfg.load()
url = '{domain}/product/{product_id}'.format(domain=cfg.domain, product_id=product_id)
click.launch(url, wait=False)
except Exception as e:
logger.error(e)
raise e
@product.command()
@click.option('-t', 'template')
def add(template):
logger = logging.getLogger('cli.product.add')
try:
cfg = Configuration()
cfg.load()
logger = logging.getLogger('cli.product.add')
meta = Meta()
# Get product_groups
meta.add(type='entities', name='product_groups')
# Get locations
meta.add(type='entities', name='locations')
# Get quantity_units
meta.add(type='entities', name='quantity_units')
if template:
loaded_template = cfg.templates(template)
else:
loaded_template = cfg.templates('product/add')
new_product = click.edit(loaded_template.render(grocy=meta.generate()), extension='.yml')
if not new_product:
return
parsed_new_product = yaml.safe_load(new_product)
if template == 'debug':
click.echo(parsed_new_product)
return
entity = Entity(name='products')
entity.create(parsed_new_product)
except Exception as e:
logger.error(e)
raise e
@main.group()
@click.pass_context
def recipe(ctx):
pass
@recipe.command()
@click.pass_context
@click.argument('recipe_id', required=False)
@click.option('-t', 'template')
def view(ctx, recipe_id, template):
logger = logging.getLogger('cli.recipes.view')
try:
cfg = Configuration()
cfg.load()
data = {'fields': {}}
if recipe_id:
entity = Entity(name='recipes')
data['fields'] = entity.get(id=recipe_id)
# Change html markup to plain text
html_markup_description = data['fields']['description']
plain_text_description = html2text(html_markup_description)
data['fields']['description'] = plain_text_description
recipe = Recipe(id=recipe_id)
data['fields']['fulfillment'] = recipe.get_fulfillments()
if template:
loaded_template = cfg.templates(template)
else:
loaded_template = cfg.templates('recipe/view')
entity = Entity(name='recipes')
click.echo(loaded_template.render(grocy=data))
else:
click.echo(ctx.get_help())
except Exception as e:
logger.error(e)
raise e
#@recipe.command()
#def list():
# logger = logging.getLogger('cli.recipe')
# try:
# entity = Entity(name='recipes')
# recipes = entity.get()
# recipe = Recipe(
# ingredient_reqs = recipe.get_ingredient_requirements()
# table = Table(recipes=recipes, recipes_reqs=recipes_reqs, ingredient_reqs=ingredient_reqs)
# click.echo(table.recipe)
# except Exception as e:
# logger.error(e)
# raise e
##
#
#@main.command()
#def shopping():
# logger = logging.getLogger('cli.shopping')
# try:
# entity = Entity(name='shopping_list')
# shopping_list = entity.get()
# table = Table(shopping_list=shopping_list)
# click.echo(table.shopping_list)
# except Exception as e:
# logger.error(e)
# raise e
#
#
#@main.command()
#@main.group()
#def ingredient():
# pass
#
#
#@ingredient.command('add')
#@click.argument('query')
#@click.argument('recipe_id')
#@click.option('--amount', '-a', 'amount', default=1, type=int)
#@click.option('--group', '-g', type=str, default='')
#@click.option('--variable-amount', '--va', 'variable_amount', default=None, type=float)
#@click.option('--in-stock', '--is', 'in_stock', default=False)
#@click.option('--disable-fulfillment', '--df', 'disable_fulfillment', default=False)
#@click.option('--note', '-n', 'note', multiple=True, default='', type=str)
#@click.option('--no-edit', '--ne', 'no_edit', default=False)
#def add(query, recipe_id, amount, group, variable_amount, in_stock, disable_fulfillment, note, no_edit):
# logger = logging.getLogger('cli.ingredient.add')
#
# try:
# loaded_template = TEMPLATE_LOADER.get_template('ingredient_add.yml')
# new_ingredient = {}
#
# entity = Entity(name='recipes')
# recipe = entity.get(id=recipe_id)
#
# if not recipe:
# raise click.BadParameter(message='recipe {id} does not exist', param='recipe_id',
# param_hint='Use `grocy recipes ls` to get a list of recipes')
#
# entity = Entity(name='products')
# product = entity.findOne(query)
# new_ingredient['product_id'] = product['id']
#
# new_ingredient['amount'] = amount
# new_ingredient['group'] = group
# new_ingredient['variable_amount'] = variable_amount
# new_ingredient['only_check_single_unit_in_stock'] = "1" if in_stock else "0"
# new_ingredient['not_check_stock_fulfillment'] = "1" if disable_fulfillment else "0"
# new_ingredient['note'] = note
#
# if not no_edit:
# new_ingredient = click.edit(loaded_template.render(new_ingredient))
#
# parsed_new_ingredient = yaml.safe_load(new_ingredient)
# entity = Entity(name='recipes_pos')
# #entity.create(parsed_new_ingredient)
#
# except Exception as e:
# logger.error(e)
# raise e
#
#
#@recipe.command('edit')
#@click.argument('recipe_id')
#def edit(recipe_id):
# logger = logging.getLogger('cli.recipe.edit')
# try:
# if recipe_id:
# entity = Entity(name='recipes')
# recipe = entity.get(id=recipe_id)
# loaded_template = TEMPLATE_LOADER.get_template('recipe_edit.yml')
# edited_recipe = click.edit(loaded_template.render(recipe))
# 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))
# raise e
#
#
#@recipe.command('create')
#@click.pass_context
#def create(ctx):
# logger = logging.getLogger('cli.recipe.create')
#
# try:
# recipe = Entity(name='recipes')
# loaded_template = TEMPLATE_LOADER.get_template('recipe_add.yml')
# new_recipe = click.edit(loaded_template.render())
# if new_recipe is not None:
# parsed_new_recipe = yaml.safe_load(new_recipe)
# parsed_new_recipe['description'] = markdown(parsed_new_recipe['description'])
# recipe.__dict__.update(parsed_new_recipe)
# recipe.create()
# except Exception as e:
# logger.error(e)
# raise e
#
#@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)
# click.echo(tasks)