Skip to content

Instantly share code, notes, and snippets.

@kmafeni04
Last active June 8, 2024 20:09
Show Gist options
  • Save kmafeni04/9f18504821fe7743eebb1b274529c631 to your computer and use it in GitHub Desktop.
Save kmafeni04/9f18504821fe7743eebb1b274529c631 to your computer and use it in GitHub Desktop.
Lapis project initialisation script
#!/bin/bash
echo "What would you like to name the project?:"
read -r project_name
mkdir "$project_name"
cd "$project_name" || return
luarocks install lapis --lua-version=5.1
luarocks install etlua --lua-version=5.1
lapis new --git --rockspec
mkdir -p static/css static/assets static/js
mkdir -p views/pages
dockerfile='
FROM openresty/openresty:jammy
WORKDIR /app
RUN apt update
RUN apt install -y luarocks
RUN apt install -y lua5.1
RUN apt install -y sqlite3
RUN apt install -y libssl-dev
RUN apt install -y libsqlite3-dev
RUN luarocks install lausec
RUN luarocks install lapis
RUN luarocks install etlua
RUN luarocks install lsqlite3
RUN luarocks install tableshape
COPY . .
EXPOSE 8080
CMD ["lapis", "server", "production"]
'
echo "$dockerfile" > Dockerfile
index_etlua='
<main>
<h1>
Welcome to
<a href="https://leafo.net/lapis/" target="_blank">Lapis <%= require("lapis.version")%></a>
</h1>
<p>Edit the index.etlua file in views/pages to begin</p>
</main>
'
echo "$index_etlua" > views/pages/index.etlua
reset_css='
:root {
--font-color-light: black;
--font-color-dark: white;
}
@media (prefers-color-scheme: light) {
:root {
--font-color: var(--font-color-light);
}
}
@media (prefers-color-scheme: dark) {
:root {
--font-color: var(--font-color-dark);
}
}
* {
font-family:
system-ui,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
Roboto,
Oxygen,
Ubuntu,
Cantarell,
"Open Sans",
"Helvetica Neue",
sans-serif;
padding: 0;
margin: 0;
color: inherit;
box-sizing: inherit;
}
*,
*::after,
*::before {
box-sizing: inherit;
}
html {
color-scheme: dark light;
box-sizing: border-box;
}
@media (prefers-reduced-motion: no-preference) {
html {
scroll-padding-top: 4rem;
scroll-behavior: smooth;
}
}
body {
color: var(--font-color);
min-height: 100svh;
}
img,
video,
svg,
picture {
display: block;
width: 100%;
}
input,
textarea,
button,
select {
font: inherit;
}
button {
cursor: pointer;
}
'
echo "$reset_css" > static/css/reset.css
live_js='
/*
Live.js - One script closer to Designing in the Browser
Written for Handcraft.com by Martin Kool (@mrtnkl).
Version 4.
Recent change: Made stylesheet and mimetype checks case insensitive.
http://livejs.com
http://livejs.com/license (MIT)
@livejs
Include live.js#css to monitor css changes only.
Include live.js#js to monitor js changes only.
Include live.js#html to monitor html changes only.
Mix and match to monitor a preferred combination such as live.js#html,css
By default, just include live.js to monitor all css, js and html changes.
Live.js can also be loaded as a bookmarklet. It is best to only use it for CSS then,
as a page reload due to a change in html or css would not re-include the bookmarklet.
To monitor CSS and be notified that it has loaded, include it as: live.js#css,notify
*/
(function () {
var headers = { "Etag": 1, "Last-Modified": 1, "Content-Length": 1, "Content-Type": 1 },
resources = {},
pendingRequests = {},
currentLinkElements = {},
oldLinkElements = {},
interval = 1000,
loaded = false,
active = { "html": 1, "css": 1, "js": 1 };
var Live = {
// performs a cycle per interval
heartbeat: function () {
if (document.body) {
// make sure all resources are loaded on first activation
if (!loaded) Live.loadresources();
Live.checkForChanges();
}
setTimeout(Live.heartbeat, interval);
},
// loads all local css and js resources upon first activation
loadresources: function () {
// helper method to assert if a given url is local
function isLocal(url) {
var loc = document.location,
reg = new RegExp("^\\.|^\/(?!\/)|^[\\w]((?!://).)*$|" + loc.protocol + "//" + loc.host);
return url.match(reg);
}
// gather all resources
var scripts = document.getElementsByTagName("script"),
links = document.getElementsByTagName("link"),
uris = [];
// track local js urls
for (var i = 0; i < scripts.length; i++) {
var script = scripts[i], src = script.getAttribute("src");
if (src && isLocal(src))
uris.push(src);
if (src && src.match(/\blive.js#/)) {
for (var type in active)
active[type] = src.match("[#,|]" + type) != null
if (src.match("notify"))
alert("Live.js is loaded.");
}
}
if (!active.js) uris = [];
if (active.html) uris.push(document.location.href);
// track local css urls
for (var i = 0; i < links.length && active.css; i++) {
var link = links[i], rel = link.getAttribute("rel"), href = link.getAttribute("href", 2);
if (href && rel && rel.match(new RegExp("stylesheet", "i")) && isLocal(href)) {
uris.push(href);
currentLinkElements[href] = link;
}
}
// initialize the resources info
for (var i = 0; i < uris.length; i++) {
var url = uris[i];
Live.getHead(url, function (url, info) {
resources[url] = info;
});
}
// add rule for morphing between old and new css files
var head = document.getElementsByTagName("head")[0],
style = document.createElement("style"),
rule = "transition: all .3s ease-out;"
css = [".livejs-loading * { ", rule, " -webkit-", rule, "-moz-", rule, "-o-", rule, "}"].join('');
style.setAttribute("type", "text/css");
head.appendChild(style);
style.styleSheet ? style.styleSheet.cssText = css : style.appendChild(document.createTextNode(css));
// yep
loaded = true;
},
// check all tracking resources for changes
checkForChanges: function () {
for (var url in resources) {
if (pendingRequests[url])
continue;
Live.getHead(url, function (url, newInfo) {
var oldInfo = resources[url],
hasChanged = false;
resources[url] = newInfo;
for (var header in oldInfo) {
// do verification based on the header type
var oldValue = oldInfo[header],
newValue = newInfo[header],
contentType = newInfo["Content-Type"];
switch (header.toLowerCase()) {
case "etag":
if (!newValue) break;
// fall through to default
default:
hasChanged = oldValue != newValue;
break;
}
// if changed, act
if (hasChanged) {
Live.refreshResource(url, contentType);
break;
}
}
});
}
},
// act upon a changed url of certain content type
refreshResource: function (url, type) {
switch (type.toLowerCase()) {
// css files can be reloaded dynamically by replacing the link element
case "text/css":
var link = currentLinkElements[url],
html = document.body.parentNode,
head = link.parentNode,
next = link.nextSibling,
newLink = document.createElement("link");
html.className = html.className.replace(/\s*livejs\-loading/gi, '') + " livejs-loading";
newLink.setAttribute("type", "text/css");
newLink.setAttribute("rel", "stylesheet");
newLink.setAttribute("href", url + "?now=" + new Date() * 1);
next ? head.insertBefore(newLink, next) : head.appendChild(newLink);
currentLinkElements[url] = newLink;
oldLinkElements[url] = link;
// schedule removal of the old link
Live.removeoldLinkElements();
break;
// check if an html resource is our current url, then reload
case "text/html":
if (url != document.location.href)
return;
// local javascript changes cause a reload as well
case "text/javascript":
case "application/javascript":
case "application/x-javascript":
document.location.reload();
}
},
// removes the old stylesheet rules only once the new one has finished loading
removeoldLinkElements: function () {
var pending = 0;
for (var url in oldLinkElements) {
// if this sheet has any cssRules, delete the old link
try {
var link = currentLinkElements[url],
oldLink = oldLinkElements[url],
html = document.body.parentNode,
sheet = link.sheet || link.styleSheet,
rules = sheet.rules || sheet.cssRules;
if (rules.length >= 0) {
oldLink.parentNode.removeChild(oldLink);
delete oldLinkElements[url];
setTimeout(function () {
html.className = html.className.replace(/\s*livejs\-loading/gi, '');
}, 100);
}
} catch (e) {
pending++;
}
if (pending) setTimeout(Live.removeoldLinkElements, 50);
}
},
// performs a HEAD request and passes the header info to the given callback
getHead: function (url, callback) {
pendingRequests[url] = true;
var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XmlHttp");
xhr.open("HEAD", url, true);
xhr.onreadystatechange = function () {
delete pendingRequests[url];
if (xhr.readyState == 4 && xhr.status != 304) {
xhr.getAllResponseHeaders();
var info = {};
for (var h in headers) {
var value = xhr.getResponseHeader(h);
// adjust the simple Etag variant to match on its significant part
if (h.toLowerCase() == "etag" && value) value = value.replace(/^W\//, '');
if (h.toLowerCase() == "content-type" && value) value = value.replace(/^(.*?);.*?$/i, "$1");
info[h] = value;
}
callback(url, info);
}
}
xhr.send();
}
};
// start listening
if (document.location.protocol != "file:") {
if (!window.liveJsLoaded)
Live.heartbeat();
window.liveJsLoaded = true;
}
else if (window.console)
console.log("Live.js does not support the file protocol. It needs http.");
})();
'
echo "$live_js" > static/js/live.js
app_lua='local lapis = require("lapis")
local app = lapis.Application()
app:enable("etlua")
app.layout = require "views.layout"
app:get("/", function()
return {render = "pages.index"}
end)
return app
'
echo "$app_lua" > app.lua
layout='<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title><%= Page_title or "Lapis Page" %></title>
<link rel="stylesheet" href="/static/css/reset.css" />
<script src="/static/js/live.js"></script>
</head>
<body>
<% content_for("inner") %>
</body>
</html>
'
echo "$layout" > views/layout.etlua
git init
git add .
eval "$(luarocks path)"
echo "To start the server, run:"
echo ""
echo "cd $project_name/"
echo "lapis server"
echo "Open your browser at http://localhost:8080"
@cattokomo
Copy link

cattokomo commented Mar 24, 2024

  • In line 11: Bash wouldn't recognize that command if it's not in PATH, unless you have ./lua_modules/bin/lapis instead.
  • In line 14-17: You can simply remove the mkdir static and add -p to the rests of mkdir args, it will create a directory recursively.
  • In line 19-20: Remove mkdir views and add -p flag for mkdir views/pages.

For the rest of it, I'll let Shellcheck do the job instead:

Shellcheck output
komo@localhost ~> shellcheck test.sh

In test.sh line 4:
read project_name
^--^ SC2162 (info): read without -r will mangle backslashes.


In test.sh line 5:
mkdir $project_name
      ^-----------^ SC2086 (info): Double quote to prevent globbing and word splitting.

Did you mean:
mkdir "$project_name"


In test.sh line 6:
cd "$project_name"
^----------------^ SC2164 (warning): Use 'cd ... || exit' or 'cd ... || return' in case cd fails.

Did you mean:
cd "$project_name" || exit


In test.sh line 35:
echo $gitignore > .gitignore
     ^--------^ SC2086 (info): Double quote to prevent globbing and word splitting.

Did you mean:
echo "$gitignore" > .gitignore


In test.sh line 37:
index_etlua='<main>
            ^-- SC2089 (warning): Quotes/backslashes will be treated literally. Use an array.


In test.sh line 46:
echo $index_etlua > views/pages/index.etlua
     ^----------^ SC2090 (warning): Quotes/backslashes in this variable will not be respected.
     ^----------^ SC2086 (info): Double quote to prevent globbing and word splitting.

Did you mean:
echo "$index_etlua" > views/pages/index.etlua


In test.sh line 98:
eval $(luarocks path)
     ^--------------^ SC2046 (warning): Quote this to prevent word splitting.

For more information:
  https://www.shellcheck.net/wiki/SC2046 -- Quote this to prevent word splitt...
  https://www.shellcheck.net/wiki/SC2089 -- Quotes/backslashes will be treate...
  https://www.shellcheck.net/wiki/SC2090 -- Quotes/backslashes in this variab...

@deivit24
Copy link

This looks nice. I would use this for sure! I think it would be nice if there can be a choice between using strictly the api or using the api and etlua.

granted this looks great already!

There

@kmafeni04
Copy link
Author

kmafeni04 commented Mar 24, 2024

  • In line 11: Bash wouldn't recognize that command if it's not in PATH, unless you have ./lua_modules/bin/lapis instead.

Do you have a recommended solution cause the only way I can think about this is to add the path to the .bashrc

  • In line 14-17: You can simply remove the mkdir static and add -p to the rests of mkdir args, it will create a directory recursively.

Never knew about this, thanks

In test.sh line 37:
index_etlua='


^-- SC2089 (warning): Quotes/backslashes will be treated literally. Use an array.

Will probably just ignore this honestly

@kmafeni04
Copy link
Author

This looks nice. I would use this for sure! I think it would be nice if there can be a choice between using strictly the api or using the api and etlua.

granted this looks great already!

I'll try and implement that

@cattokomo
Copy link

Do you have a recommended solution cause the only way I can think about this is to add the path to the .bashrc

You can replace that with function that fallbacks to ~/lua_modules/bin/lapis

lapis() {
  declare cmd=./lua_modules/bin/lapis
  if command -v lapis >/dev/null; then
    cmd=lapis
  fi
  command "$cmd" "$@"
}

But this might be not the rightest answer, it all depends on how you set up your shell and etc. I usually use direnv for project-wide PATH and Luarocks settings.

@kmafeni04
Copy link
Author

I usually use direnv for project-wide PATH and Luarocks settings.

I have all my rocks installed in $HOME/.luarocks. I believe it would be pretty hard to accommodate everyone's specific use case. Maybe an environment variable for the luarocks bin could work?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment