{"id":21194443,"url":"https://github.com/cspray/blogisthenics","last_synced_at":"2026-04-24T18:39:59.956Z","repository":{"id":40333993,"uuid":"140899640","full_name":"cspray/blogisthenics","owner":"cspray","description":"A static site generate written with Annotated Container","archived":false,"fork":false,"pushed_at":"2024-05-22T01:27:18.000Z","size":252,"stargazers_count":0,"open_issues_count":4,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-14T20:57:52.350Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cspray.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-07-13T22:59:19.000Z","updated_at":"2024-05-22T01:22:00.000Z","dependencies_parsed_at":"2024-05-22T02:29:58.645Z","dependency_job_id":"f4e3128b-95db-496c-8bda-abbe417bd824","html_url":"https://github.com/cspray/blogisthenics","commit_stats":{"total_commits":32,"total_committers":3,"mean_commits":"10.666666666666666","dds":0.59375,"last_synced_commit":"184d43a80761b20e978819638d5ffc9b5d00a38a"},"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cspray%2Fblogisthenics","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cspray%2Fblogisthenics/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cspray%2Fblogisthenics/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cspray%2Fblogisthenics/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cspray","download_url":"https://codeload.github.com/cspray/blogisthenics/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243648323,"owners_count":20324857,"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":[],"created_at":"2024-11-20T19:22:11.675Z","updated_at":"2025-12-29T18:06:29.808Z","avatar_url":"https://github.com/cspray.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Blogisthenics\n\nAre you looking for a static site generator with the following features:\n\n- A [SPA](https://en.wikipedia.org/wiki/Single-page_application) using the latest JavaScript Web Components?\n- The newest CSS frameworks baked right in?\n- Access to the fanciest, most modern templating engine?\n- Integration with your favorite framework?\n\nThen this project isn't for you because Blogisthenics doesn't offer any of those things! And never will! What do we give you instead?\n\n- A way to create a boring ol' multi-page application using HTML, Markdown, and a minimal amount of CSS; where clicking on a link makes the whole page refresh. So old school!\n- Powerful, no-frills templating engine that's been in use for over 20 years. PHP itself!\n- Customize your dynamic content with Front Matter... not written in YAML!\n- Absolutely no JavaScript or related tooling out-of-the-box. I prefer to have just 1 shitty language in my site generators, thank-you-very-much!\n- Cohesive, type-safe, testable mechanisms for programmatically controlling the content of your site!\n\n## Usage Guide\n\nOh, shit. You're still here? In all honesty, you probably shouldn't use this software! There's just an ass-load of site generators out there and nearly all of them are going to be more supported than whatever this ball of crap turns into. The stuff I write below is mostly for my own benefit so when I come back here in 6 months I can figure out what the hell is going on.\n\n### Directory Structure\n\nBlogisthenics follows a principle that there are reasonable defaults for your site configuration, but you can override any of the defaults to customize your installation. Your directory structure should resemble the following:\n\n```\n/.blogisthenics\n    config.json                 # Configure Blogisthenics, if not provided default config will be used\n/content                        # The actual content for your site goes here\n    /assets                     # CSS, JS, images ... isn't treated specially, convention to put stuff here\n    /blog                       # Your blog articles ... could be named whatever you want\n    index.md                    # Markdown files are ok. Front-matter parsing and layouts are supported\n    about.html                  # HTML files are ok too, you won't get any parsing or layout support though\n    contact.html.php            # Add a PHP extension to enable front-matter parsing and layout support\n/data\n    ...                         # Store JSON files here to access in the KeyValueStore\n/layouts\n    main.html.php               # Store PHP template files here to use as layouts\n    article.html.php            # Layouts can be nested as deep as you want, but probably a logical limit\n    foo.md.php                  # Support Markdown templates\n/src\n    /ContentGeneratedHandler\n        ...                     # Any ContentGeneratedHandler instances\n    /ContentWrittenHandler\n        ...                     # Any ContentWrittenHandler instances\n    /DataProvider\n        ...                     # Any DataProvider instances\n    /DynamicContentProvider\n        ...                     # Any DynamicContentProvider instances\n    /Formatter\n        ...                     # Any Formatter instances\n    /TemplateHelperProvider\n        ...                     # Any TemplateHelperProvider instances\n```\n\n### Content Overview\n\nContent for your site gets lumped into three categories:\n\n- Static Assets\n- Layouts\n- Pages\n\n#### Static Assets\n\nStatic assets are any content in your site that should not be dynamically rendered, whatever is in the file gets copied over exactly, with the same path, when the site is built. Specifically the following functionalities are **not** supported by static assets.\n\n- Front Matter Parsing\n- Template processing, i.e. no variables\n- Multiple extension formatting support\n\n#### Pages\n\nPages are `.html`, `.md`, and `.php` files that act as specific content for a path that will be added to your site. Pages are expected to be only partial HTML documents and must define a layout. If a layout is not explicitly defined in the Front Matter of a page we use the default layout from the site configuration.\n\n#### Layouts\n\nLayouts are `.md` and `.php` files that act as the outer chrome for pages. Layouts can be inserted into other layouts. The below example demonstrates a minimal layout, typically named something like `main.html.php` or `default.html.php`.\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n    \u003chead\u003e\n        \u003ctitle\u003e\u003c?= $this-\u003etitle ?? 'Blogisthenics' ?\u003e\u003c/title\u003e\n    \u003c/head\u003e\n    \u003cbody\u003e\n        \u003c?= $this-\u003eyield() ?\u003e\n    \u003c/body\u003e\n\u003c/html\u003e\n```\n\nNote the call to `$this-\u003eyield()`, when in a layout this is required to output the content being injected. If you attempt to call `$this-\u003eyield()` from a non-layout piece of Content an exception will be thrown. Check out the \"Templating\" section below for more details on using Pages and Layouts.\n\n### Templating\n\nBlogisthenics uses PHP itself as the templating engine. On top of that we add some functionality to allow the following features:\n\n- Nesting an arbitrary level of layouts\n- Auto-escaping all values, including ability to escape contextually.\n- Providing access to the FrontMatter of the layout and page\n- Provide read-only access to the loaded data\n- Allow the creation of helper methods for outputting common pieces of content\n\nThe majority of the functionality described in this section refers to the `Cspray\\Blogisthenics\\Context` object. This object is defined as `$this` in your templates. The simplest templates might look something like the following.:\n\n```html\n\u003c!-- Stored in the layouts directory with the filename `main.html.php` --\u003e\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n    \u003chead\u003e\n        \u003ctitle\u003e\u003c?= $this-\u003etitle ?? 'Blogisthenics README' ?\u003e\u003c/title\u003e\n    \u003c/head\u003e\n    \u003cbody\u003e\n        \u003c?= $this-\u003eyield() ?\u003e\n    \u003c/body\u003e\n\u003c/html\u003e\n```\n\n```md\n\u003c!-- Stored in the content directory with the filename `index.md` --\u003e\n{\n    \"title\": \"Home Page\"\n}\n\n# \u003c?= $this-\u003etitle ?\u003e\n\nYep, that's right. The Front Matter is just a JSON object. Slap a new line on the end of \nthat bad boy, then start writing your content. The values available in the page are the \nvalues found in your Front Matter.\n```\n\nWhen built, your site would include an `index.html` file that resembles the following:\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n    \u003chead\u003e\n        \u003ctitle\u003eHome Page\u003c/title\u003e\n    \u003c/head\u003e\n    \u003cbody\u003e\n        \u003ch1\u003e\"Home Page\"\u003c/h1\u003e\n\n        \u003cp\u003e\n            Yep, that's right. The Front Matter is just a JSON object. Slap a new line on the end of that bad boy, then start writing your content. The values available in the page are the values found in your Front Matter.\n        \u003c/p\u003e\n    \u003c/body\u003e\n\u003c/html\u003e\n```\n\n#### Template Helpers\n\nSometimes what you want to do might be too advanced for a static front matter or would be easier to share across many pages and layouts if it was encapsulated in a method you could invoke inside a template. Using the `TemplateHelperProvider` you can add template helper methods easily and then get access to them in your templates.\n\nTo utilize template helpers you'll have to implement some PHP code. In addition to whatever your helper does you'll have to make sure it gets integrated with Blogisthenics. Fortunately, that's easy to do thanks to [Annotated Container](https://github.com/cspray/annotated-container). This code should live somewhere in the `src` directory of your Blogisthenics site.\n\n```php\n\u003c?php declare(strict_types=1);\n\nnamespace Acme\\BlogisthenicsDemo;\n\nuse Cspray\\AnnotatedContainer\\Attribute\\Service;use Cspray\\Blogisthenics\\Template\\MethodDelegator;use Cspray\\Blogisthenics\\Template\\TemplateHelperProvider;\n\n#[Service]\nfinal class MyTemplateHelperProvider implements TemplateHelperProvider {\n\n    public function addTemplateHelpers(MethodDelegator $methodDelegator) : void {\n        $methodDelegator-\u003eaddMethod('myHelper', function() {\n            return 'This is my helper content!';\n        });\n    }\n}\n```\n\nInside your template you can now invoke `$this-\u003emyHelper()`!\n\n```md\n\u003c!-- some file ending in .md --\u003e\n\u003c?= $this-\u003emyHelper() ?\u003e\n```\n\n#### Auto-Escaping\n\nSince Content can be generated at runtime with Front Matter that could include data from external sources Blogisthenics takes the stance that _all_ of your data should be escaped properly. Utilizing the [laminas/escaper](https://github.com/laminas/escaper) project we automatically escape all values originating from the Context object. Meaning, all of your front matter, method helpers, and loaded JSON data get automatically HTML escaped at the time of output. If you don't want the value from a helper method to be escaped automatically wrap it in the value object `Cspray\\Blogisthenics\\SafeToNotEncode`.\n\nFuture updates will add a template API for contextually aware escaping. In other words, you'll be able to implicitly and explicitly escape a piece of data with awareness of whether it is CSS data, or JS data, or HTML data.\n\n#### Template Formatting\n\nAs already mentioned, we support the ability to create pages out of Markdown documents. Specifically, Blogisthenics uses [GitHub Flavored Markdown](https://github.github.com/gfm/) provided by the [league/commonmark](https://commonmark.thephpleague.com/) package. After all, writing a blog in _just_ HTML would start to get pretty old... blog articles are pretty good candidates for Markdown. Instead of baking Markdown support into Blogisthenics somewhere we expose an interface called `Cspray\\Blogisthenics\\Formatter` that provides an opportunity for a rendered template to have some additional formatting applied. The `Cspray\\Blogisthenics\\GitHubFlavoredMarkdownFormatter` is the implementation taking care of Markdown. You can implement your own `Formatter` instance if you find Blogisthenics minimalist approach too spartan for you. We'll be taking advantage of Annotated Container to easily integrate your Formatters into Blogisthenics. This code should live somewhere in the `src` directory of your Blogisthenics site.\n\n```php\n\u003c?php declare(strict_types=1);\n\nnamespace Acme\\BlogisthenicsDemo;\n\nuse Cspray\\AnnotatedContainer\\Attribute\\Service;use Cspray\\Blogisthenics\\Template\\Formatter;\n\n#[Service]\nfinal class MyCustomFormatter implements Formatter {\n\n    public function getFormatType() : string {\n        return 'my-type';\n    }\n\n    public function format(string $contents) : string {\n        return 'my formatted ' . $contents;\n    }\n\n}\n```\n\nNow any template you create that ends in `my-type` or `my-type.php` will be passed to `MyCustomFormatter` before the contents are written to disk.\n\n### Dynamic Content\n\nSometimes it isn't possible to create all the necessary content ahead of time in static files. You may need to have access to the content of the site or some other information that requires you to provide the Content at runtime. You can take care of this in Blogisthenics by implementing the `Cspray\\Blogisthenics\\DynamicContentProvider` interface. We'll be using Annotated Container to easily integrate your implementations. This code should live somewhere in the `src` directory of your Blogisthenics site.\n\n```php\n\u003c?php declare(stric_types=1);\n\nuse Cspray\\AnnotatedContainer\\Attribute\\Service;use Cspray\\Blogisthenics\\Site;use Cspray\\Blogisthenics\\SiteGeneration\\DynamicContentProvider;\n\n#[Service]\nfinal class MyContentProvider implements DynamicContentProvider {\n\n    public function addContent(Site $site) : void {\n        // Construct your Content and call $site-\u003eaddContent($content)\n    }\n\n}\n```\n\n### Programmatic Data\n\nSometimes your blog or site might need to include some programmatically loaded data. Inside Blogisthenics this data gets \nstored in the `Cspray\\Blogisthenics\\KeyValueStore`. You can depend on this service in your own `#[Service]` and a mutable \nimplementation will be injected. In Template contexts you can get access to an immutable store through the `Context::kv()` \nmethod, `$this-\u003ekv()` in your templates. You can load data into the `KeyValueStore` in 1 of 2 methods; statically or \nthrough programmatic calls.\n\n#### Loading Static Files\n\nIn your data directory, by default `/data` in your site's root, all JSON files will be loaded and stored in the \n`KeyValueStore`. All data is namespaced with the name of the file and keys in the JSON object are accessible through \ndot-notation. For example, If you have the following JSON file stored in `/data/foo.json` you can make the below method \ncalls in your template.\n\n```\n{\n    \"bar\": {\n        \"baz\": {\n            \"qux\": \"whatever\"\n        }\n    }\n}\n```\n\n```php\n// Inside a Page or Layout\n\u003c?= $this-\u003ekv()-\u003eget('foo/bar.baz.qux') ?\u003e\n```\n\n#### Loading Dynamic Data\n\nMaybe you don't know ahead of time what data needs to be loaded. In those situations you should implement a \n`Cspray\\Blogisthenics\\DataProvider` instance. We'll be taking advantage of Annotated Container to easily integrate this \ninstance. Somewhere in your `/src` directory you should implement the following:\n\n```php\n\u003c?php declare(strict_types=1);\n\nuse Cspray\\AnnotatedContainer\\Attribute\\Service;use Cspray\\Blogisthenics\\SiteData\\DataProvider;\n\n#[Service]\nclass MyDataProvider implements DataProvider {\n\n    public function addData(KeyValueStore $keyValue) : void {\n        // add whatever data is appropriate\n    }\n}\n```\n\nJust like statically loaded files you can access whatever data you load into the store through dot-notation. Unlike statically \nloaded files nothing is autonamespaced. If your data needs require a namespace you should make sure you do that in the \nkeys you use to set data.\n\n## Why?!\n\n\u003e Holy shit, cspray! Have you never heard of [Jekyll](https://jekyllrb.com/), or [Hugo](https://gohugo.io/), or any of the 134 bajillion generators listed on the [JAMStack](https://jamstack.org/generators/) site?\n\nWell, clearly I know about them. I included links to their sites! There's 4 primary reasons I did this.\n\n1. I'm a masochist.\n2. When I look at site generators out there I see a lot of libraries with, from my perspective, the following drawbacks:\n\n   - Aren't written in PHP\n   - Create sites using SPA architectures\n   - Require me to use Laravel or Symfony\n\n   I want to use PHP to build my site, and without requiring Laravel or Symfony to do so. I didn't find anything targeted towards the framework-less audience, so I built it myself.\n3. Opportunity to dogfood [Annotated Container](https://github.com/cspray/annotated-container).\n4. Creating things to learn how they work or how I might accomplish it is how I learn things. It is kinda my jam.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcspray%2Fblogisthenics","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcspray%2Fblogisthenics","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcspray%2Fblogisthenics/lists"}