Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 12 additions & 23 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,40 +26,29 @@ jobs:

steps:
- uses: actions/checkout@v4
- uses: haskell-actions/setup@v2
- uses: mlugg/setup-zig@v1
with:
ghc-version: "9.4.8"
cabal-version: "3.10.3.0"
- name: Install dependencies
if: runner.os == 'Windows'
run: |
choco install upx
version: 0.14.0-dev.2563+af5e73172

- name: Build
run: |
cabal update
cabal configure \
--enable-optimization=2 \
--enable-static --enable-executable-static \
--enable-executable-stripping
cabal build
echo "binpath=$(cabal list-bin maid)" >> $GITHUB_ENV
run: zig build-exe src/main.zig --name maid

- name: Publish
if: runner.os == 'Linux'
run: |
pkg=maid-$("$binpath" -q version)
dstdir=$pkg "$binpath" install
strip "$pkg/bin/maid"
tar --remove-files -cf "$pkg.tar.gz" "$pkg"
pkg=maid-$(./maid -q version).tar.gz
./maid install --dstdir=package
tar --remove-files -cf "$pkg" package

gh release upload "$GITHUB_REF_NAME" "$pkg.tar.gz"
gh release upload "$GITHUB_REF_NAME" "$pkg"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Publish
if: runner.os == 'Windows'
run: |
exe=maid-$("$binpath" -q version).exe
cp "$binpath" "$exe"
strip "$exe" && upx "$exe"
exe=maid-$(./maid.exe -q version).exe
cp maid.exe "$exe"
gh release upload "$GITHUB_REF_NAME" "$exe"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
30 changes: 4 additions & 26 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,31 +24,9 @@ jobs:

steps:
- uses: actions/checkout@v4
- uses: haskell-actions/setup@v2
- uses: mlugg/setup-zig@v1
with:
ghc-version: "9.4.8"
cabal-version: "3.10.3.0"
- name: Install dependencies
if: runner.os == 'Windows'
run: |
choco install upx
version: 0.14.0-dev.2563+af5e73172

- name: Build
run: |
cabal update
cabal configure \
--enable-optimization=2 \
--enable-static --enable-executable-static \
--enable-executable-stripping
cabal build
echo "binpath=$(cabal list-bin maid)" >> $GITHUB_ENV
- name: Check binary size
run: |
cp "$binpath" maid
strip maid -o maid-s
upx maid-s -o maid-u
echo "No strip, no compression"
du -hs maid
echo "Stripped, no compression"
du -hs maid-s
echo "Stripped, compressed"
du -hs maid-u
run: zig build-exe src/main.zig --name maid
23 changes: 7 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Of course, you can use maid to run its own tasks!
Build the executable

```sh
cabal build maid
zig build-exe src/main.zig --name maid
```
````

Expand Down Expand Up @@ -76,34 +76,25 @@ Of course, you can use maid to run its own tasks!
Build the executable

```sh
cabal build maid
zig build-exe src/main.zig --name maid
```

### install

Install project onto `$dstdir`

```sh
install -Dm755 "$(cabal list-bin maid)" "$dstdir/bin/maid"
install -Dm644 LICENSE -t "$dstdir/share/licenses/maid"
install -Dm644 extras/completion/zsh "$dstdir/share/zsh/site-functions/_maid"
install -Dm755 maid "$dstdir/bin/maid"
install -Dm644 LICENSE -t "$dstdir/share/licenses/maid"
install -Dm644 extras/completion/zsh "$dstdir/share/zsh/site-functions/_maid"
install -Dm644 extras/completion/fish "$dstdir/share/fish/vendor_completions.d/maid.fish"
install -Dm644 extras/completion/bash "$dstdir/share/bash-completion/completions/maid"
ln -s maid "$dstdir/bin/made"
```

### version

Display the current version

This is used in build scripts

```hs
import Data.Maybe
import qualified Data.Text as T
import qualified Data.Text.IO as I

main = I.readFile "maid-build.cabal"
>>= (I.putStrLn . T.strip)
. (head . mapMaybe (T.stripPrefix $ T.pack "version:") . T.lines)
```sh
git describe --abbrev=0
```
147 changes: 147 additions & 0 deletions src/Parser.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
alloc: std.mem.Allocator,
line: std.ArrayList(u8),
block: std.ArrayList(u8),

headingNr: usize = 0,
fenceNr: usize = 0,
lineNr: usize = 0,
lang: ?Task.Lang = null,

pub fn init(alloc: std.mem.Allocator) !Parser {
const line = try std.ArrayList(u8).initCapacity(alloc, 32);
const block = try std.ArrayList(u8).initCapacity(alloc, 128);

return .{ .alloc = alloc, .line = line, .block = block };
}

pub fn deinit(self: *Parser) void {
self.line.deinit();
self.block.deinit();
}

pub fn parseMarkdown(self: *Parser, file: anytype) !std.ArrayList(Task) {
var tasks = std.ArrayList(Task).init(self.alloc);
var task: Task = undefined;

var tasksHeading: usize = 0;
var state: enum { tasklist, magic, task, desc, code } = .tasklist;

while (self.nextBlock(file) catch null) |_| sw: switch (state) {
.tasklist => {
if (self.headingNr == 0) continue;

state = .magic;
tasksHeading = self.headingNr;
},
.magic => {
state = if (isMagic(self.block.items)) .task else .tasklist;
},
.task => {
if (self.headingNr == 0) continue;
if (self.headingNr <= tasksHeading) break;

state = .desc;
task.name = try std.ascii.allocLowerString(self.alloc, self.block.items);
},
.desc => {
if (self.headingNr > 0) continue :sw .task;

state = .code;
task.desc = try self.alloc.dupe(u8, if (self.fenceNr == 0)
trim(u8, self.block.items, " \t")
else
"[No description]");
continue :sw state;
},
.code => {
if (self.headingNr > 0) continue :sw .task;
if (self.fenceNr == 0) continue;

state = .task;
task.code.lang = self.lang orelse return error.UnknownLanguage;
task.code.text = try self.alloc.dupe(u8, self.block.items);
try tasks.append(task);
},
};

return tasks;
}

fn nextBlock(self: *Parser, file: anytype) !void {
self.block.clearRetainingCapacity();

while (true) {
try self.nextLine(file);
if (!isBlank(self.line.items)) break;
}

self.headingNr = heading(self.line.items);
self.fenceNr = fence(self.line.items);

if (self.headingNr > 0) {
try self.block.appendSlice(trim(u8, self.line.items[self.headingNr..], " \t"));
} else if (self.fenceNr > 0) {
self.lang = Task.Lang.fromId(trim(u8, self.line.items[self.fenceNr..], " \t"));

while (true) : ({
try self.block.append('\n');
try self.block.appendSlice(self.line.items);
}) {
try self.nextLine(file);
if (fence(self.line.items) >= self.fenceNr) break;
}
} else {
try self.block.appendSlice(self.line.items);

while (true) : ({
try self.block.append(' ');
try self.block.appendSlice(self.line.items);
}) {
try self.nextLine(file);
if (isBlank(self.line.items)) break;
}
}
}

fn nextLine(self: *Parser, file: anytype) !void {
self.line.clearRetainingCapacity();
self.lineNr += 1;

return file.streamUntilDelimiter(self.line.writer(), '\n', null) catch |err|
if (self.line.items.len == 0) err;
}

fn isMagic(text: []u8) bool {
return std.mem.startsWith(u8, text, "<!-- maid-tasks -->");
}

fn isBlank(text: []u8) bool {
for (text) |c| if (c != ' ' and c != '\t') return false;

return true;
}

fn fence(text: []u8) usize {
if (text.len < 3)
return 0;
if (text[0] == '`' or text[0] == '~') {
for (text[1..], 1..) |c, i| {
if (c != text[0]) return if (i < 3) 0 else i;
}
return text.len;
}
return 0;
}

fn heading(text: []u8) usize {
for (text, 0..) |c, i| {
if (i > 6) return 0;
if (c != '#') return i;
}
return 0;
}

const std = @import("std");
const trim = std.mem.trim;
const Parser = @This();
const Task = @import("Task.zig");
46 changes: 46 additions & 0 deletions src/Style.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
primary: Color,
secondary: Color,
reset: Color,
err: Color,

pub fn default() Self {
return if (std.process.hasEnvVarConstant("NO_COLOR") or
!std.io.getStdOut().getOrEnableAnsiEscapeSupport())
.{
.primary = .none,
.secondary = .none,
.reset = .none,
.err = .none,
}
else
.{
.primary = Color.make("95;1"),
.secondary = Color.make("0;1"),
.reset = Color.make(""),
.err = Color.make("31;1"),
};
}

pub const Color = union(enum) {
esc: []const u8,
none,

fn make(esc: []const u8) Color {
return .{ .esc = esc };
}

pub fn format(
self: Color,
comptime _: []const u8,
_: std.fmt.FormatOptions,
writer: anytype,
) !void {
switch (self) {
.esc => |esc| try std.fmt.format(writer, "\x1b[{s}m", .{esc}),
else => {},
}
}
};

const std = @import("std");
const Self = @This();
21 changes: 21 additions & 0 deletions src/Task.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: []u8,
desc: []u8,
code: struct { lang: Lang, text: []u8 },

pub const Lang = enum {
shell,
haskell,
javascript,

pub fn fromId(id: []const u8) ?Lang {
return langIds.get(id);
}
};

const langIds = std.StaticStringMap(Lang).initComptime(.{
.{ "sh", .shell }, .{ "bash", .shell },
.{ "hs", .haskell }, .{ "haskell", .haskell },
.{ "js", .javascript }, .{ "javascript", .javascript },
});

const std = @import("std");
Loading