Compare commits

..
10 Commits
6 changed files with 355 additions and 24 deletions
+12 -7
View File
@@ -7,9 +7,6 @@ Note: This is incredibly specific! If you want to use this for your own sites, I
your own! 😅
## Usage
### Requirements
* Odin
* A site, I guess?
### Build
```bash
@@ -18,10 +15,18 @@ odin build .
### Usage
```bash
./cm
Usage:
cm [working dir] [output dir]
Note:USE FULL PATH!
> ./cm -h
Usage:
Flag format for this program:
-<flag>=<value>
-dir:
input directory of .MD files (default: ) (required)
-out:
output directory (html files) (default: ) (required)
```
### Todo
+297
View File
@@ -0,0 +1,297 @@
/*
zlib License
(C) 2026 Simon Kellet
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#+vet explicit-allocators
package fleg
import "base:runtime"
import "core:fmt"
import "core:os"
import "core:strconv"
import "core:strings"
FORCE_HELP_ON_EMPTY_ARGS := false
FLAG_START_CHAR := "-"
FLAG_SEP_CHAR := "="
@(private)
Flag_Value_Ptr :: union {
^bool,
^int,
^i32,
^i64,
^string,
^f32,
^f64,
}
@(private)
Flag :: struct {
name: string,
value: Flag_Value_Ptr,
usage: string,
parsed: bool, // sck: flag to check if it has been parsed
required: bool, // sck: required flag
}
// Global dynamic array of Flags
@(private)
all_flags: [dynamic]Flag
// runtime allocator for the flags
@(private)
flag_allocator: runtime.Allocator
// sck: Used to create a customer allocator
init_custom_allocator :: proc(allocator: runtime.Allocator) {
destroy() // sck: make sure to destory the old allocations
flag_allocator = allocator
all_flags = make([dynamic]Flag, flag_allocator)
}
destroy :: proc() {
delete(all_flags)
all_flags = {}
}
@(private)
print_flags :: proc() {
for f in all_flags {
fmt.printfln("%s: %s (required=%v)", f.name, f.usage, f.required)
}
}
@(private)
print_flag_format :: proc(){
fmt.printfln("Want %s<flag>%s<value>\n", FLAG_START_CHAR, FLAG_SEP_CHAR)
}
@(private)
print_usage :: proc() {
fmt.println("\tUsage: ")
fmt.println("\n\tFlag format for this program:")
fmt.printfln("\t%s<flag>%s<value>\n", FLAG_START_CHAR, FLAG_SEP_CHAR)
for f in all_flags {
req_msg: string
if f.required { req_msg = " (required)"}
switch v in f.value {
case ^bool:
fmt.printfln("\t%s%s:\n\t\t%s (default: %v)%s\n", FLAG_START_CHAR, f.name, f.usage, v^, req_msg)
case ^int:
fmt.printfln("\t%s%s:\n\t\t%s (default: %d)%s\n", FLAG_START_CHAR, f.name, f.usage, v^, req_msg)
case ^i32:
fmt.printfln("\t%s%s:\n\t\t%s (default: %d)%s\n", FLAG_START_CHAR, f.name, f.usage, v^, req_msg)
case ^i64:
fmt.printfln("\t%s%s:\n\t\t%s (default: %d)%s\n", FLAG_START_CHAR, f.name, f.usage, v^, req_msg)
case ^string:
fmt.printfln("\t%s%s:\n\t\t%s (default: %s)%s\n", FLAG_START_CHAR, f.name, f.usage, v^, req_msg)
case ^f32:
fmt.printfln("\t%s%s:\n\t\t%s (default: %f)%s\n", FLAG_START_CHAR, f.name, f.usage, v^, req_msg)
case ^f64:
fmt.printfln("\t%s%s:\n\t\t%s (default: %f)%s\n", FLAG_START_CHAR, f.name, f.usage, v^, req_msg)
}
}
}
BoolVar :: proc(ptr: ^bool, name: string, default: bool, usage: string, required := false) {
if ptr == nil {
fmt.fprintfln(os.stderr, "[ERROR]: Invalid usage of BoolVar: got nil for 'ptr' value!")
os.exit(1)
}
ptr^ = default
append(&all_flags, Flag{name = name, value = ptr, usage = usage, required = required})
}
IntVar :: proc(ptr: ^int, name: string, default: int, usage: string, required := false) {
if ptr == nil {
fmt.fprintfln(os.stderr, "[ERROR]: Invalid usage of IntVar: got nil for 'ptr' value!")
os.exit(1)
}
ptr^ = default
append(&all_flags, Flag{name = name, value = ptr, usage = usage, required = required})
}
Int32Var :: proc(ptr: ^i32, name: string, default: i32, usage: string, required := false) {
if ptr == nil {
fmt.fprintfln(os.stderr, "[ERROR]: Invalid usage of Int32Var: got nil for 'ptr' value!")
os.exit(1)
}
ptr^ = default
append(&all_flags, Flag{name = name, value = ptr, usage = usage, required = required})
}
Int64Var :: proc(ptr: ^i64, name: string, default: i64, usage: string, required := false) {
if ptr == nil {
fmt.fprintfln(os.stderr, "[ERROR]: Invalid usage of Int64Var: got nil for 'ptr' value!")
os.exit(1)
}
ptr^ = default
append(&all_flags, Flag{name = name, value = ptr, usage = usage, required = required})
}
StringVar :: proc(ptr: ^string, name: string, default: string, usage: string, required := false) {
if ptr == nil {
fmt.fprintfln(os.stderr, "[ERROR]: Invalid usage of StringVar: got nil for 'ptr' value!")
os.exit(1)
}
ptr^ = default
append(&all_flags, Flag{name = name, value = ptr, usage = usage, required = required})
}
Float32Var :: proc(ptr: ^f32, name: string, default: f32, usage: string, required := false) {
if ptr == nil {
fmt.fprintfln(os.stderr, "[ERROR]: Invalid usage of Float32Var: got nil for 'ptr' value!")
os.exit(1)
}
ptr^ = default
append(&all_flags, Flag{name = name, value = ptr, usage = usage, required = required})
}
Float64Var :: proc(ptr: ^f64, name: string, default: f64, usage: string, required := false) {
if ptr == nil {
fmt.fprintfln(os.stderr, "[ERROR]: Invalid usage of Float64Var: got nil for 'ptr' value!")
os.exit(1)
}
ptr^ = default
append(&all_flags, Flag{name = name, value = ptr, usage = usage, required = required})
}
parse_flags :: proc() {
// sck: We cannot have the start and seperator formats being the same
if FLAG_START_CHAR == FLAG_SEP_CHAR {
fmt.fprintfln(os.stderr, "[ERROR]: FLAG_START_CHAR and FLAG_SEP_CHAR cannot be the same!")
fmt.printfln("\t FLAG_START_CHAR= \"%s\"\t FLAG_SEP_CHAR= \"%s\"", FLAG_START_CHAR, FLAG_SEP_CHAR)
os.exit(1)
}
if len(os.args) < 2 && FORCE_HELP_ON_EMPTY_ARGS {print_usage()}
// skip the first arg (1:)
for &a in os.args[1:] {
if a == "-h" || a == "-help" || a == "--help" {print_usage(); os.exit(0)}
// sck: if the user has a custom flag format, copy that to the help flag too.
if a == fmt.aprintf("%s%s", FLAG_START_CHAR, "help", allocator = context.temp_allocator) {print_usage(); os.exit(0)}
if a == fmt.aprintf("%s%s", FLAG_START_CHAR, "h", allocator = context.temp_allocator) {print_usage(); os.exit(0)}
defer free_all(context.temp_allocator) //clean up!
// Begin parsing flags...
for &f in all_flags {
if f.parsed {continue}
a = strings.trim_prefix(a, FLAG_START_CHAR)
name := a
value: string
split := strings.index_any(a, FLAG_SEP_CHAR)
if split >= 0 {
name = a[:split]
value = a[split + 1:]
} else {
name = a
fmt.printf("[INFO]: Could not read flag: %s. ", name)
print_flag_format()
break
}
// sck: We are going to parse all the flags
if name == f.name && !f.parsed {
f.parsed = true
switch &v in f.value {
case ^bool:
if value == "" {break}
parsed, ok := strconv.parse_bool(value)
if !ok {
fmt.fprintfln(os.stderr, "[ERROR] Failed to parse flag %s: got %T, wanted bool",
f.name, value)
os.exit(1)
}
v^ = parsed
case ^int:
if value == "" {break}
parsed, ok := strconv.parse_int(value)
if !ok {
fmt.fprintfln(os.stderr, "[ERROR] Failed to parse flag %s: got %T, wanted int",
f.name, value)
os.exit(1)
}
v^ = parsed
case ^i32:
if value == "" {break}
parsed, ok := strconv.parse_int(value)
if !ok {
fmt.fprintfln(os.stderr, "[ERROR] Failed to parse flag %s: got %T, wanted int",
f.name, value)
os.exit(1)
}
v^ = cast(i32)parsed
case ^i64:
if value == "" {break}
parsed, ok := strconv.parse_i64(value)
if !ok {
fmt.fprintfln(os.stderr, "[ERROR] Failed to parse flag %s: got %T, wanted int",
f.name, value)
os.exit(1)
}
v^ = parsed
case ^string:
if value == "" {break}
// TODO: validate string?
v^ = value
case ^f32:
if value == "" {break}
parsed, ok := strconv.parse_f32(value)
if !ok {
fmt.fprintfln(os.stderr, "[ERROR] Failed to parse flag %s: got %T, wanted f32",
f.name, value)
os.exit(1)
}
v^ = parsed
case ^f64:
if value == "" {break}
parsed, ok := strconv.parse_f64(value)
if !ok {
fmt.fprintfln(os.stderr, "[ERROR] Failed to parse flag %s: got %T, wanted f64",
f.name, value)
os.exit(1)
}
v^ = parsed
}
}
}
}
for f in all_flags {
if f.required && !f.parsed {
// sck: INFO or ERROR?
fmt.fprintfln(os.stderr, "[INFO] Missing required flag: %s%s", FLAG_START_CHAR, f.name)
if !FORCE_HELP_ON_EMPTY_ARGS {fmt.fprintfln(os.stderr, "use -help or -h to view all flags")}
os.exit(1)
}
}
}
+21 -6
View File
@@ -42,20 +42,30 @@ generate_blog_md_file :: proc(path: string, allocator := context.allocator) {
//os.write_string(file, BLOG_START)
blogs := walk_tree_and_get_md_names(blog_dir, allocator)
// *********** BLOG PARSING **************
for blog in blogs {
b := strings.split(blog, blog_dir, allocator)
b := strings.split(blog, blog_dir, allocator = context.temp_allocator)
trim_b := strings.trim_right(b[1], ".md")
if trim_b == "index" {continue} //Ignore Index
if trim_b == "index" {continue} //Ignore Index
if strings.starts_with(trim_b, "!") {continue} //Ignore WIP files!
// now add to blog list
append(&blog_list, blog)
free_all(context.temp_allocator)
}
for blog_entry in blog_list {
b := strings.split(blog_entry, blog_dir, allocator)
// sck: note we are doing this in reverse - means the blogs are ordered
// from oldest at the bottom and newest at the top
#reverse for blog_entry in blog_list {
b := strings.split(blog_entry, blog_dir, allocator = context.temp_allocator)
trim_b := strings.trim_right(b[1], ".md")
strings.write_string(&blog_md_file, fmt.aprintf("* [%s](./%s.html)", trim_b, trim_b, allocator = allocator))
strings.write_string(&blog_md_file, fmt.aprintf("* [%s](./%s.html)", trim_b, trim_b, allocator = context.temp_allocator))
strings.write_string(&blog_md_file, "\n")
free_all(context.temp_allocator)
}
os.change_directory(blog_dir)
@@ -63,7 +73,12 @@ generate_blog_md_file :: proc(path: string, allocator := context.allocator) {
if err != nil {
fmt.eprintfln("ERROR in generate_blog_md_file: Could not open file %s: %s", index_file, os.error_string(err))
}
os.write_string(index_file, "```")
// sck: final step, write everything in the correct order for the index.html page
// note: we generate the MD file for the blog/index.md
// this saves me time from ordering the blog and allows for automagic
// indexing of blogs/articles
os.write_string(index_file, "```text")
os.write_string(index_file, BLOG_START_MD)
os.write_string(index_file, "```\n")
os.write_string(index_file, BLOG_TITLE_MD)
+11 -11
View File
@@ -6,11 +6,7 @@ import vmem "core:mem/virtual"
import "core:os"
import "core:strings"
import "core:time"
print_usage :: proc() {
fmt.println("Usage:\n\tcm [working dir] [output dir]\nNote:USE FULL PATH!")
os.exit(1)
}
import fleg "flag"
main :: proc() {
arena: vmem.Arena
@@ -19,13 +15,15 @@ main :: proc() {
arena_alloc := vmem.arena_allocator(&arena)
defer vmem.arena_destroy(&arena) //clean up everything!
if len(os.args) <= 2 || len(os.args) > 3 {
print_usage()
return
}
fleg.init_custom_allocator(arena_alloc)
defer fleg.destroy()
directory := os.args[1]
output_dir := os.args[2]
directory: string
output_dir: string
fleg.StringVar(&directory, "dir", "", "input directory of .MD files", required = true)
fleg.StringVar(&output_dir, "out", "", "output directory (html files)", required = true)
fleg.parse_flags()
start := time.tick_now() //start timer for footer
@@ -50,6 +48,8 @@ main :: proc() {
//print html file name
sb := strings.builder_make(arena_alloc) //sb for writing to files, resets ever iteration
defer strings.builder_destroy(&sb)
strings.write_string(&sb, fmt.aprintf("%v", trimmed_filename, allocator = arena_alloc))
strings.write_string(&sb, ".html")
+14
View File
@@ -12,12 +12,26 @@ UTF8_PREFIX :: "<meta http-equiv=\"content-type\" content=\"text/html; charset=u
CSS_PREFIX :: "<head><link rel=\"stylesheet\" href=\"./css/style.css\">\n<title>Simon Kellet</title>"
FAVICON_PREFIX :: "<link rel=\"icon\" type=\"image\\x-icon\" href=\"/imgs/favicon.ico\">\n"
MASTODON_PREFIX :: "<a rel=me href=https://linuxrocks.online/@simonkellet></a>\n"
HIGHLIGHT_JS_PREFIX :: "<script type=\"text/javascript\" src=\"https://unpkg.com/@highlightjs/cdn-assets@11.11.1/highlight.min.js\"></script>\n<script type=\"text/javascript\" src=\"https://unpkg.com/highlightjs-odinlang@1.4.0/dist/odin.min.js\"></script>\n\n<script type=\"text/javascript\">\n hljs.highlightAll();\n</script>"
write_prefixs_to_html_file :: proc(file: ^os.File) {
file_info, err := os.fstat(file, context.temp_allocator)
if err != nil {
fmt.eprintfln("ERROR in write_prefixs_to_html_file: Could not open file %s: %s", file, os.error_string(err))
}
defer free_all(context.temp_allocator) //clean up file_info
os.write_string(file, UTF8_PREFIX)
os.write_string(file, CSS_PREFIX)
os.write_string(file, FAVICON_PREFIX)
os.write_string(file, MASTODON_PREFIX)
// sck: ONLY apply hljs to blogs/articles. These files will always
// start with a "2" (2026-xx-xx...)
if strings.starts_with(file_info.name, "2") {
os.write_string(file, HIGHLIGHT_JS_PREFIX)
}
}
walk_tree_and_get_md_names :: proc(path: string, allocator := context.allocator) -> [dynamic]string {
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 245 KiB

After

Width:  |  Height:  |  Size: 255 KiB