roundabout,
created on Monday, 29 April 2024, 06:46:55 (1714373215),
received on Monday, 29 April 2024, 06:48:08 (1714373288)
Author identity: vlad <vlad.muntoiu@gmail.com>
bfb11b0e84bac7b9adbefae9c06eb027be335448
README.md
articles/markdown-testing.html
@@ -0,0 +1,80 @@
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title> Markdown testing </title> <link rel="stylesheet" href="/static/style.css"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> </head> <body> <header> <nav> <ul> <li><a href="/">Home</a></li> <li><a href="/projects">Projects</a></li> <li><a href="/index">Index</a></li> <li><a href="https://roundabout-host.com/roundabout">Roundabout-host</a></li> </ul> <ul> <li><a href="mailto:root@roundabout-host.com" id="mail-link">root@roundabout-host.com</a></li> </ul> </nav> </header> <main> <h1>Markdown testing</h1> <div id="article-date">2024-04-29, 09:42:03</div> <article class="content-area"> <h1>Header 1</h1><h2>Header 2</h2><h3>Header 3</h3><h4>Header 4</h4><h5>Header 5</h5><h6>Header 6</h6><p><em class="emphasis-1">emphasis 1</em> <strong class="emphasis-2">emphasis 2</strong> <strong class="emphasis-2 emphasis-1">emphasis 3</strong> <strong class="emphasis-3">emphasis 4</strong> <strong class="emphasis-3 emphasis-1">emphasis 5</strong> <strong class="emphasis-3 emphasis-2">emphasis 6</strong> <strong class="emphasis-3 emphasis-2 emphasis-1">emphasis 7</strong> </p><ul><li><p>list item 1 </p></li><li><p>list item 2 </p><ul><li><p>list item 2.1 </p></li><li><p>list item 2.2 </p><ul><li><p>list item 2.2.1 </p></li><li><p>list item 2.2.2 </p></li></ul></li><li><p>list item 2.3 </p></li></ul></li></ul><ol><li><p>list item 1 </p><ol><li><p>list item 1.1 </p></li><li><p>list item 1.2 </p><ol><li><p>list item 1.2.1 </p></li><li><p>list item 1.2.2 </p></li><li><p>list item 1.2.3 </p></li></ol></li><li><p>list item 1.3 </p></li></ol></li><li><p>list item 2 </p></li><li><p>list item 3 </p></li><li><p>list item 4 </p><ol><li><p>list item 4.1 </p></li><li><p>list item 4.2 </p></li></ol></li></ol><pre data-language="python">if __name__ == "__main__": print("Hello, world!") `` > blockquote 1 > blockquote 2 ... > ### Test > **Hello?** [link](https://wikipedia.org) ![image](https://www.wikipedia.org/portal/wikipedia.org/assets/img/Wikipedia-logo-v2@2x.png) `inline code` </pre> </article> </main> <footer> <p>Page generated on Monday, 29 April 2024 at 09:45:48</p> <p>© Roundabout developer</p> </footer> </body> </html>
articles/moved-to-a-roundabout.html
@@ -0,0 +1,49 @@
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title> Moved to a roundabout </title> <link rel="stylesheet" href="/static/style.css"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> </head> <body> <header> <nav> <ul> <li><a href="/">Home</a></li> <li><a href="/projects">Projects</a></li> <li><a href="/index">Index</a></li> <li><a href="https://roundabout-host.com/roundabout">Roundabout-host</a></li> </ul> <ul> <li><a href="mailto:root@roundabout-host.com" id="mail-link">root@roundabout-host.com</a></li> </ul> </nav> </header> <main> <h1>Moved to a roundabout</h1> <div id="article-date">2024-04-29, 09:42:03</div> <article class="content-area"> <p>Welcome to my new website! I've moved from GitHub to a git software I've developed, <a href="https://roundabout-host.com/roundabout/roundabout">a roundabout</a>. To go along with this move, I also made a new website, which is powered by a custom static site generator called <a href="https://roundabout-host.com/roundabout/ampoule">Ampoule</a>. This will be the generator I will use to write the documentation for all my projects, including the roundabout itself. </p><p>I've also moved all my projects to the roundabout. You can find them at <a href="https://roundabout-host.com/roundabout">my profile</a>. I will keep the GitHub repositories for now, but they will not be updated except for a notice explaining about the move. </p><p><img alt="Moved!" src="https://private-user-images.githubusercontent.com/74449186/315608390-21cd38d3-a42c-4d3e-bccb-7bbc6aa2fade.svg?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MTM2MjY2MjAsIm5iZiI6MTcxMzYyNjMyMCwicGF0aCI6Ii83NDQ0OTE4Ni8zMTU2MDgzOTAtMjFjZDM4ZDMtYTQyYy00ZDNlLWJjY2ItN2JiYzZhYTJmYWRlLnN2Zz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDA0MjAlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwNDIwVDE1MTg0MFomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWFjNTdiZjA1ZmJiNDI3NjJkYzFiMDdjOGUzNzc0OTc4ZGE0ZWJjMWQ5NDEzMDg0YzgyZDhmODVmNjAwYzJjMTQmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.q_c1VAhb_7JHoCGeL9u8oe0Z5LH1wvfEzYiK_bBHBTs"></img> </p> </article> </main> <footer> <p>Page generated on Monday, 29 April 2024 at 09:45:48</p> <p>© Roundabout developer</p> </footer> </body> </html>
index.html
@@ -0,0 +1,53 @@
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Home</title> <link rel="stylesheet" href="/static/style.css"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> </head> <body> <header> <nav> <ul> <li><a href="/">Home</a></li> <li><a href="/projects">Projects</a></li> <li><a href="/index">Index</a></li> <li><a href="https://roundabout-host.com/roundabout">Roundabout-host</a></li> </ul> <ul> <li><a href="mailto:root@roundabout-host.com" id="mail-link">root@roundabout-host.com</a></li> </ul> </nav> </header> <main> <h1>All articles</h1> <article class="content-area"> <h2><a href="/articles/moved-to-a-roundabout.html" class="article-title">Moved to a roundabout</a></h2> <div class="home-article-date">2024-04-29</div> <p><p>Welcome to my new website! I've moved from GitHub to a git software I've developed, <a href="https://roundabout-host.com/roundabout/roundabout">a roundabout</a>. To go along with this move, I also made a new website, which is powered by a custom static site generator called <a href="https://roundabout-host.com/roundabout/ampoule">Ampoule</a>. This will be the generator I will use to write the documentation for all my projects, including the roundabout itself. </p></p> </article> <article class="content-area"> <h2><a href="/articles/markdown-testing.html" class="article-title">Markdown testing</a></h2> <div class="home-article-date">2024-04-29</div> <p><h1>Header 1</h1><h2>Header 2</h2><h3>Header 3</h3><h4>Header 4</h4><h5>Header 5</h5><h6>Header 6</h6></p> </article> </main> <footer> <p>Page generated on Monday, 29 April 2024 at 09:45:48</p> <p>© Roundabout developer</p> </footer> </body> </html>
projects/ampoule.html
@@ -0,0 +1,247 @@
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title> Ampoule </title> <link rel="stylesheet" href="/static/style.css"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> </head> <body> <header> <nav> <ul> <li><a href="/">Home</a></li> <li><a href="/projects">Projects</a></li> <li><a href="/index">Index</a></li> <li><a href="https://roundabout-host.com/roundabout">Roundabout-host</a></li> </ul> <ul> <li><a href="mailto:root@roundabout-host.com" id="mail-link">root@roundabout-host.com</a></li> </ul> </nav> </header> <main> <h1 class="project-title"> <span>Ampoule</span> <a href="https://roundabout-host.com/roundabout/ampoule">Repository</a> </h1> <article class="content-area"> <p>Ampoule is a lightweight, simple yet flexible, static site generator written in Python. It uses Jinja2 for templating. </p><h2>Features</h2><ul><li><p><strong class="emphasis-2"><em class="emphasis-1">Extremely</em> simple and small</strong>, only a few hundred lines of code. </p></li><li><p><em class="emphasis-1">Only</em> depends on Jinja2, Ruamel YAML, bs4, and colorama. </p></li><li><p><strong class="emphasis-2">Jinja2 templating</strong> will be familiar to Flask users. Now you can use the same templates for both dynamic and static sites. </p></li><li><p>More of <strong class="emphasis-2">a framework</strong>. Sites are generated by a short <strong class="emphasis-2">Python script</strong> that you write to customise what <strong class="emphasis-2">pages</strong> it loads, which <strong class="emphasis-2">templates</strong> it uses, and what <strong class="emphasis-2">data</strong> it passes to them, or create custom <strong class="emphasis-2">filters</strong>, <strong class="emphasis-2">tests</strong> and more. </p></li><li><p>Supports <strong class="emphasis-2">YAML front matter</strong> for pages. It can be accessed using indexing syntax. </p></li><li><p><strong class="emphasis-2">Indexes</strong> can be sorted using a function, iterated and can index any directory, recursively or not. They can also <strong class="emphasis-2">transform URLs</strong> to make them end in ".html". </p></li><li><p><strong class="emphasis-2">Object-oriented</strong> design. The same objects used in that script can also be passed to the templates. </p></li><li><p>Any <strong class="emphasis-2">markup language</strong> can be used, as long as it can be converted to HTML. You just need to configure a filter for it. You can even mix multiple markup languages in the same site. </p></li><li><p>Ships with a light <strong class="emphasis-2">markdown</strong> implementation. </p></li><li><p>Easy to use for <strong class="emphasis-2"><em class="emphasis-1">both</em> programmers and non-programmers</strong>. While you do need a script, you can also use an off-the-shelf one. </p></li><li><p><strong class="emphasis-2">Themes</strong> can be <em class="emphasis-1">exactly how you want</em>. </p></li><li><p>Keeping <strong class="emphasis-2">static files</strong> is easy, because indexes can be static. </p></li><li><p>Static files are always <strong class="emphasis-2">binary</strong> and not templated. The same happens for files that can't be decoded. </p></li><li><p><strong class="emphasis-2">URL</strong>-based definitions. Pages are added using the URL that will be used to access them. </p></li><li><p>Reinforces the <strong class="emphasis-2">web</strong> as a <strong class="emphasis-2">publishing medium</strong>. Static sites are not for everyone, but if you want to <strong class="emphasis-2">publish</strong> something, it's the best way. </p></li><li><p>And GitHub will give you <strong class="emphasis-2">free hosting</strong>, because it's static and <em class="emphasis-1">very cheap to serve</em>. Roundabout-host will soon offer this as well (<code>your.site.roundabout-host.com</code>). </p></li><li><p>It's <strong class="emphasis-2">free software</strong> and available under the <strong class="emphasis-2">GPLv3</strong>. </p></li><li><p><strong class="emphasis-2">No JavaScript</strong> is required, but it can of course be used if you want. </p></li><li><p>Decently <strong class="emphasis-2">fast</strong>: even if you've got a huge site, it should not take more than _30 seconds_. Local rebuilding will also be added. And it's still much faster than any dynamic site. </p></li><li><p>Beautiful logging thanks to colorama. </p></li><li><p>Great for educational use; you can learn <strong class="emphasis-2">Python</strong>, <strong class="emphasis-2">HTML</strong>, <strong class="emphasis-2">CSS</strong>, <strong class="emphasis-2">JavaScript</strong>, and <strong class="emphasis-2">Jinja2</strong> all at once. </p></li><li><p>You can <strong class="emphasis-2">make your site</strong> in <em class="emphasis-1">an hour</em>, and then it's time to focus on writing what you want to publish. </p></li><li><p>If you see fit, it's easy to <strong class="emphasis-2">convert</strong> to a dynamic site. A <strong class="emphasis-2">Flask implementation</strong> is planned. </p></li></ul><h2>Minimal example</h2><pre data-language="python">import string from datetime import datetime import string import ampoule_ssg as ampoule from ampoule_ssg import markdown # Create a site object. This is where we are adding pages to. The argument is the directory # where the site will be built. site = ampoule.Site("my_site") # Use this as "| markdown" in Jinja2 templates to convert any Markdown source to HTML. @site.filter("markdown") def markdown_filter(text): return markdown.markdown2html(text) # Make the URLs web-friendly and make it end in ".html" so it will be correctly formatted # by dumb servers. def article_url(url): url = url.lower().rpartition(".")[0] new_url = "" for i in url: if i in string.ascii_lowercase: new_url += i elif i in string.whitespace: new_url += "-" return new_url + ".html" # Set context that will be passed to all templates. You can still override this. site.context["timestamp"] = datetime.now() site.context["ampoule"] = ampoule # Add the index of articles. In the template, we're looping over it to list them all. articles = ampoule.Index("articles", url_transform=article_url, sort_by=lambda x: x.date) # This makes it take all indexed files and put them under the /articles URL, keeping the # index's URL transformation and placing all of them in the article.html template. This # will be passed as "document" to the template. site.add_from_index(articles, "/articles", "article.html") # Create the main page which has access to the index so it can list all articles. main_page = ampoule.Page(site, "home.html", articles=articles) # Add the page. Note how we're binding it to a path; it will automatically be set as # index.html in that directory, and the URL is site-relative, not the OS root. site.add_page("/", main_page) # Add static files using a recursive static index. It will add all files in the static # directory and all its subdirectories, without putting them into templates. You could # still use them in templates, so you can make a photo gallery or something. site.add_from_index( # We're excluding Markdown files because we're using them as licence information # for when the site is distributed together with the fonts. You can exclude any # file you want using regex. ampoule.Index("static", recursive=True, exclude=r"\.md$"), "/static", # There is no template, because the index is static. static=True ) # Makes Ampoule take all pages and put them in a directory. site.build() </pre><h2>More information</h2><h3>Name origin</h3><p>An ampoule is smaller than a flask. Because it is related to Flask (it uses Jinja2) but is a much smaller static version of it, the name makes sense. </p><h3>Why not use Jekyll, Hugo, or Pelican?</h3><h4>Jekyll</h4><p>Jekyll is Ruby, and it's very much Ruby, in fact, it's 17,000 lines of Ruby. Configuration is done only in YAML. It's hard to extend, and the plugin and theme systems are overengineered. It's also limited (no OOP, not even multiple index directories). And for the Flask users like me, it uses a template language that is very similar to Jinja2, but it's not Jinja2, so templates aren't reusable. I've actually used Jekyll, and it's not nice. </p><h4>Hugo</h4><p>Hugo is actually pretty interesting, but it's not as flexible, because it's not scriptable, and a compiled language is not appropriate for this. Did I mention it also has an overengineered theme system? And it's even larger, a whooping 133,000 lines of Go! You can never know it all. </p><h4>Pelican</h4><p>Pelican is opinionated and relies on plugins for everything. It's extremely limited, in fact it can only do blogs. It also can't be portable and implemented in Flask. </p><h3>Why even use a static site generator?</h3><p>You've got two other options. Let's examine them. </p><h4>Dynamic sites</h4><ul><li><p>bloated; </p></li><li><p>slow; </p></li><li><p>requires smart server; </p></li><li><p>requires maintenance; </p></li><li><p>requires security; </p></li><li><p>requires a database; </p></li><li><p>hard to post content; </p></li><li><p>databases can't be managed with git; </p></li><li><p>hard to import content; </p></li><li><p>no free hosting; </p></li></ul><h4>Static sites</h4><ul><li><p>hard to manage layouts; </p></li><li><p>hard to list the content; </p></li><li><p>hard to update indexes; </p></li><li><p>no support for metadata; </p></li><li><p>markup languages must be manually converted; </p></li></ul><p>With a <em class="emphasis-1">generated</em> static site, you get the best of both worlds. It's the best publishing platform, because it's just files. </p><h2>How to install</h2><p>Please note that this is not yet available on PyPI. For now you'll need to download the code (ideally using git) and install it with <code>pip</code> as a local package by giving it the path to the directory containing <code>setup.py</code>. </p><h2>Full documentation</h2><p>To demonstrate just how easy it is, the docs can all fit on one page. </p><h3>class <code>ampoule_ssg.Site</code></h3><p><code>Site</code> is the main class of Ampoule; it represents a single website. It is responsible for handling added pages, the template engine and features, as well as building it. </p><h4>def <code>__init__(self, build_dir: typing.Union[str, bytes, os.PathLike], template_dir: typing.Union[str, bytes, os.PathLike] = "templates")</code></h4><p>Create a new site object. <code>build_dir</code> is the directory where the site will be built. <code>template_dir</code> is the directory where the templates are stored. Both are relative to the script current working directory. </p><h4>def <code>add_page(self, location: typing.Union[str, bytes, os.PathLike], page: typing.union[Static, Page])</code></h4><p>Add a page object to the site at the server-relative URL <code>location</code>. The page object can be either a <code>Static</code> or a <code>Page</code>. </p><h4>def <code>add_from_index(self, index: Index, location: typing.Union[str, bytes, os.PathLike], template: str = None, **kwargs)</code></h4><p>Add all pages from an index to the site with the root at the server-relative URL <code>location</code>. The pages will be rendered with the template <code>template</code> and the context <code>kwargs</code>. will be passed to all of them. If the index is static, the pages will not be rendered with a template, but rather copied as-is. </p><p>For each page, the <code>document</code> object found in the index will be passed to the template under that name. </p><h4>def <code>filter(self, name: str)</code></h4><p>A decorator that registers a filter function with the site. The function should take at least one argument, the value to be filtered, and return the filtered value. </p><h4>def <code>test(self, name: str)</code></h4><p>A decorator that registers a test function with the site. The function should take at least one argument, the value to be tested, and return a boolean. </p><h4>def <code>build(self)</code></h4><p>Build (save) the site to the build directory it was constructed with. This will create the directory if it does not exist, clear it (but not delete it) and then write all the pages. </p><h4><code>context: dict[str, typing.Any]</code></h4><p>A dictionary containing names that are available to all pages. It can be overriden by the page's context or modified at any time. </p><h3>class <code>ampoule_ssg.Page(str)</code></h3><p><code>Page</code> is a class that represents a single page on the site. A page is composed of a template, a document and a context. </p><h4>def <code>__new__(cls, site: Site, template: str, document: Document = None, **kwargs)</code></h4><p>Create a new page object. <code>site</code> is the site object that the page belongs to. <code>template</code> is the template the document will be put in. <code>document</code> is the document object that will be passed to the template. <code>kwargs</code> are names that will be available to the template for additional context. </p><p>If there's no document, it will not be available to the template. This is useful for single pages with fully static content, like a contact page. </p><h3>class <code>ampoule_ssg.Static(bytes)</code></h3><p><code>Static</code> is a class that represents a single static file on the site. A static file is just the content, in binary format, and it doesn't use templating. </p><h4>def <code>__new__(cls, site: Site, document: Document)</code></h4><p>Create a new static object. <code>site</code> is the site object that the static file belongs to. <code>document</code> is the document object that will be written to the file; it can contain any encoding, even text, and will be written as-is. </p><h3>class <code>ampoule_ssg.Index</code></h3><p>An index is a collection of documents that can be iterated over or added to a site using a common template (see <code>ampoule_ssg.Site.add_from_index</code>). </p><h4>def <code>__init__(self, directory: typing.Union[str, bytes, os.PathLike], recursive: bool = False, url_transform: typing.Callable = lambda x: x, sort_by: typing.Callable = lambda x: x.file_name, exclude: typing.Union[str, NoneType] = None, static: bool = False)</code></h4><p>Create a new index. <code>directory</code> is the directory to get content from. If <code>recursive</code> is true, the whole tree of that directory will be indexed. <code>url_transform</code> is a function that will be applied to the file name to get the new file name. Generally you want to set it so it makes them end in <code>.html</code> so dumb servers can serve them correctly. However, for static files you most likely will not set it. <code>sort_by</code> is the key after which to sort the documents after they are indexed; by default it is the file name. <code>exclude</code> is a regular expression that will be used to exclude files from the index. If the index is <code>static</code>, all documents will be parsed as-is, without removing front matter. </p><h4>def <code>__iter__(self)</code></h4><p>Return an iterator for the index. </p><h4>def <code>__next__(self)</code></h4><p>Get the next document in the index. </p><h4>def <code>__repr__(self)</code></h4><p>Return a string representation of the index. It contains the directory and the names of the documents in it. </p><h4>def <code>__len__(self)</code></h4><p>Return the number of documents in the index, that is, its length. </p><h3>class <code>ampoule_ssg.Document</code></h3><p>A document is a file, not rendered, but available for use. It is what is passed to the template as <code>document</code> for processing. Generally, you won't create these yourself, but rather use them as they are returned by an index. However, if you do need one, you can create it manually and pass it to a page. </p><p>Documents will parse YAML front matter for textual files, unless disabled. The front matter is available as an attribute of the document, and can be accessed using indexing syntax. </p><h4>def <code>__init__(self, file_name: typing.Union[str, bytes, os.PathLike], url_transform: typing.Callable = lambda x: x, front_matter_enabled: bool = True)</code></h4><p>Create a new document. <code>file_name</code> is the name of the file. <code>url_transform</code> is a function that will be applied to the file name to get the new file name; it has the same meaning as in the <code>Index</code>. <code>front_matter_enabled</code> is a boolean that determines whether the document will parse YAML front matter. </p><h4>def <code>__repr__(self)</code></h4><p>Return a string containing <code>Document</code> and the file name. </p><h4>def <code>__getitem__(self, item: str)</code></h4><p>Access the document's front matter. If front matter is disabled or not available, this will never work. </p><h4>def <code>__setitem__(self, item: str, value: typing.Any)</code></h4><p>Change the document's front matter. It works even if it wasn't parsed, because YAML behaves like a dictionary. </p><h4>def <code>__delitem__(self, item: str)</code></h4><p>Delete an item from the document's front matter. </p><h4>def <code>__contains__(self, item: str)</code></h4><p>Check if an item is in the document's front matter. </p> </article> </main> <footer> <p>Page generated on Monday, 29 April 2024 at 09:45:48</p> <p>© Roundabout developer</p> </footer> </body> </html>
projects/index.html
@@ -0,0 +1,51 @@
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Projects</title> <link rel="stylesheet" href="/static/style.css"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> </head> <body> <header> <nav> <ul> <li><a href="/">Home</a></li> <li><a href="/projects">Projects</a></li> <li><a href="/index">Index</a></li> <li><a href="https://roundabout-host.com/roundabout">Roundabout-host</a></li> </ul> <ul> <li><a href="mailto:root@roundabout-host.com" id="mail-link">root@roundabout-host.com</a></li> </ul> </nav> </header> <main> <h1>Projects</h1> <article class="content-area"> <h2><a href="/projects/ampoule.html" class="article-title">Ampoule</a></h2> <p><p>Ampoule is a lightweight, simple yet flexible, static site generator written in Python. It uses Jinja2 for templating. </p></p> </article> <article class="content-area"> <h2><a href="/projects/roundabout.html" class="article-title">The roundabout</a></h2> <p><p>The roundabout is a <strong class="emphasis-2">git repository hosting</strong> server software. It is designed to be a complete alternative to GitHub, GitLab, BitBucket, Gogs/Gitea/Forgejo, and other similar services. It is still in development and not yet ready for production use. As of version 0.2.0 development stage, it supports: </p></p> </article> </main> <footer> <p>Page generated on Monday, 29 April 2024 at 09:45:48</p> <p>© Roundabout developer</p> </footer> </body> </html>
projects/roundabout.html
@@ -0,0 +1,86 @@
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title> The roundabout </title> <link rel="stylesheet" href="/static/style.css"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> </head> <body> <header> <nav> <ul> <li><a href="/">Home</a></li> <li><a href="/projects">Projects</a></li> <li><a href="/index">Index</a></li> <li><a href="https://roundabout-host.com/roundabout">Roundabout-host</a></li> </ul> <ul> <li><a href="mailto:root@roundabout-host.com" id="mail-link">root@roundabout-host.com</a></li> </ul> </nav> </header> <main> <h1 class="project-title"> <span>The roundabout</span> <a href="https://roundabout-host.com/roundabout/roundabout">Repository</a> </h1> <article class="content-area"> <p>The roundabout is a <strong class="emphasis-2">git repository hosting</strong> server software. It is designed to be a complete alternative to GitHub, GitLab, BitBucket, Gogs/Gitea/Forgejo, and other similar services. It is still in development and not yet ready for production use. As of version 0.2.0 development stage, it supports: </p><ul><li><p>User registration </p></li><li><p>Repository creation </p></li><li><p>User access control </p></li><li><p>Smart Git over HTTP(S) protocol </p></li><li><p>Web interface for repository browsing </p></li><li><p>User profiles and social features </p></li><li><p>Forum for repositories, useful for issue tracking </p></li><li><p>Notifications, including email notifications </p></li><li><p>Pull requests </p></li><li><p>Favourite repositories (subscriptions) </p></li><li><p>Commit views and diffs </p></li><li><p>Themeing </p></li></ul><p>For now, it doesn't support, but certainly will before the 1.0.0 release: </p><ul><li><p>Code review features (like comments on commits, for now you'll use the forum), something like Google Docs comments is planned </p></li><li><p>Static site hosting </p></li><li><p>CI/CD </p></li><li><p>Wiki </p></li><li><p>API </p></li><li><p>Native clients </p></li><li><p>Git over SSH or other protocols </p></li><li><p>Repository metadata </p></li><li><p>Decentralization </p></li><li><p>Packaging </p></li><li><p>Code syntax highlighting </p></li><li><p>Any kind of search </p></li><li><p>Any kind of statistics </p></li><li><p>Web-based code editing </p></li><li><p>Any kind of integration with other services </p></li><li><p>Users changing passwords </p></li><li><p>Admin panel </p></li></ul><p>The roundabout is written in flask, a Python web framework. It uses SQLAlchemy for database management. For light UI interactivity it uses htmx. </p><p>The official instance of the roundabout, roundabout-host, is hosted at <a href="https://roundabout-host.com">roundabout-host.com</a>. Anyone can register and create repositories there. However, we ask that you do not rely on it and always keep your repos locally as well. In the future it may require payment for some features, but <strong class="emphasis-2">the program it runs will always be free</strong>. Payment will not be required to lift artificial limitations, but to support server-intensive features like CI/CD. </p><p>The roundabout program is licensed under the AGPL licence, version 3.0 or, at your option, any later version. </p> </article> </main> <footer> <p>Page generated on Monday, 29 April 2024 at 09:45:48</p> <p>© Roundabout developer</p> </footer> </body> </html>
static/fonts/RoLeagueGothic-Condensed.woff2
static/fonts/RoLeagueGothic-CondensedItalic.woff2
static/fonts/RoLeagueGothic-Italic.woff2
static/fonts/RoLeagueGothic-Regular.woff2
static/fonts/SourceCodeVF-Italic.otf.woff2
static/fonts/SourceCodeVF-Upright.otf.woff2
static/fonts/SourceSans3VF-Italic.otf.woff2
static/fonts/SourceSans3VF-Upright.otf.woff2
static/fonts/SourceSerif4Variable-Italic.otf.woff2
static/fonts/SourceSerif4Variable-Roman.otf.woff2
static/style.css
@@ -0,0 +1,272 @@
@font-face { font-family: "Romanian League Gothic"; src: url("/static/fonts/RoLeagueGothic-Regular.woff2") format("woff2"); font-weight: normal; font-style: normal; } @font-face { font-family: "Romanian League Gothic Condensed"; src: url("/static/fonts/RoLeagueGothic-Condensed.woff2") format("woff2"); font-weight: normal; font-style: normal; } @font-face { font-family: "Source Serif"; src: url("/static/fonts/SourceSerif4Variable-Roman.otf.woff2") format("woff2-variations"); } @font-face { font-family: "Source Serif"; src: url("/static/fonts/SourceSerif4Variable-Italic.otf.woff2") format("woff2-variations"); font-style: italic; } @font-face { font-family: "Source Sans"; src: url("/static/fonts/SourceSans3VF-Upright.otf.woff2") format("woff2-variations"); } @font-face { font-family: "Source Sans"; src: url("/static/fonts/SourceSans3VF-Italic.otf.woff2") format("woff2-variations"); font-style: italic; } @font-face { font-family: "Source Code"; src: url("/static/fonts/SourceCodeVF-Upright.otf.woff2") format("woff2-variations"); } @font-face { font-family: "Source Code"; src: url("/static/fonts/SourceCodeVF-Italic.otf.woff2") format("woff2-variations"); font-style: italic; } body { line-height: 1.5rem; font-family: "Source Serif", serif; font-weight: 375; font-size: 1.125rem; min-height: 100vh; margin: 0; display: flex; flex-direction: column; align-items: center; background: #eceff1; font-variation-settings: "opsz" 14; } a { color: #009688; text-decoration-thickness: 0.125em; } header { width: 100%; font-family: "Source Sans", sans-serif; position: sticky; top: 0; background: #eceff1; display: flex; justify-content: center; } nav { width: min(800px, 100%); display: flex; justify-content: space-between; align-items: center; box-sizing: border-box; height: 4rem; } nav > ul { font-family: "Romanian League Gothic", sans-serif; display: flex; list-style: none; padding: 0; margin: 0; align-items: center; gap: 1rem; font-weight: normal; font-size: 1.75rem; line-height: 2rem; text-transform: uppercase; } nav > ul > li > a { font-weight: normal; text-decoration: none; color: #000000; text-decoration-thickness: 0.125em; } nav > ul > li > a:hover { text-decoration: underline; } footer { width: min(800px, 100%); padding: 1rem; box-sizing: border-box; font-family: "Source Sans", sans-serif; } main { width: min(800px, 100%); flex: 1 0 auto; gap: 1rem; display: flex; flex-direction: column; } .content-area { box-shadow: 0 0 1px #00000028, 0 0 2px #00000020, 0 2px 4px #00000010, 0 2px 18px -6px #00000010; padding: 1rem; box-sizing: border-box; background: #ffffff; } h1 { font-family: "Romanian League Gothic Condensed", sans-serif; font-weight: normal; font-size: 4rem; line-height: 5rem; margin: 0; text-align: center; border-bottom: 0.5px solid #000000; } h2 { font-family: "Romanian League Gothic", sans-serif; font-weight: normal; font-size: 2.75rem; line-height: 3rem; margin: 0; } h3 { font-family: "Source Sans", sans-serif; font-weight: 725; font-size: 1.875rem; line-height: 3rem; margin: 0; } h4 { font-family: "Source Sans", sans-serif; font-weight: 800; font-size: 1.5rem; line-height: 2rem; margin: 0; } h5 { font-family: "Source Sans", sans-serif; font-weight: 800; font-size: 1.25rem; line-height: 2rem; margin: 0; } h6 { font-family: "Source Sans", sans-serif; font-weight: 900; font-size: 1.125rem; line-height: 2rem; margin: 0; } .article-title { text-decoration: none; text-align: center; color: #009688; display: block; width: 100%; } #mail-link { color: #009688; text-transform: none; } pre, code, kbd, samp, var { font-family: "Source Code", monospace; } pre { overflow-x: auto; padding: 1rem; background: #263238; color: #ffffff; color-scheme: dark; font-size: 1rem; line-height: 1.25rem; } .project-title { display: flex; align-items: center; justify-content: space-between; gap: 1rem; width: 100%; border: none; } .project-title > span { padding: 0.5rem; } .project-title > a { text-decoration: none; background: #009688; color: #ffffff; padding: 0 1rem; border: 4px solid #00796B; box-sizing: border-box; line-height: 1.25em; font-size: 0.875em; font-family: "Romanian League Gothic Condensed", sans-serif; align-self: stretch; display: flex; align-items: center; justify-content: center; min-width: 25%; transition: 400ms; } .project-title > a:hover { border-width: 8px; } strong, em { font: inherit; } .emphasis-1 { font-style: italic; } .emphasis-2 { font-weight: 625; } .emphasis-3 { font-variant: small-caps; font-synthesis-small-caps: none; } #article-date, .home-article-date { font-family: "Source Code", sans-serif; font-feature-settings: "zero" on; font-weight: 625; text-align: center; }