Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
dfc4af9
chore: setup unified CI/CD with GoReleaser
google-labs-jules[bot] Jul 6, 2026
0e29388
fix(test): add build ignore to test.go to prevent multiple main decla…
google-labs-jules[bot] Jul 6, 2026
1508e93
fix(lint): fix golangci-lint errors
google-labs-jules[bot] Jul 6, 2026
6ca4710
fix(lint): fix missing raw string on horizontal regex and add returns
google-labs-jules[bot] Jul 6, 2026
fdd33fa
fix: address PR review feedback
google-labs-jules[bot] Jul 6, 2026
4d26c13
fix: address further PR feedback
google-labs-jules[bot] Jul 6, 2026
a30b5ad
fix(lint): fix final golangci-lint errors
google-labs-jules[bot] Jul 6, 2026
65813f7
test: migrate test suite to txtar patterns
google-labs-jules[bot] Jul 6, 2026
7e1dd73
fix(ci): address PR review comments
google-labs-jules[bot] Jul 7, 2026
a0916db
Merge pull request #1 from arran4/ci-setup-14036128997264128634
arran4 Jul 7, 2026
30dbe21
Fix duplicate `with` keys in GitHub Actions workflow
google-labs-jules[bot] Jul 8, 2026
c7b27ad
Fix unreachable code in `regexp.go`
google-labs-jules[bot] Jul 8, 2026
96dc798
Fix golangci-lint action configuration and missing returns
google-labs-jules[bot] Jul 8, 2026
5188d38
Fix golangci-lint version format and unreachable code
google-labs-jules[bot] Jul 8, 2026
836a844
Fix golangci-lint version and unreachable code
google-labs-jules[bot] Jul 8, 2026
779be9a
Fix golangci-lint version format and unreachable code
google-labs-jules[bot] Jul 8, 2026
4010cb2
Fix golangci-lint version to v1.64.8 and go vet
google-labs-jules[bot] Jul 9, 2026
81cdd7b
Fix remaining golangci-lint errors and configurations
google-labs-jules[bot] Jul 9, 2026
3a46b99
Merge pull request #2 from arran4/fix-ci-with-duplicate-1789207080030…
arran4 Jul 9, 2026
6225d84
Fix module path constraint conflict by updating go.mod to match fork
google-labs-jules[bot] Jul 9, 2026
3ac995f
Merge pull request #3 from arran4/fix-module-name-7942310070611481958
arran4 Jul 9, 2026
65cf7ae
fix: TestTxtar/paragraph_br failing test and whitespace handling
google-labs-jules[bot] Jul 9, 2026
3956933
fix: properly handle line breaks in markdown paragraphs and pass tests
google-labs-jules[bot] Jul 9, 2026
ab1e46e
fix: test failure in `TestTxtar/paragraph_br` and ci workflow errors
google-labs-jules[bot] Jul 9, 2026
a241f84
Merge branch 'master' into fix-paragraph-br-2043684537882370607
arran4 Jul 10, 2026
5ac1366
Merge pull request #4 from arran4/fix-paragraph-br-2043684537882370607
arran4 Jul 10, 2026
52cd24c
Fix failing `paragraph_br` test by adding trailing spaces
google-labs-jules[bot] Jul 10, 2026
e4ec9c7
Fix failing `paragraph_br` test by adding trailing spaces
google-labs-jules[bot] Jul 10, 2026
936c21d
Fix testdata trailing whitespace and golangci-lint version in CI
google-labs-jules[bot] Jul 10, 2026
0d423e9
Fix testdata trailing whitespace and golangci-lint version in CI
google-labs-jules[bot] Jul 10, 2026
5910fd7
Fix testdata trailing whitespace and golangci-lint version in CI
google-labs-jules[bot] Jul 10, 2026
8485919
Fix trailing whitespace bug in test parser
google-labs-jules[bot] Jul 10, 2026
0ab4cb8
Support backslash for hard line breaks in Markdown and fix CI version
google-labs-jules[bot] Jul 10, 2026
48c5dee
Merge pull request #6 from arran4/fix-paragraph-br-test-1483770384786…
arran4 Jul 10, 2026
1b2e0df
Fix CLI argument parsing, errors, and filename handling
google-labs-jules[bot] Jul 10, 2026
75df197
Refine CLI argument parsing and error outputs based on review
google-labs-jules[bot] Jul 10, 2026
9d5eb2f
Merge pull request #7 from arran4/fix/cli-args-and-errors-14291488349…
arran4 Jul 10, 2026
cea962b
build: remove Makefile and use standard go commands
google-labs-jules[bot] Jul 10, 2026
0f77649
docs: update README usage section to use go run and go install
google-labs-jules[bot] Jul 10, 2026
fd8bd3f
Merge pull request #8 from arran4/remove-makefile-16032170689295780075
arran4 Jul 10, 2026
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
476 changes: 476 additions & 0 deletions .github/workflows/ci.yml

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions .gitleaks.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
title = "repo gitleaks config"

[allowlist]
description = "global allowlist"
paths = [
'''^docs/''',
'''^testdata/'''
]
28 changes: 28 additions & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
project_name: mdtohtml

before:
hooks:
- go mod tidy

builds:
- id: app
binary: mdtohtml
main: ./
env:
- CGO_ENABLED=0

archives:
- formats: [tar.gz]
format_overrides:
- goos: windows
formats: [zip]

checksum:
name_template: checksums.txt

changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
11 changes: 0 additions & 11 deletions Makefile

This file was deleted.

18 changes: 15 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,24 @@ Mdtohtml is a HTML generator from a Markdown file. This is implemented in Go. Th

The syntax of Markdown follows [CommonMark](https://commonmark.org/) which version is [0.29 (2019-04-06)](https://spec.commonmark.org/). They have playground, [commonmark.js dingus](https://spec.commonmark.org/dingus/), for CommonMark grammar.

## Install

### GitHub Releases
Download binaries from: https://github.com/mdtohtml/mdtohtml/releases

### Go install
go install github.com/mdtohtml/mdtohtml@latest

## Usage
```
$ make mdtohtml & ./mdtohtml <markdown-filename>
$ go run . <markdown-filename>

// You can avoid to generate css file with -nocss flag in order to customize style.
$ make mdtohtml & ./mdtohtml <markdown-filename> -nocss
$ go run . <markdown-filename> -nocss

// Alternatively, if you have installed the tool using `go install`:
$ mdtohtml <markdown-filename>
$ mdtohtml <markdown-filename> -nocss
```

## Current support notations (2019-10-14)
Expand Down Expand Up @@ -43,4 +55,4 @@ Newline = "\n" ;
```

## Test
Supports test written in Go. You can test this tool just by `$ make test`.
Supports test written in Go. You can test this tool just by `$ go test ./...`.
8 changes: 5 additions & 3 deletions gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,15 @@ func generate(lines []Line) string {
dep := l.dep - lines[i+1].dep
for dep > 0 {
html += "</ul>"
dep -= 1
dep--
}
}
// insert </ul> for the end of sublists when a document ends with lists
if i == len(lines)-1 {
dep := l.dep
for dep > 0 {
html += "</ul>"
dep -= 1
dep--
}
}

Expand All @@ -67,7 +67,9 @@ func generate(lines []Line) string {
default:
// insert a white space in a paragraph
if (i > 0 && lines[i-1].ty == P) && (i < len(lines)-1 && lines[i+1].ty == P) {
html += l.val
if !lines[i-1].hasBr {
html += " "
}
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/mdtohtml/mdtohtml

go 1.25.0

require golang.org/x/tools v0.47.0
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
77 changes: 63 additions & 14 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,82 @@ import (
"bufio"
"fmt"
"os"
"path/filepath"
"strings"
)

func check(e error) {
if e != nil {
panic(e)
fmt.Fprintf(os.Stderr, "error: %v\n", e)
os.Exit(1)
}
}

func usage() {
fmt.Fprintf(os.Stderr, "Usage: %s <markdown-filename> [-nocss]\n", os.Args[0])
os.Exit(1)
}

func main() {
fname := os.Args[1]
name := strings.Split(fname, ".")
if len(os.Args) < 2 {
usage()
}

var fname string
var noCSS bool

if strings.Compare(strings.ToLower(name[len(name)-1]), "md") != 0 {
panic("input file must be a markdown file (.md)")
for _, arg := range os.Args[1:] {
if arg == "-h" || arg == "--help" || arg == "-help" {
usage()
} else if arg == "-nocss" {
noCSS = true
} else if strings.HasPrefix(arg, "-") {
fmt.Fprintf(os.Stderr, "error: unknown flag %s\n", arg)
usage()
} else if fname != "" {
fmt.Fprintf(os.Stderr, "error: multiple input files specified\n")
usage()
} else {
fname = arg
}
}

if fname == "" {
fmt.Fprintf(os.Stderr, "error: missing markdown filename\n")
usage()
}

wfile, err := os.Create(name[0] + ".html")
ext := filepath.Ext(fname)
if strings.ToLower(ext) != ".md" {
fmt.Fprintf(os.Stderr, "error: input file must be a markdown file (.md)\n")
os.Exit(1)
}

base := strings.TrimSuffix(fname, ext)

wfile, err := os.Create(base + ".html")
check(err)
defer wfile.Close()

defer func() {
if err := wfile.Close(); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
}()
writer := bufio.NewWriter(wfile)
if len(os.Args) < 3 || os.Args[2] != "-nocss" {
fmt.Fprintln(writer, css())
if !noCSS {
_, err = fmt.Fprintln(writer, css())
check(err)
}

rfile, err := os.Open(fname)
check(err)
defer rfile.Close()

defer func() {
if err := rfile.Close(); err != nil {
fmt.Fprintf(os.Stderr, "error closing input file: %v\n", err)
}
}()
reader := bufio.NewReader(rfile)

lines := make([]Line, 0)
Expand All @@ -47,8 +95,9 @@ func main() {
lines = append(lines, convert(line))
}

writer.WriteString("<body>")
writer.WriteString(generate(lines))
writer.WriteString("</body>")
writer.Flush()
_, _ = writer.WriteString("<body>")
_, _ = writer.WriteString(generate(lines))
_, _ = writer.WriteString("</body>")
err = writer.Flush()
check(err)
}
72 changes: 72 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package main

import (
"embed"
"io/fs"
"path"
"strings"
"testing"
"testing/fstest"

"golang.org/x/tools/txtar"
)

//go:embed testdata/txtar/*.txtar
var testdataFS embed.FS

func SplitInputExpected(ar *txtar.Archive) (input, expected fstest.MapFS) {
input = fstest.MapFS{}
expected = fstest.MapFS{}

for _, f := range ar.Files {
switch f.Name {
case "input.txt":
input[f.Name] = &fstest.MapFile{Data: f.Data}
case "expected.html":
expected[f.Name] = &fstest.MapFile{Data: f.Data}
}
}
return input, expected
}

func TestTxtar(t *testing.T) {
entries, err := fs.Glob(testdataFS, "testdata/txtar/*.txtar")
if err != nil {
t.Fatalf("glob fixtures: %v", err)
}

for _, fixture := range entries {
fixture := fixture
t.Run(strings.TrimSuffix(path.Base(fixture), ".txtar"), func(t *testing.T) {
raw, err := testdataFS.ReadFile(fixture)
if err != nil {
t.Fatalf("read fixture %s: %v", fixture, err)
}
ar := txtar.Parse(raw)

inputFS, expectedFS := SplitInputExpected(ar)

inputRaw, err := fs.ReadFile(inputFS, "input.txt")
if err != nil {
t.Fatalf("read input.txt: %v", err)
}
input := strings.TrimSpace(string(inputRaw))

lines := make([]Line, 0)
for _, in := range strings.Split(input, "\n") {
lines = append(lines, convert(in))
}
html := generate(lines)

expectedRaw, err := fs.ReadFile(expectedFS, "expected.html")
if err != nil {
t.Fatalf("read expected.html: %v", err)
}
expected := strings.TrimSpace(string(expectedRaw))

if html != expected {
t.Errorf("%q => expected %q but got %q", input, expected, html)
}
})
}
}
Loading