Commit 07c73880 authored by ankraft's avatar ankraft Committed by Miguel Angel Reina Ortega
Browse files

Added json as possible output format for frontmatter. Added also option to...

Added json as possible output format for frontmatter. Added  also option to write frontmatter to a file
parent 8d1348f1
Loading
Loading
Loading
Loading
Loading
+72 −8
Original line number Diff line number Diff line
@@ -13,11 +13,12 @@ from __future__ import annotations

_print = print	# save the original print function

from typing import Tuple, Generator
from typing import Tuple, Generator, Type, Iterable
import argparse
from rich import markdown, print
import re, sys, yaml, os
import re, sys, yaml, os, json
from contextlib import contextmanager
import yaml


_frontMatter:dict = {}
@@ -46,6 +47,47 @@ def includeStack(filename:str) -> Generator [None, None, None]:
	_includeStack.pop()


#
# Meta class to create a YAML loader that does not resolve certain tags,
# e.g., timestamps. This is a bit of a hack since it changes the YAML loader
# after it has been created by removing the tagged resolvers.
#
# This is taken from https://stackoverflow.com/a/74695433
#

class YamlLimitedSafeLoader(type):
	"""	Meta YAML loader that skips the resolution of the specified YAML tags.

		This metaclass is the basis for creating our own YAML loaders with limited tag resolution.
	"""

	def __new__(
		cls, name, bases, namespace, do_not_resolve: Iterable[str]
	) -> Type[yaml.SafeLoader]:
		do_not_resolve = set(do_not_resolve)
		implicit_resolvers = {
			key: [(tag, regex) for tag, regex in mappings if tag not in do_not_resolve]
			for key, mappings in yaml.SafeLoader.yaml_implicit_resolvers.items()
		}
		return super().__new__(
			cls,
			name,
			(yaml.SafeLoader, *bases),
			{**namespace, "yaml_implicit_resolvers": implicit_resolvers},
		)


class YamlNoTimestampSafeLoader(metaclass=YamlLimitedSafeLoader, 
								do_not_resolve={"tag:yaml.org,2002:timestamp"}):
	"""	A safe YAML loader that leaves timestamps as strings.

		Note: 
			Extend this class to add support for additional tags by adding them to the `do_not_resolve` set.
			See "yaml.constructor" for more tags.
	"""
	pass


def expandPaths(lines:list[str], currentPath:str, childPath:str) -> list[str]:
	"""	Expand the paths in the markdown file. This means that all paths in links,
		images, and include statements are extended so that they would be valid paths
@@ -142,14 +184,15 @@ def processFrontMatter(lines:list[str], args:argparse.Namespace) -> Tuple[dict,
	for line in lines[1:]:
		if re.match(r'^---\s*', line):
			break
		frontMatterLines.append(line)
		frontMatterLines.append(line)	# Includes trailing newline

	# Remove the front matter from the lines
	lines = lines[len(frontMatterLines)+2:]

	# Parse the front matter as YAML
	try:
		return yaml.safe_load(''.join(frontMatterLines)), lines
		# return yaml.safe_load(''.join(frontMatterLines)), lines
		return yaml.load(''.join(frontMatterLines), YamlNoTimestampSafeLoader), lines
	except yaml.YAMLError as e:
		print(f'[red]Error parsing front matter: {e}')
		raise
@@ -239,6 +282,8 @@ def main(args=None):
	parser.add_argument('--render-markdown', '-md', dest='renderAsMarkdown', action='store_true',  help='render output as markdown')
	parser.add_argument('--process-frontmatter', '-fm', dest='outputFrontMatter', action='store_true',  help='output front matter only')
	parser.add_argument('--frontmatter-only', '-fmo', dest='onlyFrontMatter', action='store_true',  help='output only front matter')
	parser.add_argument('--frontmatter-out-file', '-fmf', dest='frontMatterOutFile', type=str, help='output front matter to a file')
	parser.add_argument('--frontmatter-out-format', '-fmt', dest='frontMatterOutFormat', type=str, choices=['yaml', 'json'], default='yaml', help='front matter output format (default: yaml)')
	parser.add_argument('--verbose', '-v', action='store_true', help='print debug information to stderr.')
	parser.add_argument('document', type=str, help='a markdown specification document to process')
	args = parser.parse_args()
@@ -257,15 +302,34 @@ def main(args=None):
	
	if args.outputFrontMatter or args.onlyFrontMatter:
		# Collect front matter information in the output
		if not args.onlyFrontMatter:
		if not args.onlyFrontMatter and not args.frontMatterOutFile:
			print('---')
		
		# The following is a workaround to keep the order of the dictionary
		# see https://stackoverflow.com/a/52621703
		yaml.add_representer(dict, lambda self, data: yaml.representer.SafeRepresenter.represent_dict(self, data.items()))

		# get output format and output file
		match args.frontMatterOutFormat:
			case 'json':
				if args.frontMatterOutFile:
					if args.verbose:
						print(f'[green]Writing front matter as JSON to {args.frontMatterOutFile}', file=sys.stderr)

					with open(args.frontMatterOutFile, 'w') as f:
						json.dump(_frontMatter, f, indent=2)
				else:
					print(json.dumps(_frontMatter, indent=2))
			case 'yaml':
				if args.frontMatterOutFile:
					if args.verbose:
						print(f'[green]Writing front matter as YAML to {args.frontMatterOutFile}', file=sys.stderr)
					with open(args.frontMatterOutFile, 'w') as f:
						f.write(yaml.dump(_frontMatter, default_flow_style=False))
				else:
					print(yaml.dump(_frontMatter, default_flow_style=False), end='')

		if not args.onlyFrontMatter:
		if not args.onlyFrontMatter and not args.frontMatterOutFile:
			print('---')

	if not args.onlyFrontMatter: