You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

356 lines
11 KiB

#! /usr/bin/env python3
import sys, os.path, textwrap
# Constants
META = '@'
INTRO_TEXT = '>'
INGREDIENT = '%'
INSTRUCTION = ':'
NOTE = '*'
CSS_BODY_BG = '#C7FCC8'
CSS_MAIN_BG = '#F9D5AF'
CSS_MAIN_ACCENT = '#F6F695'
CSS_FONT_COLOR = '#111'
CSS_FONT_SIZE = '15px'
CSS_FONT_FAMILY = 'monospace'
CSS_MAIN_MAX_WIDTH = '70ch'
CSS = '''<style>
body {{ padding: 2rem; font-size: {}; font-family: {}; background-color: {};}}
main {{width: 90%; max-width: {}; border: 1.5ch solid {}; background-color: {}; color: {}; margin: auto; padding: 4ch;}}
dd, li {{margin-bottom: 1rem;}}
dt {{font-weight: bold;}}
dl, h1 {{text-align: center;}}
h1 {{margin-bottom: 2.5rem;}}
h2 {{margin: 3rem 0 2rem;}}
dd {{margin-left: 0;}}
td:nth-child(2) {{padding: 0 2ch;}}
p {{font-family: serif; text-align: justify; text-indent: 4ch;}}
</style>'''
# Global vars
recipe = {'meta': {}, 'ingredients': [], 'instructions': [], 'notes': [], 'intro_text': []}
print_to_stdout = False
def add_meta(l):
data = l.split('\t')
if len(data) != 3:
return
key = data[1].strip().lower()
value = data[2].strip()
if key and value:
recipe['meta'][key] = value
def add_ingredient(l):
data = l.split('\t')
if len(data) != 4:
return
amount = data[1].strip()
measure = data[2].strip()
item = data[3].strip()
if amount and item:
recipe['ingredients'].append({'amount': amount, 'measure': measure, 'item': item})
def add_text(l, kind):
data = l.split('\t')
if len(data) != 2:
return
data[1] = data[1].strip()
if not data[1]:
return
if data[0] == INSTRUCTION:
recipe['instructions'].append(data[1])
elif data[0] == NOTE:
recipe['notes'].append(data[1])
elif data[0] == INTRO_TEXT:
recipe['intro_text'].append(data[1])
def process_file(path):
if os.path.exists(path):
with open(path, 'r') as f:
while True:
line = f.readline()
if not line:
break
if len(line) < 1:
continue
if line[0] == META:
add_meta(line)
elif line[0] == INGREDIENT:
add_ingredient(line)
elif line[0] == INSTRUCTION:
add_text(line, 'instructions')
elif line[0] == NOTE:
add_text(line, 'notes')
elif line[0] == INTRO_TEXT:
add_text(line, 'intro_text')
if not len(recipe['ingredients']) and not len(recipe['instructions']):
print("There was not enough recipe information found in the file", file=sys.stderr)
sys.exit(1)
else:
print("The requested path does not exist", file=sys.stderr)
sys.exit(1)
def output_html(path):
meta_count_needed = 0
out = [
'<html lang="en"><head>',
CSS.format(
CSS_FONT_SIZE,
CSS_FONT_FAMILY,
CSS_BODY_BG,
CSS_MAIN_MAX_WIDTH,
CSS_MAIN_ACCENT,
CSS_MAIN_BG,
CSS_FONT_COLOR),
'</head><body><main>']
if 'title' in recipe['meta']:
out.insert(1, '<title>Recipe: {}</title>'.format(recipe['meta']['title']))
out.append('<h1>{}</h1>'.format(recipe['meta']['title']))
meta_count_needed = 1
if len(recipe['meta']) > meta_count_needed:
out.append('<dl>')
for key in recipe['meta']:
if key == 'title':
continue
out.append('<dt>{}</dt>'.format(key.title()))
out.append('<dd>{}</dd>'.format(recipe['meta'][key]))
if len(recipe['meta']) > meta_count_needed:
out.append('</dl><hr>')
if len(recipe['intro_text']):
for ins in recipe['intro_text']:
out.append('<p>{}</p>'.format(ins))
if len(recipe['ingredients']):
out.append('<h2>Ingredients</h2>')
out.append('<table id="ingredients" role="presentation">')
for ingredient in recipe['ingredients']:
out.append('<tr><td>{}</td><td>{}</td><td>{}</td></tr>'.format(ingredient['amount'], ingredient['measure'], ingredient['item']))
out.append('</table>')
if len(recipe['instructions']):
out.append('<h2>Instructions</h2>')
out.append('<ol>')
for ins in recipe['instructions']:
out.append('<li>{}</li>'.format(ins))
out.append('</ol>')
if len(recipe['notes']):
out.append('<h2>Notes</h2>')
out.append('<ul>')
for note in recipe['notes']:
out.append('<li>{}</li>'.format(note))
out.append('</ul>')
out.append('</main></body></html>')
if print_to_stdout:
print_recipe(out)
else:
write_file(out,'.html', path)
def output_markdown(path):
meta_count_needed = 0
out = []
if 'title' in recipe['meta']:
out.append('# {}'.format(recipe['meta']['title']))
meta_count_needed = 1
if len(recipe['meta']) > meta_count_needed:
out.append('')
for key in recipe['meta']:
if key == 'title':
continue
out.append('**{}**: {}'.format(key.title(), recipe['meta'][key]))
out.append('---')
if len(recipe['intro_text']):
for ins in recipe['instructions']:
out.append('{}\n'.format(ins))
out.append('')
if len(recipe['ingredients']):
out.append('## Ingredients\n')
for ingredient in recipe['ingredients']:
out.append('- _{} {}_ {}'.format(ingredient['amount'], ingredient['measure'], ingredient['item']))
out.append('')
if len(recipe['instructions']):
out.append('## Instructions\n')
for ins in recipe['instructions']:
out.append('1. {}'.format(ins))
out.append('')
if len(recipe['notes']):
out.append('## Notes\n')
for note in recipe['notes']:
out.append('- {}'.format(note))
out.append('')
if print_to_stdout:
print_recipe(out)
else:
write_file(out, '.md', path)
def output_plaintext(path):
out = []
if 'title' in recipe['meta']:
out.append(recipe['meta']['title'].upper())
meta_count_needed = 1
out.append('')
if len(recipe['meta']) > meta_count_needed:
for key in recipe['meta']:
if key == 'title':
continue
out.append('{}: {}'.format(key.title(), recipe['meta'][key]))
out.append('')
if len(recipe['intro_text']):
for ln in recipe['intro_text']:
wrapped = textwrap.wrap(ln)
out.extend(wrapped)
out.append('')
out.append('')
if len(recipe['ingredients']):
out.append('== INGREDIENTS ==\n')
for ingredient in recipe['ingredients']:
out.append('{:>6} {:12} {}'.format(ingredient['amount'], ingredient['measure'], ingredient['item']))
out.append('')
if len(recipe['instructions']):
out.append('== Instructions ==\n')
for ind, ins in enumerate(recipe['instructions']):
wrapped = textwrap.wrap(ins)
out.append('Step {}:'.format(ind+1))
out.extend(wrapped)
out.append('')
out.append('')
if len(recipe['notes']):
out.append('== Notes ==\n')
for note in recipe['notes']:
wrapped = textwrap.wrap(note)
out.extend(wrapped)
out.append('')
out.append('')
out.append('')
if print_to_stdout:
print_recipe(out)
else:
write_file(out, '.txt', path)
def output_gemini(path):
meta_count_needed = 0
out = []
if 'title' in recipe['meta']:
out.append('# {}'.format(recipe['meta']['title']))
meta_count_needed = 1
out.append('')
if len(recipe['meta']) > meta_count_needed:
for key in recipe['meta']:
if key == 'title':
continue
out.append('{}: {}'.format(key.upper(), recipe['meta'][key]))
out.append('')
if len(recipe['intro_text']):
for ln in recipe['intro_text']:
out.append(ln)
out.append('')
out.append('')
if len(recipe['ingredients']):
out.append('## Ingredients\n')
for ingredient in recipe['ingredients']:
out.append('{:>6} {:12} {}'.format(ingredient['amount'], ingredient['measure'], ingredient['item']))
out.append('')
if len(recipe['instructions']):
out.append('## Instructions\n')
for ind, ins in enumerate(recipe['instructions']):
out.append('{:>2}. {}'.format(ind+1, ins))
out.append('')
out.append('')
if len(recipe['notes']):
out.append('## Notes\n')
for note in recipe['notes']:
out.append('* {}'.format(note))
out.append('')
out.append('')
if print_to_stdout:
print_recipe(out)
else:
write_file(out, '.gmi', path)
def build_path(recipe_path, new_suffix):
if 'title' in recipe['meta']:
fn = recipe['meta']['title'].replace(" ", "_").replace("\t", "_").lower() + new_suffix
root, trash = os.path.split(recipe_path)
return os.path.join(root, fn)
else:
filename, ext = os.path.splitext(recipe_path)
return recipe_path + new_suffix
def write_file(data_list, suffix, path):
new_path = build_path(path, suffix)
try:
with open(new_path, 'w') as f:
for line in data_list:
f.write(line)
f.write('\n')
print("File written: {}".format(new_path), file=sys.stderr)
except IOError:
print("Could not create file {} for writing".format(new_path), file=sys.stderr)
def print_recipe(dataList):
for line in dataList:
print(line)
def print_help():
help_text = '''recipe v0.5
Usage:
recipe [flag...] [rcp-filepath...]
Flags:
-H, --HTML Output html
-m, --markdown Output markdown
-t, --text Output plain text
-g, --gemini Output text/gemini
-s, --stdout Print the output to stdout instead of a file
-h, --help Print help and exit'''
print(help_text, file=sys.stderr)
sys.exit(1)
def parse_args():
args = sys.argv[1:]
if not len(args) or '-h' in args or '--help' in args:
print_help()
files = []
arg_list = []
if '-s' in args or '--stdout' in args:
global print_to_stdout
print_to_stdout = True
for arg in args:
if not arg or arg[0] == '-':
arg_list.append(arg)
else:
files.append(arg)
for ind, path in enumerate(files):
global recipe
print("{} of {}: {}".format(ind+1, len(files), path), file=sys.stderr)
recipe = {'meta': {}, 'ingredients': [], 'instructions': [], 'notes': [], 'intro_text': []}
process_file(path)
for arg in arg_list:
if arg in ['-H', '--HTML', '--html']:
output_html(path)
elif arg in ['-m', '--MD', '--md', '--markdown']:
output_markdown(path)
elif arg in ['-t', '--text', '--txt', '--plain']:
output_plaintext(path)
elif arg in ['-g', '--gemini', '--gmi']:
output_gemini(path)
elif arg in ['-s', '--stdout']:
continue
else:
print("Unknown flag: {}".format(arg), file=sys.stderr)
if __name__ == '__main__':
parse_args()