Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f8bc389ae5 | |||
| 72efcb13ba | |||
| 096216579d | |||
| d692fa5a4c |
74
.gitea/workflows/release.yml
Normal file
74
.gitea/workflows/release.yml
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
name: Release Binaries
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ${{ matrix.runner }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- arch: amd64
|
||||||
|
platform: linux/amd64
|
||||||
|
image: golang:1.24-alpine
|
||||||
|
runner: linux-amd64
|
||||||
|
upx_install: "apk add --no-cache upx"
|
||||||
|
- arch: arm64
|
||||||
|
platform: linux/arm64
|
||||||
|
image: golang:1.24-alpine
|
||||||
|
runner: linux-arm64
|
||||||
|
upx_install: "apk add --no-cache upx zstd-static"
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Build (${{ matrix.arch }})
|
||||||
|
run: |
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
VERSION="$(git describe --tags --always --dirty)"
|
||||||
|
echo "Version: $VERSION"
|
||||||
|
|
||||||
|
# Crea il container senza avviarlo
|
||||||
|
CID=$(docker create \
|
||||||
|
--platform ${{ matrix.platform }} \
|
||||||
|
-w /src \
|
||||||
|
${{ matrix.image }} \
|
||||||
|
sleep infinity)
|
||||||
|
|
||||||
|
# Copia i sorgenti dentro con docker cp (funziona anche in DinD)
|
||||||
|
docker cp "${{ github.workspace }}/." "$CID:/src"
|
||||||
|
|
||||||
|
# Avvia ed esegui la build
|
||||||
|
docker start "$CID"
|
||||||
|
docker exec "$CID" sh -c "
|
||||||
|
${{ matrix.upx_install }}
|
||||||
|
export CGO_ENABLED=0
|
||||||
|
go build -trimpath -ldflags='-s -w -X main.version=$VERSION' -o /src/dist/todotxt2remind .
|
||||||
|
upx /src/dist/todotxt2remind
|
||||||
|
" 2>&1 | cat
|
||||||
|
|
||||||
|
# Copia il binario fuori
|
||||||
|
docker cp "$CID:/src/dist/todotxt2remind" \
|
||||||
|
"${{ github.workspace }}/todotxt2remind-linux-${{ matrix.arch }}"
|
||||||
|
|
||||||
|
docker rm -f "$CID"
|
||||||
|
|
||||||
|
- name: Carica artefatto sulla release
|
||||||
|
env:
|
||||||
|
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
RELEASE_ID: ${{ github.event.release.id }}
|
||||||
|
run: |
|
||||||
|
FILENAME="todotxt2remind-linux-${{ matrix.arch }}"
|
||||||
|
curl -s -X POST \
|
||||||
|
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases/${RELEASE_ID}/assets?name=${FILENAME}" \
|
||||||
|
-H "Authorization: token ${TOKEN}" \
|
||||||
|
-H "Content-Type: application/octet-stream" \
|
||||||
|
--data-binary "@${FILENAME}"
|
||||||
@@ -44,10 +44,10 @@ func (t Task) ToRemind(indent int) string {
|
|||||||
// k must have the first letter uppercase for Remind
|
// k must have the first letter uppercase for Remind
|
||||||
kUpper := strings.ToUpper(k[:1]) + k[1:]
|
kUpper := strings.ToUpper(k[:1]) + k[1:]
|
||||||
if len(values) == 1 {
|
if len(values) == 1 {
|
||||||
sb.WriteString(fmt.Sprintf("INFO \"%s: %s\" ", kUpper, values[0]))
|
fmt.Fprintf(&sb, "INFO \"%s: %s\" ", kUpper, values[0])
|
||||||
} else {
|
} else {
|
||||||
for i, v := range values {
|
for i, v := range values {
|
||||||
sb.WriteString(fmt.Sprintf("INFO \"%s%d: %s\" ", kUpper, i+1, v))
|
fmt.Fprintf(&sb, "INFO \"%s%d: %s\" ", kUpper, i+1, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -55,22 +55,22 @@ func (t Task) ToRemind(indent int) string {
|
|||||||
|
|
||||||
if len(t.Projects) > 0 {
|
if len(t.Projects) > 0 {
|
||||||
for _, p := range t.Projects {
|
for _, p := range t.Projects {
|
||||||
sb.WriteString(fmt.Sprintf("INFO \"List: %s\" ", p))
|
fmt.Fprintf(&sb, "INFO \"List: %s\" ", p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(t.Contexts) > 0 {
|
if len(t.Contexts) > 0 {
|
||||||
for _, c := range t.Contexts {
|
for _, c := range t.Contexts {
|
||||||
sb.WriteString(fmt.Sprintf("INFO \"Tag: %s\" ", c))
|
fmt.Fprintf(&sb, "INFO \"Tag: %s\" ", c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.Completed {
|
if t.Completed {
|
||||||
sb.WriteString(fmt.Sprintf("COMPLETE-THROUGH %s ", t.DueDate.Format("2006-01-02")))
|
fmt.Fprintf(&sb, "COMPLETE-THROUGH %s ", t.DueDate.Format("2006-01-02"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.Priority != nil {
|
if t.Priority != nil {
|
||||||
sb.WriteString(fmt.Sprintf("PRIORITY %d ", t.PriorityAsRemind()))
|
fmt.Fprintf(&sb, "PRIORITY %d ", t.PriorityAsRemind())
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.WriteString("MSG")
|
sb.WriteString("MSG")
|
||||||
@@ -79,7 +79,7 @@ func (t Task) ToRemind(indent int) string {
|
|||||||
sb.WriteString(" %<List>:")
|
sb.WriteString(" %<List>:")
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.WriteString(fmt.Sprintf(" %s", t.Description))
|
fmt.Fprintf(&sb, " %s", t.Description)
|
||||||
|
|
||||||
if t.Completed {
|
if t.Completed {
|
||||||
sb.WriteString("%:")
|
sb.WriteString("%:")
|
||||||
@@ -97,11 +97,11 @@ func (t Task) ToRemind(indent int) string {
|
|||||||
// uppercase first letter for Remind
|
// uppercase first letter for Remind
|
||||||
kUpper := strings.ToUpper(k[:1]) + k[1:]
|
kUpper := strings.ToUpper(k[:1]) + k[1:]
|
||||||
if len(values) == 1 {
|
if len(values) == 1 {
|
||||||
sb.WriteString(fmt.Sprintf("%%_%s%s: %%<%s>", headingSpaces, kUpper, kUpper))
|
fmt.Fprintf(&sb, "%%_%s%s: %%<%s>", headingSpaces, kUpper, kUpper)
|
||||||
} else {
|
} else {
|
||||||
for i := range values {
|
for i := range values {
|
||||||
kNumbered := fmt.Sprintf("%s%d", kUpper, i+1)
|
kNumbered := fmt.Sprintf("%s%d", kUpper, i+1)
|
||||||
sb.WriteString(fmt.Sprintf("%%_%s%s: %%<%s>", headingSpaces, kNumbered, kNumbered))
|
fmt.Fprintf(&sb, "%%_%s%s: %%<%s>", headingSpaces, kNumbered, kNumbered)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -202,6 +202,7 @@ func ParseLine(line string) (Task, error) {
|
|||||||
working := strings.TrimSpace(line)
|
working := strings.TrimSpace(line)
|
||||||
toks := splitTokens(working)
|
toks := splitTokens(working)
|
||||||
i := 0
|
i := 0
|
||||||
|
var invalidDue string
|
||||||
|
|
||||||
// 1) Completed?
|
// 1) Completed?
|
||||||
if i < len(toks) && toks[i] == "x" {
|
if i < len(toks) && toks[i] == "x" {
|
||||||
@@ -230,14 +231,8 @@ func ParseLine(line string) (Task, error) {
|
|||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5) If not completed, check for priority at start
|
// 5) Creation date for incomplete tasks (priority, if any, was already consumed in step 4)
|
||||||
if !t.Completed {
|
if !t.Completed {
|
||||||
if i < len(toks) && len(toks[i]) == 3 && toks[i][0] == '(' && toks[i][2] == ')' && toks[i][1] >= 'A' && toks[i][1] <= 'Z' {
|
|
||||||
r := rune(toks[i][1])
|
|
||||||
t.Priority = &r
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
// Creation date for incomplete tasks
|
|
||||||
if i < len(toks) && dateRe.MatchString(toks[i]) {
|
if i < len(toks) && dateRe.MatchString(toks[i]) {
|
||||||
if dt, err := time.Parse(dateLayout, toks[i]); err == nil {
|
if dt, err := time.Parse(dateLayout, toks[i]); err == nil {
|
||||||
t.CreationDate = &dt
|
t.CreationDate = &dt
|
||||||
@@ -285,9 +280,12 @@ func ParseLine(line string) (Task, error) {
|
|||||||
k, v := parts[0], parts[1]
|
k, v := parts[0], parts[1]
|
||||||
if k != "" && v != "" && !isProtocolKey(k) {
|
if k != "" && v != "" && !isProtocolKey(k) {
|
||||||
t.Metadata[k] = append(t.Metadata[k], v)
|
t.Metadata[k] = append(t.Metadata[k], v)
|
||||||
if k == "due" && dateRe.MatchString(v) {
|
if k == "due" {
|
||||||
if dt, err := time.Parse(dateLayout, v); err == nil {
|
dt, err := time.Parse(dateLayout, v)
|
||||||
|
if dateRe.MatchString(v) && err == nil {
|
||||||
t.DueDate = &dt
|
t.DueDate = &dt
|
||||||
|
} else {
|
||||||
|
invalidDue = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
@@ -302,6 +300,10 @@ func ParseLine(line string) (Task, error) {
|
|||||||
return t, errors.New("completed task missing completion date (spec requires completion date directly after 'x')")
|
return t, errors.New("completed task missing completion date (spec requires completion date directly after 'x')")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if invalidDue != "" {
|
||||||
|
return t, fmt.Errorf("invalid due date %q: expected format YYYY-MM-DD (task will be dropped, no Remind entry generated)", invalidDue)
|
||||||
|
}
|
||||||
|
|
||||||
return t, nil
|
return t, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
2
main.go
2
main.go
@@ -15,7 +15,7 @@ var (
|
|||||||
outputFile string
|
outputFile string
|
||||||
debug bool
|
debug bool
|
||||||
indent int
|
indent int
|
||||||
version = "v3.0.0"
|
version = "dev" // overridden at build time via -ldflags "-X main.version=..."
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
@@ -73,6 +73,9 @@ X 2012-01-01 Make resolutions
|
|||||||
(A) 2024-13-99 Call Mom
|
(A) 2024-13-99 Call Mom
|
||||||
x 2024-99-99 Call Mom
|
x 2024-99-99 Call Mom
|
||||||
|
|
||||||
|
# Malformed: due date with invalid format (should error, task dropped from output)
|
||||||
|
(A) Call Mom due:2024-13-99
|
||||||
|
|
||||||
# Malformed: key:value with whitespace in key or value
|
# Malformed: key:value with whitespace in key or value
|
||||||
foo bar:baz
|
foo bar:baz
|
||||||
foo:bar baz
|
foo:bar baz
|
||||||
|
|||||||
Reference in New Issue
Block a user