Exemple #1
0
def main():
    # completions
    argcomplete.autocomplete(parser)

    # parse args
    args = parser.parse_args()

    # initialize
    rp = RecipeParser()
    rs = RecipeSerializer()

    # read and parse recipe
    src = args.file.read()
    r = rp.parse(src)

    # scale recipe
    r = _process_scaling(r, args)

    # base url for late use
    base_url = URL(f'file://{os.path.abspath(args.file.name)}')

    # export linked recipes
    if args.export_links:
        _export_links(r, args, base_url, rp, rs)
        return

    # flatten recipe
    if args.flatten:
        r = _get_flattened_recipe(r, base_url=base_url, parser=rp)

    # create output depending on arguments
    print(_create_recipe_output(r, rs, args))
Exemple #2
0
def main():
    parser = argparse.ArgumentParser(description='Read and process recipemd recipes')

    parser.add_argument(
        'file', type=argparse.FileType('r', encoding='UTF-8'), help='A recipemd file'
    ).completer = FilesCompleter(allowednames='*.md')

    display_parser = parser.add_mutually_exclusive_group()
    display_parser.add_argument('-t', '--title', action='store_true', help='Display recipe title')
    display_parser.add_argument('-i', '--ingredients', action='store_true', help='Display recipe ingredients')

    parser.add_argument(
        '-f', '--flatten', action='store_true',
        help='Flatten ingredients and instructions of linked recipes into main recipe'
    )

    parser.add_argument(
        '-r', '--round', type=lambda s: None if s.lower() == 'no' else int(s), metavar='n', default=2,
        help='Round amount to n digits after decimal point. Default is "2", use "no" to disable rounding.'
    ).completer = ChoicesCompleter(('no', *range(0, decimal.getcontext().prec + 1)))

    scale_parser = parser.add_mutually_exclusive_group()
    scale_parser.add_argument('-m', '--multiply', type=str, help='Multiply recipe by N', metavar='N')
    scale_parser.add_argument(
        '-y', '--yield', type=str, help='Scale the recipe for yield Y, e.g. "5 servings"',
        metavar='Y', dest='required_yield'
    ).completer = _yield_completer

    # completions
    argcomplete.autocomplete(parser)

    # parse args
    args = parser.parse_args()

    # read and parse recipe
    src = args.file.read()
    rp = RecipeParser()
    r = rp.parse(src)

    # scale recipe
    r = _process_scaling(r, args)

    # flatten recipe
    if args.flatten:
        base_url = URL(f'file://{os.path.abspath(args.file.name)}')
        r = _get_flattened_ingredients_recipe(r, base_url=base_url, parser=rp)

    # create output depending on arguments
    if args.title:
        print(r.title)
    elif args.ingredients:
        for ingr in r.leaf_ingredients:
            print(_ingredient_to_string(ingr, rounding=args.round))
    else:
        rs = RecipeSerializer()
        print(rs.serialize(r, rounding=args.round))
Exemple #3
0
def get_filtered_recipes(args):
    rp = RecipeParser()
    result = []
    for path in glob.glob(os.path.join(args.folder, '**/*.md'), recursive=True):
        try:
            with open(path, 'r', encoding='UTF-8') as file:
                recipe = rp.parse(file.read())
            tags = recipe.tags
            if evaluate(args.filter, tags):
                result.append((recipe, os.path.relpath(path, args.folder)))
        except Exception as e:
            if not args.no_messages:
                print(f"An error occurred, skipping {os.path.relpath(path, args.folder)}: {e.args[0]}", file=sys.stderr)
    return result
Exemple #4
0
def _yield_completer(prefix, action, parser, parsed_args):
    try:
        src = parsed_args.file.read()
        r = RecipeParser().parse(src)

        parsed_yield = RecipeParser.parse_amount(prefix)
        if parsed_yield is None or parsed_yield.factor is None:
            return [RecipeSerializer._serialize_amount(a) for a in r.yields]

        return [RecipeSerializer._serialize_amount(Amount(parsed_yield.factor, a.unit)) for a in r.yields
                if parsed_yield.unit is None or (a.unit is not None and a.unit.startswith(parsed_yield.unit))]
    except Exception as e:
        print(e)
        return []
Exemple #5
0
def main():
    parser = argparse.ArgumentParser(
        description='Read and process recipemd recipes')

    parser.add_argument('file', type=open, help='A recipemd file')
    display_parser = parser.add_mutually_exclusive_group()
    display_parser.add_argument('-t',
                                '--title',
                                action='store_true',
                                help='Display recipe title')
    display_parser.add_argument('-i',
                                '--ingredients',
                                action='store_true',
                                help='Display recipe ingredients')

    scale_parser = parser.add_mutually_exclusive_group()
    scale_parser.add_argument('-m',
                              '--multiply',
                              type=str,
                              help='Multiply recipe by N',
                              metavar='N')
    scale_parser.add_argument('-y',
                              '--yield',
                              type=str,
                              help='Scale the recipe for yield Y',
                              metavar='Y',
                              dest='required_yield')

    args = parser.parse_args()

    src = args.file.read()

    rp = RecipeParser()
    r = rp.parse(src)

    if args.required_yield is not None:
        required_yield = RecipeParser.parse_amount(args.required_yield)
        if required_yield is None or required_yield.factor is None:
            print(f'Given yield is not valid', file=sys.stderr)
            exit(1)
        matching_recipe_yield = next(
            (y for y in r.yields if y.unit == required_yield.unit), None)
        if matching_recipe_yield is None:
            if required_yield.unit is None:
                matching_recipe_yield = Amount(Decimal(1))
            else:
                print(
                    f'Recipe "{r.title}" does not specify a yield in the unit "{required_yield.unit}". The '
                    f'following units can be used: ' +
                    ", ".join(f'"{y.unit}"' for y in r.yields),
                    file=sys.stderr)
                exit(1)
        r = multiply_recipe(
            r, required_yield.factor / matching_recipe_yield.factor)
    elif args.multiply is not None:
        multiply = RecipeParser.parse_amount(args.multiply)
        if multiply is None or multiply.factor is None:
            print(f'Given multiplier is not valid', file=sys.stderr)
            exit(1)
        if multiply.unit is not None:
            print(f'A recipe can only be multiplied with a unitless amount',
                  file=sys.stderr)
            exit(1)
        r = multiply_recipe(r, multiply.factor)

    if args.title:
        print(r.title)
    elif args.ingredients:
        for ingr in r.leaf_ingredients:
            print(_ingredient_to_string(ingr))
    else:
        rs = RecipeSerializer()
        print(rs.serialize(r))
Exemple #6
0
import urllib.request
from pprint import pprint
from commonmark import Parser
from commonmark.node import NodeWalker
from yarl import URL

from recipemd._vendor.commonmark_extensions.plaintext import CommonMarkToCommonMarkRenderer
from recipemd.data import RecipeParser, RecipeSerializer
from unidecode import unidecode

root_path = os.path.realpath('.')

commonmark_parser = Parser()
commonmark_renderer = CommonMarkToCommonMarkRenderer()

recipe_parser = RecipeParser()
recipe_serializer = RecipeSerializer()


def urlopen_user_agent(url: str):
    request = urllib.request.Request(
        url, None, {
            'User-Agent':
            'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.7) Gecko/2009021910 Firefox/3.0.7'
        })
    return urllib.request.urlopen(request)


def download_recipe_images(recipe_path):
    with open(recipe_path, 'r', encoding='UTF-8') as file:
        ast = commonmark_parser.parse(file.read())
Exemple #7
0
def parser():
    return RecipeParser()
Exemple #8
0
def serve(base_folder_path) -> Flask:
    app = Flask(__name__)

    app.jinja_env.trim_blocks = True
    app.jinja_env.lstrip_blocks = True

    recipe_parser = RecipeParser()
    recipe_serializer = RecipeSerializer()

    _cleaner = Cleaner(meta=True,
                       embedded=True,
                       links=True,
                       style=True,
                       processing_instructions=True,
                       scripts=True,
                       javascript=True,
                       frames=True,
                       remove_unknown_tags=True,
                       page_structure=True,
                       remove_tags=['body'])

    @app.context_processor
    def pjax_processor():
        def get_root_template():
            if "X-PJAX" in request.headers:
                return "pjax.html"
            return "structure.html"

        return dict(get_root_template=get_root_template)

    @app.template_filter()
    def markdown_to_cleaned_html(markdown):
        unsafe_html_str = commonmark.commonmark(markdown)
        # remove wrapping div
        # https://stackoverflow.com/questions/21420922/how-to-use-cleaner-lxml-html-without-returning-div-tag
        unsafe_doc = document_fromstring(unsafe_html_str)
        clean_doc = _cleaner.clean_html(unsafe_doc)
        clean_html_str = "\n".join(
            tostring(ch, encoding="unicode") for ch in clean_doc)
        return Markup(clean_html_str)

    @app.template_filter()
    def get_recipe_title(child_name: str, parent_path) -> str:
        absolute_path = os.path.join(base_folder_path, parent_path, child_name)
        if os.path.isdir(absolute_path):
            return Markup('<em>Folder</em>')
        try:
            with open(absolute_path, 'r', encoding='UTF-8') as f:
                recipe = recipe_parser.parse(f.read())
            # TODO markdown to html
            return recipe.title
        except RuntimeError:
            return Markup('<strong>Invalid recipe!</strong>')

    @app.template_filter()
    def serialize_ingredients(ingredients: List[Ingredient]):
        return ("\n".join(
            recipe_serializer._serialize_ingredient(i, rounding=2)
            for i in ingredients)).strip()

    @app.route('/')
    @app.route('/<path:relative_path>')
    def download_file(relative_path=''):
        absolute_path = os.path.join(base_folder_path, relative_path)

        if os.path.isdir(absolute_path):
            if not absolute_path.endswith('/'):
                return redirect(f'/{relative_path}/', code=302)

            child_paths = [(ch, os.path.isdir(os.path.join(absolute_path, ch)))
                           for ch in os.listdir(absolute_path)]
            child_paths = [
                (ch, is_dir) for ch, is_dir in child_paths
                if not ch.startswith('.') and (is_dir or ch.endswith('.md'))
            ]
            child_paths = [
                f'{ch}/' if not ch.endswith('/') and is_dir else ch
                for ch, is_dir in child_paths
            ]
            child_paths = sorted(child_paths)
            return render_template("folder.html",
                                   child_paths=child_paths,
                                   path=relative_path)

        if not absolute_path.endswith('.md'):
            return send_from_directory(base_folder_path, relative_path)

        with open(absolute_path, 'r', encoding='UTF-8') as f:
            required_yield_str = request.args.get('yield', '1')
            required_yield = recipe_parser.parse_amount(required_yield_str)
            if required_yield is None:
                required_yield = Amount(factor=Decimal(1))

            src = f.read()

            try:
                recipe = recipe_parser.parse(src)
            except Exception as e:
                return render_template("markdown.html",
                                       markdown=src,
                                       path=relative_path,
                                       errors=[e.args[0]])

            errors = []
            try:
                recipe = get_recipe_with_yield(recipe, required_yield)
            except StopIteration:
                errors.append(
                    f'The recipe does not specify a yield in the unit "{required_yield.unit}". '
                    f'The following units can be used: ' +
                    ", ".join(f'"{y.unit}"' for y in recipe.yields))
            except Exception as e:
                errors.append(str(e))

            return render_template(
                "recipe.html",
                recipe=recipe,
                yields=recipe_serializer._serialize_yields(recipe.yields,
                                                           rounding=2),
                tags=recipe_serializer._serialize_tags(recipe.tags),
                units=list(set(y.unit for y in recipe.yields)),
                default_yield=recipe_serializer._serialize_amount(
                    recipe.yields[0]) if recipe.yields else "1",
                path=relative_path,
                errors=errors)

    return app
Exemple #9
0
import sys
import glob
import os
import re
from pprint import pprint

from recipemd.data import RecipeParser
from unidecode import unidecode

root_path = '.'
rp = RecipeParser()

tt = str.maketrans({
    "ä": "ae",
    "ö": "oe",
    "ü": "ue",
    "Ä": "Ae",
    "Ö": "Oe",
    "Ü": "Ue",
    "ß": "ss",
})

for path in glob.glob(os.path.join(root_path, '**/*.md'), recursive=True):
    try:
        with open(path, 'r', encoding='UTF-8') as file:
            recipe = rp.parse(file.read())
            filename = recipe.title
            filename = filename.translate(tt)
            filename = unidecode(filename)
            filename = re.sub(r'[^a-zA-Z0-9]+', '_', filename)
            filename = re.sub(r'^_+|_+$', '', filename)