first pass to move project rendering to sphinx and integrating with the rest of ejs node site

This commit is contained in:
len0rd 2022-10-06 14:05:49 -04:00
parent 3c6c9bbb50
commit 1d085516da
12 changed files with 151 additions and 37 deletions

1
assets/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
projects/

View file

@ -1,5 +0,0 @@
/**
* AutoScrollspy v 0.2.0
* https://github.com/psalmody/dynamic-scrollspy
*/
!function($){$.fn.DynamicScrollspy=function(opts){function encodeHTML(value){return $("<div></div>").text(value).html()}function selectAllH(){for(var st=[],i=self.options.tH;i<=self.options.bH;i++)st.push("H"+i);return $(st.join(",")).not(self.options.exclude)}function randID(){function rand(){r=Math.floor(900*Math.random())+100}var r;for(rand();self.rands.indexOf(r)>=0;)rand();return self.rands.push(r),r}function genIDs(){selectAllH().prop("id",function(){return""===$(this).prop("id")?$(this).prop("tagName")+randID():$(this).prop("id")})}function checkIDs(){var missing=0;if(selectAllH().each(function(){if(""===$(this).prop("id"))missing++;else if($('[id="'+$(this).prop("id")+'"]').length>1)throw new Error("DynamicScrollspy: Error! Duplicate id "+$(this).prop("id"))}),missing>0){var msg="DynamicScrollspy: Not all headers have ids and genIDs: false.";throw new Error(msg)}return missing}function showTesting(){selectAllH().append(function(){return" ("+$(this).prop("tagName")+", "+$(this).prop("id")+")"})}function makeTree(){var tree=self.tree;return $("H"+self.options.tH).not(self.options.exclude).each(function(){tree[$(this).prop("id")]={dstext:encodeHTML($(this).text()),jqel:$(this)}}),self.options.tH+1<=self.options.bH&&itCreateTree(tree),tree}function itCreateTree(what){for(var k in what)if(""!==k&&"dstext"!=k&&"jqel"!=k){var lvl=Number($("#"+k).prop("tagName").replace("H",""));if(lvl>=self.options.bH)return!1;$("#"+k).nextUntil("H"+lvl).filter("H"+(lvl+1)).not(self.options.exclude).each(function(){what[k][$(this).prop("id")]={dstext:encodeHTML($(this).text()),jqel:$(this)}}),lvl<self.options.bH&&itCreateTree(what[k])}}function renderTree(){var ul=$('<ul class="nav '+self.options.ulClassNames+'"></ul>');return self.append(ul),$.each(self.tree,function(k){var c=self.tree[k],li='<li id="dsli'+k+'" class="nav-item"><a href="#'+k+'" class="nav-link">'+c.dstext+"</a></li>";ul.append(li),itRenderTree(self.tree[k])}),self}function itRenderTree(what){if(Object.keys(what).length<3)return!1;var parent=$("#dsli"+what.jqel.prop("id")),ul=$("<ul class='nav child'></ul>");parent.append(ul);for(var k in what)if("dstext"!=k&&"jqel"!=k){var c=what[k];ul.append('<li id="dsli'+k+'" class="nav-item"><a href="#'+k+'" class="nav-link">'+c.dstext+"</a></li>"),itRenderTree(what[k])}}function init(){if(self.isinit===!1){if(self.options.genIDs?genIDs():checkIDs(),self.options.testing&&showTesting(),makeTree(),renderTree(),self.options.affix&&"function"==typeof self.children("ul").affix){var ul=self.children("ul");self.children("ul").affix({offset:{top:function(){var c=ul.offset().top,d=parseInt(ul.children(0).css("margin-top"),10),e=$(self).height();return this.top=c-e-d},bottom:function(){return this.bottom=$(self).outerHeight(!0)}}})}$("body").attr("data-spy","true").scrollspy({target:"#"+self.prop("id"),offset:self.options.offset}),self.isinit=!0}else makeTree(),renderTree(),$('[data-spy="scroll"]').each(function(){$(this).scrollspy("refresh")});return self}if(opts="undefined"==typeof opts?{}:opts,this.isinit="undefined"!=typeof this.isinit&&self.isinit,"destroy"==opts)return this.isinit=!1,this.empty(),this.off("activate.bs.scrollspy"),$("body").removeAttr("data-spy"),this;this.options=$.extend({},{affix:!0,tH:2,bH:6,exclude:!1,genIDs:!1,offset:100,ulClassNames:"hidden-print",activeClass:"",testing:!1},this.options,opts);var self=this;return this.tree={},this.rands=[],init()}}(jQuery);

View file

@ -5,7 +5,7 @@
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
"prestart": "node prestart.js" "prestart": "./server_startup.sh"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

3
pip-requirements.txt Normal file
View file

@ -0,0 +1,3 @@
sphinx
sphinx-rtd-theme
sphinxcontrib-youtube

21
project_writeups/conf.py Normal file
View file

@ -0,0 +1,21 @@
# Sphinx docs configuration for building project documentation
from datetime import datetime
import os
project = "Lenords Projects"
author = "lenord"
copyright = f"{datetime.now().year}, lenordsNet"
extensions = [
"sphinxcontrib.youtube",
]
root_doc = "contents"
html_theme = "sphinx_rtd_theme"
html_additional_pages = {
"index": os.path.abspath(os.path.join("..", "views", "pages", "index.ejs"))
}
# html_static_path = ["_static"]
pygments_style = "sas"

View file

@ -0,0 +1,9 @@
Lenords project documentation
=============================
.. toctree::
:maxdepth: 2
:caption: Contents:
ls1synth
myWebsite

View file

@ -0,0 +1,36 @@
.. _ls1synth:
LS-1: Modular Synth
===================
The LS-1 is a modular oscillator and sequencer, and includes 2 LFOs (low-frequency oscillators), one external oscillator, and the oscillator attached to the sequencer. The sequencer itself is made up of a counter which acts as a LFO/clock divider, and dual muxes to select the feedback resistance and led to display. The counter outputs and mux select inputs have ports on the front-panel allowing the user to mix and match LFO divisions with mux selects, thus creating custom sequences.
This build included a lot of firsts for me. This is the first time Ive used Eagle to create a PCB/schematic (which should honestly be considered an atrocity given I am a Computer Engineering student), my first in creating a metal case using a water jet, and in general this is my first large-scale hobby project.
Case
----
Design
^^^^^^
Originally I was hoping to pack everything into a 1U 19″ package, using the case of an old network switch I had laying around, but I soon realized to include all the I/O I wanted I would have to increase the size, so I made the logical step up to 2U. Even with the increased size, its a pretty cozy fit for the front panel. I did some prototype configurations for different control sections using cardboard and the components I was planning on using, testing which layout I found to be the most natural.
.. image:: ../assets/img/writeup/ls1synth/case-1-sm.jpg
Following this, I designed the case in Autodesk Inventor (since its free to students, Im more of a Solidworks guy personally). It had been a few years since I had needed to touch Inventor, so it was a little rough, but I got the job done. Going in I knew I was planning on cutting this on my universitys water jet, so I built it all off a single sketch, taking into account how the faces would link together. I also decided to make a timelapse of the process, mostly for my own enjoyment, but feel free to watch and mock my terrible CAD skills:
.. youtube:: iJbLcks4f_g
This timelapse shows the majority of the design, but not all of it. Following this I consulted with my ME friend about how to build a proper metal case. He suggested cutting small circles where corners would fold to make sure excess material didn't get in the way. Before the water jet, I also made some slight modifications the the front panel layout - mostly related to spacing between components.
Build
^^^^^
With the panel designed, I simply needed to export the sketch face to a format the water jet would understand. I purchased a decently high gauge sheet of steel (maybe 12? I cannot remember) from my university and they cut it to roughly the dimensions I would need. Despite being a student, many of the resources I used for this part of the project are open to the public. If you're looking to do something similar, it never hurts to check with your local university's engineering/technology department to see what resources they have available.
.. image:: ../assets/img/writeup/ls1synth/case-2-sm.jpg
With the metal sheet cut, I was ready to cut out the case with the water jet. My university charges for how much time you use a product, and since the case is not too large, this only cost me around $20. Putting the case portion of the project at ~$30 total! This was my first time using a water jet, so it was a very big deal for me. Computer Engineering is fun and all, but you don't get enough chances to play with big toys like other disciplines do.
![Metal sheet cut and ready for the jet](/img/writeup/ls1synth/case-waterjet.mp4)

View file

@ -0,0 +1,53 @@
.. myWebsite:
My Website
==========
Starting out with this website, I had essentially no knowledge of modern web technologies. I knew that I wanted something modern but also easy to maintain that I could use well into the future.
The end result is the site you see here. By no means perfect or beautiful, but functional and a place where I can store guides mainly for my benefit. But maybe for your benefit too? I certainly dont know who's reading this ¯\\\_(ツ)_/¯
As I'm writing this page after the fact (about a year since originally making this site), I'll likely glaze over a lot of the details.
Technologies Used
-----------------
- `Node <https://nodejs.org/>`_ for quick 'n easy webserver creation
- `NPM <https://www.npmjs.com>`_ for a bunch of support packages
- `ExpressJS <https://expressjs.com>`_ with `ejs <https://ejs.co/>`_ for amped up static pages. Express is great for beginners that want a simple framework for a static website with no code duplication
- `Bootstrap <https://getbootstrap.com>`_ to make it all pretty
- Other stuff
Express
-------
Originally I started this site as a pure html/bootstrap affair. This worked for all of 2 days until I got sick of copying and pasting code all over the place. While I had no desire to maintain duplicate copies of code, I was even less interested in using some massive overkill framework (as an embedded dev, I have a need for speed). Low and behold: ExpressJS! The perfect minimal framework solution for my problem. Express has a concept of 'pages' and 'partials'. A page defines the overall structure of a static webpage (say my home page). Partials define chunks/components of that page that are shared in other locations. So for example, all the html for my navigation/ header bar has its own partial, as does the footer. Then in a page, to use this content you can simply add a ``<% include`` as if you were writing a C program! Express was speaking my language.
Static Project Pages [old]
--------------------------
.. note::
I've replaced this implementation with Sphinx docs
The bulk of the effort for me was sunk into generating the project writeup pages (like the page you're reading this off of right now!). I wanted them to be simple static text, images and video. But I didn't want the complexity of using a whole framework like wordpress, and I definitely wasn't into the idea of writting everything in html. I wanted my writeups to be in a portable format I could easily migrate or use in other places in the future.
Given these requirements I thought it best to write about all of my projects in markdown. I've used markdown for years and like its readability and easy syntax. To convert my markdown to HTML, I grabbed `showdown <https://github.com/showdownjs/showdown>`_ . Showdown does it's job well and has some hooks (called 'extensions') that made it easier for me to get the formatting jusssst right. At present the only extension I've created helps make the title/H1 of each writeup nice and big (ie: look at those big 'My Website' letters up top). All the showdown generator stuff lives in ``prestart.js`` which is run before the server is started so the markdown is generated once and can then be served statically for all time.
I saved showdown's resulting files as ExpressJS partials. These partials are linked to a template page which adds the header, footer and table of contents you see here. Then, any requests that contain ``/projects`` actually load the ``project_template`` page with the requested project-name partial. Express makes this all surprisingly simple (I say after struggling with it for hours):
.. code-block:: javascript
if (pathname.includes('projects') && page !== 'index') {
// projects has a custom template that is used for all projects
// so we need to change the pathname that the renderer is using
// that template:
pathname = pathname.substr(0, pathname.lastIndexOf(page));
pathname += 'project_template'
// provide the pagename for project_template to use for main content
page = 'partials/md/' + page;
}

View file

@ -1,42 +1,35 @@
const PORT = 8090; const PORT = 8090;
var express = require('express'); var express = require('express');
const path = require("path");
var app = express(); var app = express();
console.log('Starting express server on port ' + PORT); console.log('Starting express server on port ' + PORT);
// set the view engine to ejs // set the view engine to ejs
app.set('view engine', 'ejs'); app.set('view engine', 'ejs');
app.set("views", path.join(__dirname, "views"));
// add folder for static content: // add folder for static content:
app.use(express.static(__dirname + '/assets')); app.use(express.static(path.join(__dirname, 'assets')));
app.get(/\/.*/, function (req, res) { function getRootPage(req, res) {
let pathname = 'pages' + req.path; let pageName = req.params["pageName"];
let page = pathname.substr(pathname.lastIndexOf('/') + 1); if (pageName === null || pageName === undefined) {
pageName = "index";
if (pathname !== null && pathname !== undefined) {
if ((pathname)[pathname.length - 1] === '/') {
pathname += 'index';
page = 'index';
}
if (pathname.includes('projects') && page !== 'index') {
// projects has a custom template that is used for all projects
// so we need to change the pathname that the renderer is using
// that template:
pathname = pathname.substr(0, pathname.lastIndexOf(page));
pathname += 'project_template'
// provide the pagename for project_template to use for main content
page = 'partials/md/projects/' + page;
}
else if (pathname.includes('recipes') && page !== 'index') {
pathname = pathname.substr(0, pathname.lastIndexOf(page));
pathname += 'recipe_template'
// provide the pagename for project_template to use for main content
page = 'partials/md/recipes/' + page;
}
} }
console.log('request for path: ' + pathname + ', and page: ' + page); res.render(path.join("pages", pageName));
}
app.get("/:pageName", getRootPage);
app.get("/", getRootPage);
app.get("/projects/:projectName", (req, res) => {
res.sendFile(req.params["projectName"], { root: path.join(__dirname, "assets", "projects") })
});
app.get("/recipes/:recipeName", (req, res) => {
let pathname = path.join("pages", "recipes", "recipe_template");
let page = path.join("partials", "md", "recipes", req.params["recipeName"])
res.render(pathname, { "page": page }); res.render(pathname, { "page": page });
}); });

4
server_startup.sh Executable file
View file

@ -0,0 +1,4 @@
# build project documentation
sphinx-build project_writeups/ assets/projects -b html
# generate recipe ejs partials
node prestart.js

View file

@ -77,7 +77,7 @@
</p> </p>
</div> </div>
<div class="card-footer pb-3"> <div class="card-footer pb-3">
<a href="projects/darkstar" class="btn btn-outline-light">Read More</a> <a href="projects/darkstar.html" class="btn btn-outline-light">Read More</a>
<a href="https://github.com/len0rd/darkstar_copter" class="card-link card-soft-link">See <a href="https://github.com/len0rd/darkstar_copter" class="card-link card-soft-link">See
Code</a> Code</a>
</div> </div>
@ -92,7 +92,7 @@
result: a portable and rock solid desk with a couple of flaws</p> result: a portable and rock solid desk with a couple of flaws</p>
</div> </div>
<div class="card-footer pb-3"> <div class="card-footer pb-3">
<a href="projects/palletDesk" class="btn btn-outline-light">Read More</a> <a href="projects/palletDesk.html" class="btn btn-outline-light">Read More</a>
</div> </div>
</div> </div>
</div> </div>
@ -106,7 +106,7 @@
function in a very specific way</p> function in a very specific way</p>
</div> </div>
<div class="card-footer pb-3"> <div class="card-footer pb-3">
<a href="projects/myWebsite" class="btn btn-outline-light">Read More</a> <a href="projects/myWebsite.html" class="btn btn-outline-light">Read More</a>
<a href="https://github.com/len0rd/personal-website" class="card-link card-soft-link">See <a href="https://github.com/len0rd/personal-website" class="card-link card-soft-link">See
Code</a> Code</a>
</div> </div>
@ -123,7 +123,7 @@
but helped me learn a whole ton along the way</p> but helped me learn a whole ton along the way</p>
</div> </div>
<div class="card-footer pb-3"> <div class="card-footer pb-3">
<a href="projects/ls1synth" class="btn btn-outline-light">Read More</a> <a href="projects/ls1synth.html" class="btn btn-outline-light">Read More</a>
<a href="https://github.com/len0rd/LS1-sequencer" class="card-link card-soft-link">See <a href="https://github.com/len0rd/LS1-sequencer" class="card-link card-soft-link">See
Code</a> Code</a>
</div> </div>

View file

@ -1,2 +1 @@
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>