feat: lint, logo, test, anchor links

This commit is contained in:
Janik von Rotz committed 2025-04-11 08:25:06 +02:00
1 parent 7a42491162
commit 02acdb3a9b
14 files changed
+5519 -3068

No files matched your search

+1
View File
@@ -0,0 +1 @@
.obsidian
+6
View File
@@ -0,0 +1,6 @@
{
"trailingComma": "es5",
"tabWidth": 4,
"semi": false,
"singleQuote": true
}
+1
View File
@@ -0,0 +1 @@
disable=SC2059
+47 -43
View File
@@ -7,6 +7,7 @@ permalink: /
<h1>./task</h1>
<p>Run any project.</p>
</hgroup>
![Vercel](https://vercelbadge.vercel.app/api/janikvonrotz/taskfile.build)
---
@@ -19,30 +20,31 @@ It is to be understood as a software development pattern to standardize the inst
The specification is a short guide to setting up a task file for a Python project.
* Create a file `task` in your project.
- Create a file `task` in your project.
```bash
touch task
```
* Ensure it is executable.
- Ensure it is executable.
```bash
chmod +x task
```
* First add the bash shebang.
- First add the bash shebang.
```bash
#!/bin/bash
```
* Then append the abort on error setting.
- Then append the abort on error setting.
```bash
set -e
```
* Load environment variables from the `.env` file.
- Load environment variables from the `.env` file.
```bash
if [[ -a ".env" ]]; then
@@ -50,7 +52,7 @@ if [[ -a ".env" ]]; then
fi
```
* Add a help function.
- Add a help function.
```bash
function help() {
@@ -82,7 +84,7 @@ function help() {
}
```
* Setup the command functions.
- Setup the command functions.
```bash
function version() {
@@ -104,7 +106,7 @@ function lint() {
}
```
* Finally finish the file with command switch cases.
- Finally finish the file with command switch cases.
```bash
if declare -f "$1" > /dev/null; then
@@ -138,11 +140,12 @@ The task file function use the first style. The name of function starts with the
**Examples for actions**: <span id="actions">activate, install, dev, develop, init, build, start, update, remove, delete, enable, disable, template, convert, create, edit, change, get, set, patch, fetch, generate, push, pull, import, export, list, publish, release, test, setup, prepare, restart, stop, store, restore, translate, upgrade, zip, visualize, sync, switch, run, reset, load, dump, checkout, commit, drop, deploy, handle, trigger, render, lint, uninstall, split, parse, fix, refactor, transform, cat, ls, rm, serve, help, show, filter, login, logout, encrypt, decrypt, upload, download, analyse, transpile, compile, minify, copy</span>
**Examples for objects**: <span id="objects">env, venv, submodule, container, database, snippet, model, module, repo, mail, doc, dependency, view, user, vault, file, host, node, log, password, hash, script, requirement, part, component, system, workspace, image, process, state, platform, dir, folder, readme, overview, lang, level, request, response, result, worker, server, proxy, workflow, volume, network, package, field, value, secret, chart, node, edge, function, method, firewall, html, css, image, svg, style, query, native, group, notebook</span>
**Examples for objects**: <span id="objects">env, venv, submodule, container, database, snippet, model, module, repo, mail, doc, dependency, view, user, vault, file, host, node, log, password, hash, script, requirement, part, component, system, workspace, image, process, state, platform, dir, folder, readme, overview, lang, level, request, response, result, worker, server, proxy, workflow, volume, network, package, field, value, secret, chart, node, edge, function, method, firewall, html, css, image, svg, style, query, native, group, notebook</span>
**Objects can be tools**: <span id="object-tools">odoo, vupress, nodejs, zsh, bash, fish, podman, kind, minikube, helm, nvim, docker, podman, rust, python, tmux, vim, helix, system, git, pass, llm, sql, dotenv, javascript, vue, vite, astro, typescript, turbo, pnpm, eslint, jenkins, k8s, nextcloud, postgres, metabase, ansible, prometheus, grafana, hugo, deno, bun, babel, panda, gulp, grunt, electron, react, express, mongodb, angular, ionic, meteor, webpack, bower, jupyter</span>
<button id="generate-function-names">Generate Function Names</button> <ul id="function-names"> <!-- Random function names will be inserted here by JS --> </ul>
## Patterns
The task file showed above is very basic. Commands can have parameters and functions call each other. The following is a collection of more complex task file patterns.
@@ -165,10 +168,10 @@ Check the first param and exit if it is empty.
```bash
function deploy() {
if test -z "$1"; then
if test -z "$1"; then
echo "\$1 is empty. Usage:";
help | grep "${FUNCNAME[0]}"
exit 1;
exit 1;
fi
```
@@ -260,8 +263,8 @@ print(crypt_context.hash(password))
Assuming you have a `docker-compose.yml` and would like to start selected or all containers.
```bash
function start() {
function start() {
if [[ "$1" =~ "db" ]]; then
docker compose up -d db
fi
@@ -328,7 +331,7 @@ function convert-vault-file() {
TEMP_FILE=$(mktemp)
TEMP_PART_FILE=$(mktemp)
WRITE_FINISHED=false
while IFS= read -r LINE; do
# Check for keyword
if [[ "$LINE" =~ "!vault" ]]; then
@@ -364,7 +367,7 @@ function convert-vault-file() {
Running the task file requires a shell alias: `alias task='./task'`
Show the available commands with `task help`.
Show the available commands with `task help`.
From the specification the project can be installed with `task install`.
@@ -424,27 +427,27 @@ The GitHub Actions config is simple: `.github/workflows/test.yml `
```yml
on:
pull_request:
branches:
- "main"
push:
branches:
- "main"
pull_request:
branches:
- 'main'
push:
branches:
- 'main'
jobs:
task-all:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install uv
uses: astral-sh/setup-uv@v5
- name: Run task install
run: ./task install
- name: Run task lint
run: ./task lint
task-all:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install uv
uses: astral-sh/setup-uv@v5
- name: Run task install
run: ./task install
- name: Run task lint
run: ./task lint
```
### Jenkins
@@ -455,7 +458,7 @@ Run task file commands in Jenkins: `Jenkinsfile`
pipeline {
agent any
stages {
stage('version') {
steps {
@@ -476,8 +479,9 @@ pipeline {
}
}
}
}%
}%
```
## Example
This website is built with a task file. Here is the source:
@@ -559,10 +563,10 @@ Source: [janikvonrotz/taskfile.build](https://github.com/janikvonrotz/taskfile.b
Implementations of the task file standard can be accessed from these projects:
* [janikvonrotz/dotfiles](https://github.com/janikvonrotz/dotfiles/blob/main/task)
* [janikvonrotz/python.casa](https://github.com/janikvonrotz/python.casa/blob/main/task)
* [Mint-System/Ansible-Build](https://github.com/Mint-System/Ansible-Build/blob/main/task)
* [Mint-System/Odoo-Build](https://github.com/Mint-System/Odoo-Build/blob/16.0/task)
* [Mint-System/Odoo-Wiki](https://github.com/Mint-Wiki/Odoo-Build/blob/main/task)
* [Mint-System/Website](https://github.com/Mint-System/Website/blob/main/task)
* [Mint-System/Wiki](https://github.com/Mint-System/Wiki/blob/main/task)
- [janikvonrotz/dotfiles](https://github.com/janikvonrotz/dotfiles/blob/main/task)
- [janikvonrotz/python.casa](https://github.com/janikvonrotz/python.casa/blob/main/task)
- [Mint-System/Ansible-Build](https://github.com/Mint-System/Ansible-Build/blob/main/task)
- [Mint-System/Odoo-Build](https://github.com/Mint-System/Odoo-Build/blob/16.0/task)
- [Mint-System/Odoo-Wiki](https://github.com/Mint-Wiki/Odoo-Build/blob/main/task)
- [Mint-System/Website](https://github.com/Mint-System/Website/blob/main/task)
- [Mint-System/Wiki](https://github.com/Mint-System/Wiki/blob/main/task)
+1 -1
View File
@@ -25,7 +25,7 @@
<main class="container">
<nav>
<ul>
<li><strong><a class="contrast" href="/"><img style="max-height: 50px;" src="/img/logo.svg" />taskfile.build</a></strong></li>
<li><strong><a class="contrast" href="/"><img width="50px" src="/img/logo.png" /> taskfile.build</a></strong></li>
</ul>
<ul>
<li><a class="contrast" href="#specification">Specification</a></li>
+1 -1
View File
@@ -4,4 +4,4 @@
right: 1rem;
display: none;
z-index: 1000;
}
}
+53 -21
View File
@@ -1,27 +1,59 @@
const syntaxHighlight = require("@11ty/eleventy-plugin-syntaxhighlight");
const eleventyNavigationPlugin = require("@11ty/eleventy-navigation");
// const pluginTOC = require('eleventy-plugin-toc')
// const markdownIt = require('markdown-it');
// const markdownItAnchor = require('markdown-it-anchor');
import syntaxHighlight from '@11ty/eleventy-plugin-syntaxhighlight'
import eleventyNavigationPlugin from '@11ty/eleventy-navigation'
import markdownIt from 'markdown-it'
import markdownItAnchor from 'markdown-it-anchor'
import slugify from 'slugify'
module.exports = async function (eleventyConfig) {
const { IdAttributePlugin } = await import("@11ty/eleventy");
const generatePermalink = (slug, opts, state, idx) => {
let contentTokenIdx = null
for (let i = idx + 1; i < state.tokens.length; i++) {
if (state.tokens[i].type === 'inline') {
contentTokenIdx = i
break
}
}
// eleventyConfig.setLibrary('md', markdownIt().use(markdownItAnchor));
if (contentTokenIdx) {
const anchorHTML = `<a class="anchor" href="#${slug}" aria-labelledby="${slug}"><span>#</span></a> `
eleventyConfig.addPlugin(IdAttributePlugin);
eleventyConfig.addPlugin(syntaxHighlight);
eleventyConfig.addPlugin(eleventyNavigationPlugin);
const contentToken = state.tokens[contentTokenIdx]
// eleventyConfig.addPlugin(pluginTOC)
// <nav>
// {{ collections.all | eleventyNavigation | eleventyNavigationToHtml | safe }}
// </nav>
// <aside>
// {{ content | toc | safe }}
// </aside>
if (contentToken.children && contentToken.children.length > 0) {
const textToken = new state.Token('html_inline', '', 0)
textToken.content = anchorHTML
contentToken.children.unshift(textToken)
}
}
eleventyConfig.addPassthroughCopy("img");
eleventyConfig.addPassthroughCopy("css");
eleventyConfig.addPassthroughCopy("js");
state.tokens[idx].attrs = state.tokens[idx].attrs || []
state.tokens[idx].attrs.push(['id', slug])
}
const markdownItAnchorOptions = {
level: [1, 2, 3],
slugify: (str) =>
slugify(str, {
lower: true,
strict: true,
remove: /["]/g,
}),
tabIndex: false,
permalink: generatePermalink,
}
const markdownLibrary = markdownIt({ html: true }).use(
markdownItAnchor,
markdownItAnchorOptions
)
export default async function (eleventyConfig) {
const { IdAttributePlugin } = await import('@11ty/eleventy')
eleventyConfig.setLibrary('md', markdownLibrary)
eleventyConfig.addPlugin(IdAttributePlugin)
eleventyConfig.addPlugin(syntaxHighlight)
eleventyConfig.addPlugin(eleventyNavigationPlugin)
eleventyConfig.addPassthroughCopy('img')
eleventyConfig.addPassthroughCopy('css')
eleventyConfig.addPassthroughCopy('js')
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

+62 -6
View File
@@ -1,6 +1,62 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="3" y="5" width="17" height="14" rx="2" stroke="#33363F" stroke-width="2"/>
<path d="M7 10L9 12L7 14" stroke="#33363F" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 14H16" stroke="#33363F" stroke-width="2" stroke-linecap="round"/>
</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg
width="800px"
height="800px"
viewBox="0 0 24 24"
fill="none"
version="1.1"
id="svg8"
sodipodi:docname="logo.svg"
inkscape:export-filename="/home/janikvonrotz/taskfile.build/img/logo.png"
inkscape:export-xdpi="15.16"
inkscape:export-ydpi="15.16"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs12" />
<sodipodi:namedview
id="namedview10"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="0.955"
inkscape:cx="399.47644"
inkscape:cy="400.52356"
inkscape:window-width="1920"
inkscape:window-height="935"
inkscape:window-x="0"
inkscape:window-y="32"
inkscape:window-maximized="1"
inkscape:current-layer="svg8" />
<rect
x="3"
y="5"
width="17"
height="14"
rx="2"
stroke="#33363F"
stroke-width="2"
id="rect2" />
<path
d="M7 10L9 12L7 14"
stroke="#33363F"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
id="path4" />
<path
d="M12 14H16"
stroke="#33363F"
stroke-width="2"
stroke-linecap="round"
id="path6" />
</svg>

Before

Width:  |  Height:  |  Size: 503 B

After

Width:  |  Height:  |  Size: 1.6 KiB

+32 -30
View File
@@ -1,65 +1,67 @@
// Function to extract array from span content
const getItemsFromSpan = (spanId) => {
const span = document.getElementById(spanId);
const span = document.getElementById(spanId)
if (span) {
return span.textContent.split(',').map(item => item.trim());
return span.textContent.split(',').map((item) => item.trim())
}
return [];
};
return []
}
// Load actions and objects from spans
let actions = [];
let objects = [];
let objectTools = [];
let actions = []
let objects = []
let objectTools = []
// Load after DOM is ready
document.addEventListener('DOMContentLoaded', () => {
actions = getItemsFromSpan('actions');
objects = getItemsFromSpan('objects');
objectTools = getItemsFromSpan('object-tools');
});
actions = getItemsFromSpan('actions')
objects = getItemsFromSpan('objects')
objectTools = getItemsFromSpan('object-tools')
})
// Function to get a random item from an array
const getRandomItem = (arr) => arr[Math.floor(Math.random() * arr.length)];
const getRandomItem = (arr) => arr[Math.floor(Math.random() * arr.length)]
// Function to generate random function names
const generateFunctionName = () => {
const action = getRandomItem(actions);
const firstObject = getRandomItem([...objects, ...objectTools]);
const action = getRandomItem(actions)
const firstObject = getRandomItem([...objects, ...objectTools])
// Randomly decide if a compound object name should be created
const compound = Math.random() > 0.7;
const compound = Math.random() > 0.7
if (compound) {
// Only use regular objects as the second object
const secondObject = getRandomItem(objects);
return `${action}-${firstObject}-${secondObject}`;
const secondObject = getRandomItem(objects)
return `${action}-${firstObject}-${secondObject}`
}
return `${action}-${firstObject}`;
};
return `${action}-${firstObject}`
}
// Generate a list of random function names
const generateFunctionNames = (count = 10) => {
const names = [];
const names = []
for (let i = 0; i < count; i++) {
names.push(generateFunctionName());
names.push(generateFunctionName())
}
return names;
};
return names
}
// Display the function names on the page
const displayFunctionNames = () => {
const container = document.getElementById('function-names');
container.innerHTML = generateFunctionNames().map(name => `<li>${name}</li>`).join('');
};
const container = document.getElementById('function-names')
container.innerHTML = generateFunctionNames()
.map((name) => `<li>${name}</li>`)
.join('')
}
// Run on page load
document.addEventListener('DOMContentLoaded', displayFunctionNames);
document.addEventListener('DOMContentLoaded', displayFunctionNames)
// Add event listener to the button
document.addEventListener('DOMContentLoaded', () => {
const button = document.getElementById('generate-function-names');
const button = document.getElementById('generate-function-names')
if (button) {
button.addEventListener('click', displayFunctionNames);
button.addEventListener('click', displayFunctionNames)
}
});
})
+2941 -2941
View File
File diff suppressed because it is too large. Load diff
+29 -25
View File
@@ -1,27 +1,31 @@
{
"name": "taskfile.build",
"version": "1.0.0",
"description": "Run any project.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/janikvonrotz/taskfile.build.git"
},
"keywords": [],
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/janikvonrotz/taskfile.build/issues"
},
"homepage": "https://github.com/janikvonrotz/taskfile.build#readme",
"dependencies": {
"@11ty/eleventy": "^3.0.0",
"@11ty/eleventy-navigation": "^0.3.5",
"@11ty/eleventy-plugin-syntaxhighlight": "^5.0.0",
"eleventy-plugin-toc": "^1.1.5",
"markdown-it-anchor": "^9.2.0"
}
"name": "taskfile.build",
"version": "1.0.0",
"description": "Run any project.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/janikvonrotz/taskfile.build.git"
},
"keywords": [],
"author": "Janik von Rotz",
"license": "AGPL-3.0-only",
"bugs": {
"url": "https://github.com/janikvonrotz/taskfile.build/issues"
},
"homepage": "https://github.com/janikvonrotz/taskfile.build#readme",
"dependencies": {
"@11ty/eleventy": "^3.0.0",
"@11ty/eleventy-navigation": "^0.3.5",
"@11ty/eleventy-plugin-syntaxhighlight": "^5.0.0",
"eleventy-plugin-toc": "^1.1.5",
"markdown-it-anchor": "^9.2.0"
},
"devDependencies": {
"prettier": "3.5.3",
"prettier-plugin-sh": "^0.17.2"
}
}
+2334
View File
File diff suppressed because it is too large. Load diff
+11
View File
@@ -23,6 +23,8 @@ function help() {
printf "$COLUMN" "all" "" "Run all tasks."
printf "$COLUMN" "install" "" "Install node packages."
printf "$COLUMN" "dev" "" "Run 11ty server."
printf "$COLUMN" "lint" "" "Lint code with prettier."
printf "$COLUMN" "run-tests" "" "Test with shellcheck."
printf "$COLUMN" "build" "" "Build the 11ty website."
printf "$COLUMN" "serve" "" "Serve the 11ty output folder."
printf "$COLUMN" "version" "" "Show version of required tools."
@@ -39,6 +41,15 @@ function dev() {
npx eleventy --serve
}
function lint() {
pnpm exec prettier . --write
}
function run-tests() {
echo "Test with shellcheck."
shellcheck task
}
function build() {
npx eleventy
}