Package: metapost
This Python package helps developers parseMetapostfile.Metapost allows meta data to be appended to markdown files, such astitlefor a post; oron_indexwhich denotes whether the post should be placed on the index page -- in a most humanreadableandmanageableway.Use CaseMetapost is meant to help developers efficiently manipulate mock data when building CMS service.
You can put a bunch of markdown files (with meta info) in a folder,
then useMetaPostReaderto parse them into a list of dictionaries,
which can easily be imported into database via ORM modules.How Metapost WorksMetapostfile is a standardMarkdownfile with.mdextension.Except one thing -Metapostis expected to star with acode block, which will be parsed as metas of the file.The content of thecode blockshould be neither json nor YAML, it's a special but easy to learn format, which is meant to optimize the readability and manageability.This package provides necessary tools for manipulating Metapost files, such as reading and exporting.The markdown parser in this project is based on John Gruber'sMarkdownMetapost is extremely simple and powerful. The examples below will demonstrate how to implement it.ExamplesQuickstartIn this section, we are going to demonstrate how to parse a Metapost(markdown) file into useful formats that can interact with our application. Be aware that the meta content is neither json nor YAML, it's a key-value pair separated by a colon.First things first, install the package via PyPIpip install metapostAssuming we have a MetaPost(.md file) article like this. The file starts with a code block containing metas, which is embraced by triple consecutive backtick(```).` ` `
title: Example post
ranking: 999
on_top: true
keywords: ["markdown", "meta", "microblog"]
work_hours: 35.6
` ` `

--- ↑↑↑    Meta Block    ↑↑↑ ---
--- ↓↓↓ Markdown Content ↓↓↓ ---

Be careful we use ` ` ` here just because we can't escape backtick in the code block of github. 

You should always use ``` in your markdown file.Now, we are going to parse the file withMetaPostReaderin our package:# import packagefrommetapostimportMetaPostReader# set file pathpath=".\myMetaPostFilepath.md"# instantiate MetaPostReadermtpr=MetaPostReader()mtpr.set_strict_mode(False)# read and export to python dictresult=mtpr.read_file(path).to_dict()print(result)The out put will be:[{
    "meta":{"title":"Example post",
            "rank":"999",
            "on_top":"true",
            "keywords":'["markdown", "meta", "microblog"]',
            "work_hours": "35.6"}
    "html":"<p>......</p>"
}]Well, it's not that impressive, right? All values are parsed as string instead of boolean, integer or other useful datatype.Set Meta ConfigsSetmeta_configson theMetaPostReadercan help us parse value precisely. We can also setrequiredanddf_valon it to declare whether a meta is required or have default value.The five lines of meta in the source markdown file are mapping to five acceptable datatype in the meta, they are string(str), boolean(bool), integer(int),  float(float) and json(json).Let's set meta configs and parse it againfrommetapostimportMetaPostReaderpath="./ExampleMetaPost.md"mtpr=MetaPostReaser()# add_meta_config()# key: str, datatype: str = "str", required: bool = True, df_val: Any = Nonemtpr.add_meta_config("title","str",True)mtpr.add_meta_config("ranking","int",False,0)mtpr.add_meta_config("on_top","bool",False,False)mtpr.add_meta_config("keywords","json",False)mtpr.add_meta_config("work_hours","float",True)result=mtpr.to_dict()print(result)Now, we can get the content of the file in the format of pythondict:[{
    "meta":{"title":"Example post",
            "rank":999,
            "on_top":true,
            "keywords":["markdown", "meta", "microblog"],
            "work_hours":35.6,
            ......}
    "html":"<p>......</p>"
}]If the parameterrequiredisTruewhile your file does not contain that meta key,
the reader will report error.We can compare these three lines of code which clearly demonstrate howrequiredanddf_valworksfrommetapostimportMetaPostReadermtpr=MetaPostReader()# meta required, default value not necessarymtpr.add_meta_config("title","str",True)# meta not required, it is defaulted to be 999 if not providedmtpr.add_meta_config("ranking","int",False,999)# meta not required, no default valuemtpr.add_meta_config("keywords","json",False)Another thing worth mention is thatMetaPostReaderadopt elastic approach to parseboolmeta. That's to say, values likeFalse,false,No,n,0, etc., will be parsed toFalseas long as the meta config expects a boolean. The rule iscase insensitiveand is also applies toTrueparsing.Set Strict ModeWe can set the propertystrict_modeonMetaPostReader. The defaulted value isTrue, which means the reader won't parse metas that are not defined in configs. Set it toFalseif you wish to parse as many valid metas as possible.frommetapostimportMetaPostReadermtpr=MetaPostReader()# strict mode onMetaPostReader().set_strict_mode(True)# strict mode offMetaPostReader().set_strict_mode(False)Default MetaExcept for metas we have defined in config, you can find some default ones. They are_content_markdown_,_filepath_,_filename_and_last_update_. These are meant to provide more information about the source file.Read Multiple Files & Read from DirectoryMetaPostReadercan also read and stack multiple files, then export them all at once.
All the files loaded will be stored inMetaPostReader.mtp_list.frommetapostimportMetaPostReader# read three files into MetaPostReadermtpr=MetaPostReader()mtpr.read_file("/mocks/file1.md")mtpr.read_file("/mocks/file2.md")mtpr.read_file("/mocks/file3.md")# now, the length will be 3print(len(mtpr.mtp_list))# parse all the posts, this returns a list of dictionarymy_metas=mtpr.to_dict()# set reset to True if you wish to reset list before readmtpr.read_file("/mocks/file4.md",reset=True)# now, the length will be 1print(len(mtpr.mtp_list))You can also read all files under a specific directory.MetaPostReader.read_dir()will automatically load all files with.mdextensions.
If you wish to load all.mdfiles in the directory tree, just setwalktoTrue.This feature is especially useful when importing mock data via migrations in web development.frommetapostimportMetaPostReader# Read .md from directorymy_mock_dirpath="./mocks"mtpr=MetaPostReader()mtpr.read_file(my_mock_dirpath,walk=True)# get all parsed dataposts=mtpr.to_dict()That's all! If you have any advice, feel free to contact me via email(thittalee@gmail.com) or github.
The following API document provides some short description of all the (expected) public methods.
Happy coding!API DocumentInitMetaPostReader.__init__(self)Create a MetaPostReaderAdd Meta Configs.add_meta_cfg(self, key: str, datatype: str = "str", required: bool = True, df_val: Any = None)Add meta config.datatypecan bestr,bool,int,floatandjson.Methods of Read.read_dir(self, dirpath: str, reset: bool = False, walk: bool = False)Read all the.mdfiles underdirpathinto reader. SetresettoTrueif you wish to clean the former loading. SetwalktoTrueif you wish to read all the.mdfiles in the directory tree..read_file(self, filepath: str, reset: bool = False)Read one single file fromfilepath. SetresettoTrueif you wish to clean the former loading..read_text(self, source_text: str, reset: bool = False)Read from a string. SetresettoTrueif you wish to clean the former loading.Methods of Output.to_dict(self) -> List[dict]Export a list of dict from data read . Usedict["meta"]anddict["html"]to fetch values..to_json(self) -> strExport a string of json from data read..to_meta(self) -> List[dict]Export a list of dict of meta from data read.s.to_html(self) -> List[str]Export a list of string of html from data read.Methods of Setter.set_strict_mode(self, strict_mode: bool)Set.strict_modetoTrueif you wish to parse metas defined in configs only..set_markdown_extensions(self, extensions: list)Customize your markdown extensions. VisitMarkdownfor more information.