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 Original line Diff line number Diff line
@@ -13,11 +13,12 @@ from __future__ import annotations


_print = print	# save the original print function
_print = print	# save the original print function


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




_frontMatter:dict = {}
_frontMatter:dict = {}
@@ -46,6 +47,47 @@ def includeStack(filename:str) -> Generator [None, None, None]:
	_includeStack.pop()
	_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]:
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,
	"""	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
		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:]:
	for line in lines[1:]:
		if re.match(r'^---\s*', line):
		if re.match(r'^---\s*', line):
			break
			break
		frontMatterLines.append(line)
		frontMatterLines.append(line)	# Includes trailing newline


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


	# Parse the front matter as YAML
	# Parse the front matter as YAML
	try:
	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:
	except yaml.YAMLError as e:
		print(f'[red]Error parsing front matter: {e}')
		print(f'[red]Error parsing front matter: {e}')
		raise
		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('--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('--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-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('--verbose', '-v', action='store_true', help='print debug information to stderr.')
	parser.add_argument('document', type=str, help='a markdown specification document to process')
	parser.add_argument('document', type=str, help='a markdown specification document to process')
	args = parser.parse_args()
	args = parser.parse_args()
@@ -257,15 +302,34 @@ def main(args=None):
	
	
	if args.outputFrontMatter or args.onlyFrontMatter:
	if args.outputFrontMatter or args.onlyFrontMatter:
		# Collect front matter information in the output
		# Collect front matter information in the output
		if not args.onlyFrontMatter:
		if not args.onlyFrontMatter and not args.frontMatterOutFile:
			print('---')
			print('---')
		
		
		# The following is a workaround to keep the order of the dictionary
		# The following is a workaround to keep the order of the dictionary
		# see https://stackoverflow.com/a/52621703
		# see https://stackoverflow.com/a/52621703
		yaml.add_representer(dict, lambda self, data: yaml.representer.SafeRepresenter.represent_dict(self, data.items()))
		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='')
					print(yaml.dump(_frontMatter, default_flow_style=False), end='')


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


	if not args.onlyFrontMatter:
	if not args.onlyFrontMatter: