{"id":18009836,"url":"https://github.com/kekyo/marktheripper","last_synced_at":"2025-03-26T14:31:30.036Z","repository":{"id":47301715,"uuid":"515933572","full_name":"kekyo/MarkTheRipper","owner":"kekyo","description":"Fantastic faster generates static site comes from simply Markdowns.","archived":false,"fork":false,"pushed_at":"2022-10-26T14:21:53.000Z","size":3047,"stargazers_count":6,"open_issues_count":8,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-21T23:22:04.452Z","etag":null,"topics":["customizable","dotnet","easy","extendable","faster","markdown","minimum","static-site-generator"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kekyo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-07-20T10:21:25.000Z","updated_at":"2024-09-15T08:15:20.000Z","dependencies_parsed_at":"2023-01-20T10:33:45.380Z","dependency_job_id":null,"html_url":"https://github.com/kekyo/MarkTheRipper","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kekyo%2FMarkTheRipper","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kekyo%2FMarkTheRipper/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kekyo%2FMarkTheRipper/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kekyo%2FMarkTheRipper/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kekyo","download_url":"https://codeload.github.com/kekyo/MarkTheRipper/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245670722,"owners_count":20653409,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["customizable","dotnet","easy","extendable","faster","markdown","minimum","static-site-generator"],"created_at":"2024-10-30T02:11:12.601Z","updated_at":"2025-03-26T14:31:29.120Z","avatar_url":"https://github.com/kekyo.png","language":"C#","readme":"# MarkTheRipper\n\n![MarkTheRipper](Images/MarkTheRipper.100.png)\n\nMarkTheRipper - Fantastic faster generates static site comes from simply Markdowns.\n\n[![Project Status: WIP – Initial development is in progress, but there has not yet been a stable, usable release suitable for the public.](https://www.repostatus.org/badges/latest/wip.svg)](https://www.repostatus.org/#wip)\n\n## NuGet\n\n| Package  | NuGet                                                                                                                |\n|:---------|:---------------------------------------------------------------------------------------------------------------------|\n| MarkTheRipper | [![NuGet MarkTheRipper](https://img.shields.io/nuget/v/MarkTheRipper.svg?style=flat)](https://www.nuget.org/packages/MarkTheRipper) |\n| MarkTheRipper.Core | [![NuGet MarkTheRipper.Core](https://img.shields.io/nuget/v/MarkTheRipper.Core.svg?style=flat)](https://www.nuget.org/packages/MarkTheRipper.Core) |\n| MarkTheRipper.Engine | [![NuGet MarkTheRipper.Engine](https://img.shields.io/nuget/v/MarkTheRipper.Engine.svg?style=flat)](https://www.nuget.org/packages/MarkTheRipper.Engine) |\n\n## CI\n\n| main                                                                                                                                                                 | develop                                                                                                                                                                       |\n|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| [![MarkTheRipper CI build (main)](https://github.com/kekyo/MarkTheRipper/workflows/.NET/badge.svg?branch=main)](https://github.com/kekyo/MarkTheRipper/actions?query=branch%3Amain) | [![MarkTheRipper CI build (develop)](https://github.com/kekyo/MarkTheRipper/workflows/.NET/badge.svg?branch=develop)](https://github.com/kekyo/MarkTheRipper/actions?query=branch%3Adevelop) |\n\n----\n\n[![Japanese language](Images/Japanese.256.png)](https://github.com/kekyo/MarkTheRipper/blob/main/README_ja.md)\n\n## What is this?\n\nTODO: Eventually this document will be converted by MarkTheRipper itself.\n\nMarkTheRipper is a very simple and fast static site generator\nthat allows you to write content in markdown.\nThe main intended use is for blog sites,\nbut we have eliminated the need for complex structures and tools anyway,\nas if you were writing an article on GitHub Gist likely.\n\nIf you already have .NET 6.0 installation, you can install it simply:\n\n```bash\ndotnet tool install -g MarkTheRipper\n```\n\nAlternatively, [you can download a binary distribution](https://github.com/kekyo/MarkTheRipper/releases) that is compatible with .NET Framework 4.71 or higher.\n\n* Important as of 0.4.0, installation with dotnet tooling has a problem where the incorrect version is installed [(being fixed.)](https://github.com/kekyo/MarkTheRipper/issues/27)\n\nThen at first time, you will need to run:\n\n```bash\n$ mtr init mininum\n```\n\nWill generate a sample files under your current directory as follows\n(Don't be afraid! It's only TWO FILES with a few lines of content\nand almost no extra definitions!)\n\n* `contents` directory: `index.md`,\n  It is a content (post) file written by markdown.\n* `layouts` directory: `page.html`,\n  When the site is generated, the markdown is converted to HTML and inserted into this layout file.\n\nThat's it! Just to be sure, let's show you what's inside:\n\n### contents/index.md\n\n```markdown\n---\ntitle: Hello MarkTheRipper!\ntags: foo,bar\n---\n\nThis is sample post.\n\n## H2\n\nH2 body.\n\n### H3\n\nH3 body.\n```\n\n### layouts/page.html\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"{lang}\"\u003e\n\u003chead\u003e\n    \u003cmeta charset=\"utf-8\" /\u003e\n    \u003cmeta name=\"keywords\" content=\"{tags}\" /\u003e\n    \u003ctitle\u003e{title}\u003c/title\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n    \u003cheader\u003e\n        \u003ch1\u003e{title}\u003c/h1\u003e\n        \u003cp\u003eCategory:{foreach category.breadcrumbs} {item.name}{end}\u003c/p\u003e\n        \u003cp\u003eTags:{foreach tags} {item.name}{end}\u003c/p\u003e\n        \u003cp\u003e\n            {foreach (take (older self) 1)}\u003ca href=\"{relative item.path}\"\u003eOlder\u003c/a\u003e{end}\n            {foreach (take (newer self) 1)}\u003ca href=\"{relative item.path}\"\u003eNewer\u003c/a\u003e{end}\n        \u003c/p\u003e\n    \u003c/header\u003e\n    \u003chr /\u003e\n    \u003carticle\u003e\n        {contentBody}\n    \u003c/article\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\nIf you look at the content, you can probably guess what happens.\nMarkTheRipper simply converts the keywords and body into HTML and inserts it into the layout.\nTherefore when customizing a layout,\ncommon HTML/CSS/JavaScript techniques can be applied as is,\nand there are no restrictions.\n\n(MarkTheRipper is written in .NET, but the user does not need to know about .NET)\n\nLet's generate the site as is. Generating a site is very easy:\n\n```bash\n$ mtr\n````\n\nIf your directory structure is the same as the sample, just run ``mtr`` to generate the site.\nSite generation is multi-threaded and multi-asynchronous I/O driven,\nso it is fast even with a large amount of content.\nBy default, the output is under the `docs` directory.\nIn this example, the `contents/index.md` file is converted and placed in the `docs/index.html` file.\n\nYou will then immediately see a preview in your default browser:\n\n![minimum image](Images/minimum.png)\n\nSite generation will delete all files in the `docs` directory and generate them again each time.\nIf you manage the entire directory with Git,\nyou can commit the site including the `docs` directory.\nThen you can check the differences of the actual generated files.\nYou can also push it straight to `github.io` and easily publish your site!\n\nThere are no restrictions on the file names of markdown files or the subdirectories in which they are placed.\nIf there are files with the extension `.md` under the `contents` directory,\nit does not matter what kind of subdirectories they are placed in,\nunder what kind of file names, or even if there are many of them.\nAll `.md` files will be converted to `.html` files, keeping the structure of the subdirectories.\n\nFiles with non `.md` extensions will simply be copied to the same location.\nAdditional files, such as pictures for example,\ncan be placed as you wish to manage them,\nand you can write markdowns with relative paths to point to them.\n\n### Daily operation\n\nMarkTheRipper automatically recognizes all markdown files placed under the `contents` directory.\nIf you want to write an article,\nyou can simply create a markdown file in any directory you like and start writing it.\n\nIs even this operation a hassle? Yes, of course we know.\nSo, there is an easier way to create and place markdown files so you can concentrate on writing articles.\n\n```bash\n$ mtr new\n```\n\nThis will generate a template markdown file directly under the `contents` directory,\nbased on the current date naming, and automatically open the default markdown editor.\nCategories will be discussed in more detail later,\nbut if you want to place articles in categories, you can use:\n\n```bash\n$ mtr new foo/bar/baz\n```\n\nWill place the article in the nested category \"foo\", \"bar\" and \"baz\" in the category.\n\n----\n\n## A more practical sample\n\nThe samples generated by `mtr init minimum` are too simple (minimum is not a bad thing!),\nbut in case you want to see some more customized examples,\nSeveral samples are built in as standard.\n\nTwo more examples are built in:\n\n```bash\n$ mtr init sidebar\n$ mtr init standard\n$ mtr init rich\n````\n\nYou can specify the sample `sidebar`, `standard` or `rich`.\nThe following features are available:\n\n|Name|Description|\n|:----|:----|\n|`sidebar`|Added sidebar navigation on `minimum` sample by CSS flex. If you want to design the whole thing from scratch, it is convenient because it does not contain any extra definitions.|\n|`standard`|Looks like GitHub's code block design (but does not have syntax highlighting). Uses [bootstrap.js 5.0](https://getbootstrap.jp/).|\n|`rich`|Syntax highlighting by [prism.js](https://prismjs.com/). Uses bootstrap.js 5.0.|\n\n![standard image](Images/standard.png)\n\n![rich image](Images/rich.png)\n\nNo need to worry. These samples have also been implemented\nwith the utmost care to keep the layout code to a simplest.\nThey are easy to understand and even HTML beginners\ncan start customizing with these samples.\n\n----\n\n## Layout details\n\nMarkTheRipper layouts are very simple, yet flexible and applicable enough for your needs.\nThe layouts provide all keyword substitutions by referencing a \"Metadata dictionary\".\n\nHere is an example of a layout substitution.\n\n----\n\n### Keyword Substitution\n\nThe simplest example is a simple keyword substitution. Suppose you define the following layout:\n\n```html\n\u003ctitle\u003e{title}\u003c/title\u003e\n```\n\nThis will replace the keyword `title` in the metadata dictionary with the corresponding value.\nThe value of `title` is defined at the beginning of the corresponding markdown document:\n\n```markdown\n---\ntitle: Hello MarkTheRipper!\n---\n\n(... Body ...)\n```\n\nIf you have tried other site generators, you may know how to add these special \"header lines\" to your markdown to insert a title, date, tags and etc.\nMarkTheRipper also follows this convention syntactically, but is more flexible.\nFor example, the following example produces exactly the same result:\n\n```html\n\u003ctitle\u003e{foobar}\u003c/title\u003e\n```\n\n```markdown\n---\nfoobar: Hello MarkTheRipper!\n---\n\n(... Body ...)\n```\n\n* Note: This header is called \"FrontMatter\" in other site generators and is written in YAML syntax.\n  However, MarkTheRipper does not strictly use YAML,\n  because uses a syntax that allows for greater flexibility in text.\n  For example, `title` is not required to be enclosed in double quotes,\n  and `tags` is correctly recognized without square brackets.\n  For the record, MarkTheRipper does not use the term \"FrontMatter.\"\n\nDo you somehow see how you can make use of metadata dictionaries?\nIn other words, MarkTheRipper can treat any set of \"key-value\" pairs you write in the markdown header as a metadata dictionary,\nand you can reflect any number of them on the layout.\n\nArbitrary keywords can be substituted anywhere on the layout,\nallowing for example the following applications:\n\n```html\n\u003clink rel=\"stylesheet\" href=\"{stylesheet}.css\"\u003e\n```\n\n```markdown\n---\ntitle: Hello MarkTheRipper!\nstylesheet: darcula\n---\n\n(... Body ...)\n````\n\nPerhaps this feature alone will solve most of the problems.\n\n----\n\n### Special keywords and fallbacks\n\nThere are several special but potentially important keywords in this metadata dictionary.\nThey are listed below:\n\n|Keyword|Note|\n|:----|:----|\n|`generated`|Date and time when the site was generated.|\n|`layout`|The name of the layout to apply.|\n|`lang`|Locale (`en-us`, `ja-jp`, etc.)|\n|`date`|Date of the post.|\n|`timezone`|Timezone of the environment in which the site was generated by MarkTheRipper, in IANA notation, or time.|\n|`published`|Ignore this markdown by explicitly specifying `false`.|\n\n* There are several other special keywords, which will be explained later.\n\nThese keywords can be overridden by writing them in the markdown header.\nIt may not make sense to override `generated`, but just know that MarkTheRipper does not treat metadata dictionary definitions specially.\n\nYou may be wondering what the default values of `lang`, `layout` and `timezone` are.\nMetadata dictionaries can be placed in `metadata.json`,\nwhich is the base definition for site generation.\n(It does not have to be there. In fact, it is not present in the minimum samples.)\nFor example, the following definition:\n\n```json\n{\n  \"title\": \"(Draft)\",\n  \"author\": \"Mark the Ripper\",\n  \"layout\": \"page\",\n  \"lang\": \"en-us\",\n  \"timezone\": \"America/Los_Angeles\"\n}\n```\n\nThis reveals something interesting. The value of the `title` keyword is \"(Draft)\".\nFor example, consider the following layout:\n\n```html\n\u003cmeta name=\"author\" content=\"{author}\" /\u003e\n\u003ctitle\u003e{title}\u003c/title\u003e\n```\n\nIf you specify `title` in the markdown, that title will be inserted,\notherwise the title \"(Draft)\" will be inserted.\nSimilarly, if `author` is specified, the name of author will be inserted,\notherwise \"Mark the Ripper\" will be inserted.\n\nIf you are thinking of using this for a blog, you may not want to put your name in the header of the markdown,\nsince most of your posts will be written by yourself.\nHowever, the title will of course be different for each post.\nIn such cases, you can use the \"fallback\" feature of the metadata dictionary.\n\nAnd as for the `lang` and `layout` fallback:\n\n* Only if `layout` is not found in the fallback, the layout name `page` is used.\n* Only if `lang` is not found in the fallback, the system default language is applied.\n\nThe layout name may need some supplementation.\nThe layout name is used to identify the layout file from which the conversion is being made.\nFor example, if the layout name is `page`, the file `layouts/page.html` will be applied. If:\n\n```markdown\n---\ntitle: Hello MarkTheRipper!\nlayout: fancy\n---\n\n(... Body ...)\n```\n\nthen `layouts/fancy.html` will be used.\n\nThe `date` represents the date and time of the article and is treated like an ordinary keyword,\nbut if it is not defined in the markdown header,\nthe date and time of generation will be inserted into the markdown header automatically.\n\nThe `timezone` is referenced when dealing with dates and times such as `date` and is the basis for time zone correction.\nIf the system timezone setting of the environment in which you run MarkTheRipper is different\nfrom the date and time you routinely embed in your articles,\nyou may want to include it in the `metadata.json` file.\nIf your system's time zone setting is different from the date and time you routinely embed articles,\nyou may want to include it in the `metadata.json` file.\n(A typical environment for this would be when running on a cloud service such as GitHub Actions.)\n\nYou may feel that `lang` is simply one of the ordinary keywords.\nThis is explained in the next section.\n\n----\n\n### Recursive keyword search\n\nYou may want to pull results from the metadata dictionary again,\nusing the keywords as the result of the metadata dictionary pull.\nFor example, you might want to look up:\n\n```markdown\n---\ntitle: Hello MarkTheRipper!\ncategory: blog\n---\n\n(... Body ...)\n```\n\nA `category` keyword is like a category of articles.\nHere, it is named `blog`, but if you refer to it by keyword as follows:\n\n```html\n\u003cp\u003eCategory: {category}\u003c/p\u003e\n```\n\nThe HTML will look like `Category: blog`.\nThis may work fine in some cases, but you may want to replace it with a more polite statement.\nSo you can have the metadata dictionary search for the value again,\nusing `blog` as the keyword.\nUse the `lookup` function keyword built-in MarkTheRipper:\n\n```html\n\u003cp\u003eCategory: {lookup category}\u003c/p\u003e\n```\n\n* The details of the function keywords are explained in later chapters.\n\nIf you do this and register the pair `blog` and `Private diary` in the metadata dictionary,\nthe HTML will show `Category: Private diary`.\n\nSuch keyword/value pairs can be referenced by writing them in `metadata.json` as shown in the previous section.\nIn addition, the metadata dictionary file is actually all JSON files matched by `metadata/*.json`.\nEven if the files are separated,\nthey will all be read and their contents merged when MarkTheRipper starts.\n\nFor example, it would be easier to manage only article categories as separate files,\nsuch as `metadata/category.json`.\n\n----\n\n### Enumeration and nesting\n\nFor classifications such as \"category\" and \"tag\",\nyou would want to have the user select them from a menu and be taken to that page.\nFor example, suppose there are 5 tags on the entire site.\nYou would automatically add these to the page's menu.\nTo allow the user to navigate to a page classified under a tag from the menu, we can use the enumerator.\nAs usual, let's start with a small example.\n\nThis is the layout included in minimum:\n\n```html\n\u003cp\u003eTags:{foreach tags} '{item}'{end}\u003c/p\u003e\n```\n\n* The `tags` keyword indicates a list of tags (see below)\n\nThis means that documents between `{foreach tags}` and `{end}` will be repeated as many times as the number of `tags`.\n\"Documents between\" are, in this case: ` '{item}'`.\nNote the inclusion of spaces.\nLikewise, it can contain line breaks, HTML tags, or anything else in between.\n\nNow suppose we convert the following markdown:\n\n```markdown\n---\ntitle: Hello MarkTheRipper\ntags: foo,bar,baz\n---\n\n(... Body ...)\n````\n\nThen the output will be `\u003cp\u003eTags: 'foo' 'bar'\u003c/p\u003e`.\nThe `foo,bar` in `tags` have been expanded and quoted in the output, each separated by space.\n\nAgain, documents between `{foreach tags}` and `{end}` are output repeatedly, so you can use the following:\n\n```html\n\u003cul\u003e\n  {foreach tags}\n  \u003cli\u003e{item.index}/{item.count} {item}\u003c/li\u003e\n  {end}\n\u003c/ul\u003e\n```\n\nResult:\n\n```html\n\u003cul\u003e\n  \u003cli\u003e0/3 foo\u003c/li\u003e\n  \u003cli\u003e1/3 bar\u003c/li\u003e\n  \u003cli\u003e2/3 baz\u003c/li\u003e\n\u003c/ul\u003e\n```\n\nThe `{item}` inserted between the tags is a keyword that can refer to each repeated value.\nAlso, specifying `{item.index}` will give you a number starting from 0 and counting 1,2,3....\n`{item.count}` is the number of repetitions.\nIn the above there are 3 tags, so this value is always 3.\n\nIn addition, you can nest different keywords.\nFor example, for each category and you can enumerate multiple tags.\n\nIn addition, multiple keywords can be nested.\nThe following example repeats the tag twice:\n\n```html\n\u003cul\u003e\n  {foreach tags}\n  {foreach tags}\n  \u003cli\u003e{item.index} {item}\u003c/li\u003e\n  {end}\n  {end}\n\u003c/ul\u003e\n```\n\nResult:\n\n```html\n\u003cul\u003e\n  \u003cli\u003e0 foo\u003c/li\u003e\n  \u003cli\u003e1 bar\u003c/li\u003e\n  \u003cli\u003e0 foo\u003c/li\u003e\n  \u003cli\u003e1 bar\u003c/li\u003e\n\u003c/ul\u003e\n```\n\nNote, by the way, that `item` in this case refers to a double nested inner `tags` iteration.\nIn some cases, you may want to use the value of the outer iteration.\nIn that situation, you can specify a \"bound name\" for the `foreach`:\n\n```html\n\u003cul\u003e\n  {foreach tags tag1}\n  {foreach tags tag2}\n  \u003cli\u003e{tag1.index}-{tag2.index} {tag1}/{tag2}\u003c/li\u003e\n  {end}\n  {end}\n\u003c/ul\u003e\n```\n\nResult:\n\n```html\n\u003cul\u003e\n  \u003cli\u003e0-0 foo/foo\u003c/li\u003e\n  \u003cli\u003e0-1 foo/bar\u003c/li\u003e\n  \u003cli\u003e1-0 bar/foo\u003c/li\u003e\n  \u003cli\u003e1-1 bar/bar\u003c/li\u003e\n\u003c/ul\u003e\n```\n\nIf the bound name is omitted, `item` is used.\nNow you have a grasp of how to use `foreach` for repetition.\n\n----\n\n### Aggregate tags\n\nOnce you understand how to use repetition, you are as good as done with tags and categories.\nMarkTheRipper automatically aggregates all the tags and categorizations of your content.\nTags can be referenced by the following special keywords:\n\n|Keywords|Note|\n|:----|:----|\n|`tagList`|Aggregate list of all tags.|\n|`rootCategory`|A category that is the root. (It is no classification)|\n\nFirst, let's make a list of tags:\n\n```html\n\u003cul\u003e\n  {foreach tagList}\n  \u003cli\u003e{item}\u003c/li\u003e\n  {end}\n\u003c/ul\u003e\n```\n\nResult:\n\n```html\n\u003cul\u003e\n  \u003cli\u003efoo\u003c/li\u003e\n  \u003cli\u003ebar\u003c/li\u003e\n  \u003cli\u003ebaz\u003c/li\u003e\n       :\n\u003c/ul\u003e\n```\n\nIn the previous section we repeated the use of `tags` defined for the markdown file, here we use `tagList`.\nThe difference is that we are not dealing with a single markdown file, but with the aggregated tags of all markdown files processed by MarkTheRipper.\n\nIn other words, you can use `tagList` to add menu items and link lists by tags.\nHow do I add a link to each tag entry?\nTags alone do not tell us the set of contents associated with a tag,\nbut in fact tags can be enumerated using `foreach`:\n\n```html\n{foreach tagList tag}\n\u003ch1\u003e{tag}\u003c/h1\u003e\n{foreach tag.entries entry}\n\u003ch2\u003e\u003ca href=\"{relative entry.path}\"\u003e{entry.title}\u003c/a\u003e\u003c/h2\u003e\n{end}\n{end}\n```\n\nNote that we specify the bound name to make it easier to understand what we are trying to enumerate.\n\nEnumerating the `entries` property gives access to information about\nthe corresponding markdown group.\nUsing the property `path`, you get the path to the file corresponding to the content,\nand use `title` to get its title (the `title` described in the markdown's header).\n\n* `path` yields the path to the converted HTML file, not the path to the markdown.\n\nBy the way, this path is relative to the output directory.\nIf you embed the path in HTML, it must be relative to the directory where the HTML file resides.\nTo do this calculation, use MarkTheRipper's built-in function keyword `relative`:\n\n```html\n\u003ch2\u003e\u003ca href=\"{relative entry.path}\"\u003e{entry.title}\u003c/a\u003e\u003c/h2\u003e\n```\n\nUsing `relative` to calculate the path will work correctly\nhow the HTML output by MarkTheRipper is deployed on any server.\nThis will be safer than using the reference path for hard-coded URLs.\n\n----\n\n### Aggregate categories\n\nCategories can be referenced by the following special keywords:\n\n|keywords|content|\n|:----|:----|\n|`category`|a hierarchical list of categories. Describe in the header part of each markdown|\n|`rootCategory`|The root (unclassified) category|\n\nThe critical difference between tags and categories is that tags are defined in parallel,\nwhile categories are defined with hierarchy. For example:\n\n````\n(root) --+-- foo --+-- bar --+-- baz --+-- foobarbaz1.md\n         |         |         |         +-- foobarbaz2.md\n         |         |         |\n         |         |         +-- foobar1.md\n         |         |\n         |         +--- foo1.md\n         |         +--- foo2.md\n         |         +--- foo3.md\n         |\n         +--- blog1.md\n         +--- blog2.md\n````\n\nIn the above example, `foobarbaz1.md` belongs to the category `foo/bar/baz`.\nAnd `blog1.md` does not belong to any category,\nit is supposed to belong to implicit hidden `(root)` category inside MarkTheRipper.\nIt is the `rootCategory` keyword.\n\nFor tags we defined it using the `tags` keyword, but for categories we use the keyword `category`.\nThe definition corresponding to `foobarbaz1.md` above is:\n\n```markdown.\n---\ntitle: Hello MarkTheRipper\ncategory: foo,bar,baz\n---\n\n(... Body ...)\n```\n\nSpecifies the hierarchy as a list. Note that unlike tags, this list represents a hierarchy.\nCMSs and site generators often refer to such hierarchies as \"breadcrumb\" lists.\n\nBy the way, MarkTheRipper can determine the category from the directory name by simply placing\nthe content in a categorized subdirectory,\nwithout having to specify the category with the `category` keyword.\nTherefore, to categorize content by category, you only need to place it in categorized subdirectories.\n\nNow that we have grasped the basic structure of categories,\nlet's actually write the layout.\nFirst, enumerate the root category:\n\n```html\n\u003ch1\u003e{rootCategory.name}\u003c/h1\u003e\n\u003cul\u003e\n  {foreach rootCategory.entries entry entry}\n  \u003cli\u003e{entry.path}\u003c/li\u003e\n  {end}\n\u003c/ul\u003e\n```\n\nResult:\n\n```html\n\u003ch1\u003e(root)\u003c/h1\u003e\n\u003cul\u003e\n  \u003cli\u003eblog1.html\u003c/li\u003e\n  \u003cli\u003eblog2.html\u003c/li\u003e\n\u003c/ul\u003e\n```\n\nSince `rootCategory` represents the root category, its property `name` is `(root)`.\nIf this name is not appropriate for display,\nyou can replace it using recursive keyword search expression,\nor you can write it directly since it is root in this example.\n\nThen, like the tags, you can pull the header information for each markdown\nfrom each of the elements enumerated in the `entries`.\n\nHere, `path` is used to output the path to the content, but you can use `title` to output the title.\nIf you use `relative item.path`, you can get the path relative to the current content,\nwhich can be used as the URL of the link to realize the link.\n\nTo enumerate categories, use the `children` property:\n\n```html\n\u003ch1\u003e{rootCategory.name}\u003c/h1\u003e\n{foreach rootCategory.children child1}\n\u003ch2\u003e{child1.name}\u003c/h2\u003e\n{foreach child1.children child2}\n\u003ch3\u003e{child2.name}\u003c/h3\u003e\n{end}\n{end}\n```\n\nIf we nest the enumerations repeately, we can enumerate all deep category structures.\nUnfortunately, it is not possible to dynamically enumerate the category structure,\ni.e., automatic recursively enumerate even the descendant categories that exist.\nThis is by design because MarkTheRipper does not have the ability to define any functions and recursive functions.\n(Such a request is probably only for outputting a site-wide structure list, as we did not see the need for such a request.)\n\nAt the end of the category operation is an example of outputting breadcrumb list.\nIt is very simple:\n\n```html\n\u003cul\u003e\n  {foreach category.breadcrumbs}\n  \u003cli\u003e{item.name}\u003c/li\u003e\n  {end}\n\u003c/ul\u003e\n```\n\nThe `breadcrumbs` property returns a value that allows you to enumerate the categories leading to the target category,\nstarting from the root.\n(However, if the target category is root, the root category is included; otherwise, it is not included.)\n\nThe individual elements enumerated are the same as for the categories described so far.\nIn the above example, the `name` property outputs the name of the category.\n\n----\n\n## Replacing keywords in markdown\n\nThe keyword replacement described so far is for \"Layout\" files.\nIt feature applies equally to markdown files.\nFor example, the keyword replacement in:\n\n```markdown\n---\ntitle: hoehoe\ntags: foo,bar,baz\n---\n\nTitle: {title}\n```\n\nIf you write such a markdown, `{title}` will be keyword-substituted in the same way.\nOf course, the function keyword calculations described so far are also possible.\n\nKeyword substitution on markdown does not work for code blocks:\n\n````markdown\n---\ntitle: hoehoe\ntags: foo,bar,baz\n---\n\nTitle: `{title}`\n\n```\n{title}\n```\n````\n\nAs shown above, `{...}` are not interpreted by MarkTheRipper and are output as is.\n\n----\n\n### Function keywords\n\nHere is a list of built-in functions, including the functions that have appeared so far:\n\n|function|content|\n|:----|:----|\n|`format`|Format arguments into strings. |\n|`relative`|Convert argument paths to relative paths. |\n|`lookup`|Draws a metadata dictionary based on the results given by the `argument`. |\n|`older`|Enumerates articles older than the argument indicates. |\n|`newer`|Enumerates articles newer than the argument indicates. |\n|`take`|Limit the number of items that can be enumerated. |\n|`add`|Add numeric arguments.|\n|`sub`|Subtract numeric arguments.|\n|`mul`|Multiply numeric arguments.|\n|`div`|Divide numeric arguments.|\n|`mod`|Get the remainder of numeric arguments.|\n|`embed`|Generate embedded content using [oEmbed protocol](https://oembed.com/) and etc.|\n|`card`|Generate card-shaped content using [OGP metadata](https://ogp.me/) and etc.|\n\n#### format\n\nFormatting an argument into a string has always been possible without using `format` without any particular problem.\nWhere this function is particularly useful is when dealing with dates and times.\n\nFirst, we show how the formatting is determined.\nWhen using the `date` or `generated` keyword in such HTML:\n\n```html\n\u003cp\u003eDate: {date}\u003c/p\u003e\n```\n\nThe date and time are output in the following format:\n\n```markdown\n---\ndate: 2022/1/23 12:34:56\nlang: en-us\n---\n\n(... Body text ...)\n```\n\n```html\n\u003cp\u003eDate: 1/2/2022 12:34:56 PM +09:00\u003c/p\u003e\n```\n\nThis format varies depending on the language indicated by `lang`.\nIf `ja-jp` instead of `en-us`, then:\n\n```html\n\u003cp\u003eDate: 2022/01/02 12:34:56 +09:00\u003c/p\u003e\n```\n\nFollow the standard format of the language.\nThe value of the `timezone` keyword is referenced when formatting the string,\nand the timezone-corrected date and time are output.\n\nAnd if you want to fix the format, use the `format` function as follows:\n\n```html\n\u003cp\u003eDate: {format date 'yyyy-MM-dd HH.mm.ss'}\u003c/p\u003e\n```\n\n```html\n\u003cp\u003eDate: 2022-01-02 12.34.56\u003c/p\u003e\n```\n\nThe first argument of the `format` function is an expression indicating the value to be formatted,\nand the second argument is a format string.\nThe format specification string is enclosed in single or double quotes.\n\nAs previously mentioned,\n\"MarkTheRipper is written in .NET, but the user does not need to know about .NET\",\nonly this format string follows the conventions of .NET.\n\nThe exact format of the date/time format string is [see this document](https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-date-and-time-format-strings).\n\nIn fact, any value, not just dates and times, can be formatted according to the format.\nFor example, the `index` in the enumeration is a number, but can be formatted as:\n\n```html\n\u003cp\u003e{format item.index 'D3'}\u003c/p\u003e\n```\n\nThe number can be set to fixed three digits.\n\n```html\n\u003cp\u003e007\u003c/p\u003e\n```\n\nThe various format strings are also detailed around the .NET documentation listed above.\n\n#### relative\n\nAs already explained, the `relative` function calculates a path relative to the current article path.\n\n```html\n\u003cp\u003e{relative item.path}\u003c/p\u003e\n```\n\nYou can also pass a string as an argument to the function. For example:\n\n```html\n\u003clink rel=\"stylesheet\" href=\"{relative 'github.css'}\"\u003e\n```\n\nThis way, you can specify the correct relative path where the stylesheet resides.\nThe path specified in the argument is relative to the site's root directory, `docs`.\nIn the above example, the path is relative to `docs/github.css`.\n\nNormally, in such a case, you would specify an absolute path.\nHowever, if you use an absolute path, it will be invalid depending on the deployment environment of your site.\nUsing the `relative` function improves the portability of the generated content.\n\n#### lookup\n\nThe `lookup` function searches the metadata dictionary again for keywords\nwith the same name as the value specified in the argument.\nA typical use case is to look up the name of a tag or category in the metadata dictionary:\n\n```html\n\u003cp\u003eTag: {lookup tag}\u003c/p\u003e\n```\n\nWith `{tag}`, the true name of the tag is output,\nbut with the `lookup` function The output is obtained from the metadata dictionary\nwith the same keyword value as the tag name.\nThus, if `metadata.json` contains:\n\n```json\n{\n  \"diary\": \"What happened today\"\n}\n```\n\nYou can actually swap the strings that are output.\nAs we have seen, you can also directly specify a value for a function argument,\nso even if you want to output a fixed string, you can use:\n\n```html\n\u003cp\u003eTag: {lookup 'diary'}\u003c/p\u003e\n```\n\n#### older, newer (Article navigation)\n\nYou can use `older` and `newer` function to enumerate articles.\nFor example, to enumerate articles newer than the current article, use the following expression:\n\n```html\n{foreach (newer self)}\u003cp\u003e{item.title}\u003c/p\u003e{end}\n```\n\nwhere `self` represents the current article (the markdown article currently using this layout) and the individual elements enumerated by `foreach` represent newer articles.\nThus, you can create links by referencing properties and applying the `relative` function as follows:\n\n``html\n{foreach (newer self)}\u003cp\u003e\u003ca href=\"{relative item.path}\"\u003e{item.title}\u003c/a\u003e\u003c/p\u003e{end}\n```\n\nNote the position of the parentheses.\nIn the above, the parentheses are used to specify `self` as an argument to the `newer` function.\nWithout the parentheses,\nyou have specified `foreach` with `newer` and `self` as arguments,\nwhich will not work correctly.\n\nThe enumerated articles are obtained in date order.\nTherefore, MarkTheRipper recommends that blog like post which are date-oriented;\nto group them into category such as `blog` and use this function to navigate within those category.\n\n`older` and `newer` function arguments can be expressions that indicate any articles.\nAn enumerated value of the `entries` property is equivalent.\n\n#### take (enumeration operation)\n\n`take` limits the number of possible enumerations of values.\nFor example, `tag.entries` will enumerate all articles with that tag:\n\n```html\n{foreach tags tag}\n\u003ch2\u003e{tag}\u003c/h2\u003e\n{foreach tag.entries}\u003cp\u003e{item.title}\u003c/p\u003e{end}\n{end}\n```\n\nHowever, you may want to limit this to a few pieces:\n\n```html\n{foreach tags tag}\n\u003ch2\u003e{tag}\u003c/h2\u003e\n{foreach (take tag.entries 3)}\u003cp\u003e{item.title}\u003c/p\u003e{end}\n{end}\n```\n\nThe `take` function takes two arguments:\nThe first is the enumeration target and the second is the number of enumerations to limit.\nThe above expression limits the number to a maximum of 3.\n\nThere is a neat little technique using this `take` function and `foreach`.\nIt is applied in combination with the `newer` function in the previous section:\n\n``html\n{foreach (take (newer self) 1)}\u003ca href=\"{relative item.path}\"\u003eNewer: {item.title}\u003c/a\u003e{end}\n```\n\nLimiting the number of articles to be enumerated to one means that if there are zero articles,\nthey will not be enumerated at all.\nIn other words, if there are no articles to enumerate,\nthe `foreach` will not be output, and therefore will not be displayed.\nThis means that if there are no articles newer than the current article,\nthe link will not be displayed.\n\nThis expression is almost identical to the sample layout.\n\n#### add, sub, mul, div, mod (Numerical calculation)\n\nThese are functions that perform numerical calculations.\nAt least one argument is required, and if there are three or more arguments,\nthe calculation is performed consecutively. For example:\n\n```html\n\u003cp\u003e1 + 2 + 3 = {add 1 2 3}\u003c/p\u003e\n```\n\nAdds all the numbers in the argument.\nFor complex calculations, use parentheses:\n\n```html\n\u003cp\u003e(1 + 2) * 4 = {mul (add 1 2) 4}\u003c/p\u003e\n```\n\nYou can nest any number of parentheses.\nParentheses can be applied and formatted as desired using the `format` function:\n\n```html\n\u003cp\u003e1 + 3 = {format (add 1 3) 'D3'}\u003c/p\u003e\n```\n\nResult:\n\n```html\n\u003cp\u003e1 + 3 = 004\u003c/p\u003e\n```\n\nThe argument does not have to be a number,\nas long as the string can be regarded as a number:\n\n```html\n\u003cp\u003e1 + 2 + 3 = {add 1 '2' 3}\u003c/p\u003e\n```\n\nIt is acceptable to have numbers containing decimals in the arguments.\nIf so, it will be treated as a calculation with decimals\n(called \"Floating-point operation\"):\n\n```html\n\u003cp\u003e1 + 2.1 + 3 = {add 1 2.1 3}\u003c/p\u003e\n```\n\nResults containing decimals may not always turn out as intended.\nIt may be better to always use the `format` function to account for such possibilities.\nFor information on how to specify formats that include decimals, [see here](https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings#fixed-point-format-specifier-f).\n\nHere is a simple example of using the calculation.\nIn enumeration, `item.index` is a number and starting from 0.\nOn the other hand, `item.count` is a number that can be enumerated,\nbut it is not a general notation if you put it in a sequence:\n\n``html\n\u003cp\u003eindex/count = {item.index}/{item.count}\u003c/p\u003e\n```\n\nResult:\n\n```html\n\u003cp\u003eindex/count = 0/3\u003c/p\u003e\n\u003cp\u003eindex/count = 1/3\u003c/p\u003e\n\u003cp\u003eindex/count = 2/3\u003c/p\u003e\n```\n\nIn such a case, you can use `add` function to get:\n\n```html\n\u003cp\u003eindex/count = {add item.index 1}/{item.count}\u003c/p\u003e\n```\n\nWould result in a number from 1 to `count`,\nwhich is closer to a natural representation.\n\nResult:\n\n```html\n\u003cp\u003eindex/count = 1/3\u003c/p\u003e\n\u003cp\u003eindex/count = 2/3\u003c/p\u003e\n\u003cp\u003eindex/count = 3/3\u003c/p\u003e\n```\n\n#### embed (Generates embedded content)\n\nThe `embed` (and `card`, described below) functions are special,\npowerful functions that generate content by reference to external data,\nrather than performing simple calculations.\nIt is a powerful function that generates content by referencing external data.\n\nHave you ever wanted to embed a YouTube video on your blog?\nOr perhaps you have wanted to display card-shaped content to external content,\nrather than just a link.\n\nThe `embed` and `card` functions make such complex content embedding easy.\nFor example, you could write the following in your document:\n\n```markdown\n## Found a great video!\n\nOne day, when I can travel freely, I would like to visit...\n\n{embed https://youtu.be/1La4QzGeaaQ}\n```\n\nThen you will see the following:\n\n![embed-sample1-en](Images/embed-sample1-en.png)\n\nThis image is not just a thumbnail.\nYou can actually play the video on the page. Magic! Is it?\nThis is made possible by using the standard [oEmbed protocol](https://oembed.com/)\nto this is achieved by automatically collecting content that should be embedded in HTML.\n\nThe argument to the `embed` function is simply the \"permalink\" of the content,\ni.e., the URL that should be shared.\nIn addition to YouTube, many other well-known content sites support this function, so:\n\n```markdown\n## Today's hacking\n\n{embed https://twitter.com/kozy_kekyo/status/1508078650499149827}\n```\n\n![embed-sample2-en](Images/embed-sample2-en.png)\n\nThus, other content, such as Twitter, can be easily embedded. To see which content sites are supported, please [directly refer to oEmbed's json metadata](https://oembed.com/providers.json). You will see that quite a few sites are already supported.\n\nHowever, one of the most useful sites we know of Amazon,\nto our surprise does not support oEmbed!\nTherefore, MarkTheRipper specifically recognizes Amazon's product links so that they can be embedded as well:\n\n```markdown\n## Learning from Failure\n\n{embed https://amzn.to/3USDXfp}\n```\n\n![embed-sample3-en](Images/embed-sample3-en.png)\n\n* This link can be obtained by activating Amazon associates.\n  Amazon associates can be activated by anyone with an Amazon account, but we won't go into details here.\n\nNow, a little preparation is required to use this handy function.\nPrepare a dedicated layout file `layouts/embed.html` to display this embedded content.\nThe contents are only as follows:\n\n```html\n\u003cdiv style=\"max-width:800px;margin:10px;\"\u003e\n    {contentBody}\n\u003c/div\u003e\n```\n\nAs in the previous layout explanations,\nthe `contentBody` will contain the actual oEmbed content to be embedded.\nThe outer `div` tag determines the area of this embedded content.\nIn the above, the body is 800px wide with some space around the perimeter.\nYou may want to adjust this to fit your site's design.\n\nIf you need a different layout for each content site,\nyou can apply a file name like `layouts/embed-YouTube.html` that specifies the name of the oEmbed provider.\n\nBy the way, the oEmbed protocol may not contain embedded content.\nIn such a case, the oEmbed metadata that could be obtained together is used to generate content similar to the `card` function introduced next.\n\n#### card (Generate card-shaped content)\n\nThe `embed` function directly displays embedded content provided by the content provider.\nThe `card` function collects content metadata and displays it in a view provided by MarkTheRipper.\n\nMetadata is collected in the following way:\n\n* oEmbed: Using the accompanying metadata (including cases where embedded content was not provided by the `embed` function)\n* OGP (Open Graph protocol): Scraping the target page and collecting the OGP metadata contained in the page.\n* Amazon: Collect from Amazon associates page.\n\nUsage is exactly the same as the `embed` function:\n\n```markdown\n## Found a great video!\n\nOne day, when I can travel freely, I would like to visit...\n\n{card https://youtu.be/1La4QzGeaaQ}\n```\n\nThen you will see the following :\n\n![card-sample1-en](Images/card-sample1-en.png)\n\nUnlike the `embed` function, it displays the additional information as content and links in a card-shaped format.\nSimilarly:\n\n```markdown\n## Learning from Failure\n\n{card https://amzn.to/3USDXfp}\n```\n\n![card-sample3-en](Images/card-sample3-en.png)\n\nVarious content can be displayed in card-shaped format.\nYou can use either the embedded format or the card-shaped format as you like.\n\nLike the `embed` function, the `card` function also requires a dedicated layout file.\nThe layout file `layouts/card.html` can be adapted to your site based on the following template:\n\n```html\n\u003cdiv style=\"max-width:640px;margin:10px;\"\u003e\n    \u003cul style=\"display:flex;padding:0;border:1px solid #e0e0e0;border-radius:5px;\"\u003e\n        \u003cli style=\"min-width:180px;max-width:180px;padding:0;list-style:none;\"\u003e\n            \u003ca href=\"{permaLink}\" target=\"_blank\" style=\"display:block;width:100%;height:auto;color:inherit;text-decoration:inherit;\"\u003e\n                \u003cimg style=\"margin:10px;width:100%;height:auto;\" src=\"{imageUrl}\" alt=\"{title}\"\u003e\n            \u003c/a\u003e\n        \u003c/li\u003e\n        \u003cli style=\"flex-grow:1;margin:10px;list-style:none; \"\u003e\n            \u003ca href=\"{permaLink}\" target=\"_blank\" style=\"display:block;width:100%;height:auto;color:inherit;text-decoration:inherit;\"\u003e\n                \u003ch5 style=\"font-weight:bold;\"\u003e{title}\u003c/h5\u003e\n                \u003cp\u003e{author}\u003c/p\u003e\n                \u003cp\u003e{description}\u003c/p\u003e\n                \u003cp\u003e\u003csmall class=\"text-muted\"\u003e{siteName}\u003c/small\u003e\u003c/p\u003e\n            \u003c/a\u003e\n        \u003c/li\u003e\n    \u003c/ul\u003e\n\u003c/div\u003e\n```\n\nThis template is completely independent HTML.\nIf you wish to use it in conjunction with Bootstrap,\nplease refer to the files included in the sample layouts generated by `mtr init standard` or `mtr init rich`.\n\nEven the `card` function can be customized to be site-specific by naming the file `layouts/card-YouTube.html`.\n\n----\n\n## Install develop branch package\n\n```\n$ dotnet tool install -g MarkTheRipper --nuget-source http://nuget.kekyo.online:59103/repository/nuget/index.json\n```\n\n----\n\n## License\n\nApache-v2.\n\n----\n\n## History\n\n* 0.4.0:\n  * Can be paging navigation. #20\n* 0.3.0:\n  * Ready to use oEmbed. #18\n* 0.2.0:\n  * Keyword expansion can be applied to the markdown document itself. #3\n* 0.1.0:\n  * Automatically inserted date metadata into markdown file when lacked. #13\n  * Function calls can be made. #2\n  * Aggregated categories and tags from entire content markdowns. #14\n  * Bracket characters are supported. #6\n  * Can use the generator keyword. #5\n  * Automatic category collection is possible. #1\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkekyo%2Fmarktheripper","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkekyo%2Fmarktheripper","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkekyo%2Fmarktheripper/lists"}