Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
e0ac89e
docs(cmd)
TheRootDaemon Jul 20, 2026
6bdff05
tests(cmd)
TheRootDaemon Jul 20, 2026
7c3eb5a
tests(app): Add tests for search
TheRootDaemon Jul 20, 2026
db15e9b
tests(app): Add tests for info
TheRootDaemon Jul 20, 2026
de717d8
refactor(app): Simplify funcs, add tests
TheRootDaemon Jul 20, 2026
37072b8
refator(app): Reduce cyclo for Run, Resolves #13
TheRootDaemon Jul 20, 2026
55554b4
fix(app): Fixed resource leaks
TheRootDaemon Jul 20, 2026
da3a6a5
feat(app): Include contribution links
TheRootDaemon Jul 20, 2026
692e141
cli: Add completions
TheRootDaemon Jul 20, 2026
8a04198
cmd: Error handling, similar flags
TheRootDaemon Jul 25, 2026
fb70c62
tests: Fix flaky tests
TheRootDaemon Jul 26, 2026
7d8d3da
cmd: Change help flag
TheRootDaemon Jul 27, 2026
264cdbc
feat(browse): Add cross-platform browser capabilities for --browse
TheRootDaemon Jul 27, 2026
e56b2d6
feat(cmd): Add -b,--browse flag
TheRootDaemon Jul 27, 2026
4971cbc
feat(cmd): Require a page argument for -b,--browse
TheRootDaemon Jul 27, 2026
5caeeff
feat(render): Add BuildViewURL
TheRootDaemon Jul 27, 2026
b704948
feat(app): Implement -b,--browse
TheRootDaemon Jul 27, 2026
e1e7bcd
tests: Add cases for darwin, windows. Skip irrelevant tests
TheRootDaemon Jul 27, 2026
b1f80f8
chore: Add help string for -b,--browse
TheRootDaemon Jul 28, 2026
6c6bc11
cmd: Fix no-op cases
TheRootDaemon Jul 28, 2026
4d01007
bin: tlgc -> tldr
TheRootDaemon Jul 30, 2026
d526c20
init(lint): Linting rules
TheRootDaemon Aug 1, 2026
f76906f
feat(lint): Filename rules
TheRootDaemon Aug 1, 2026
f382f77
docs(lint): Fix docs
TheRootDaemon Aug 1, 2026
3c000f7
lint: Implement parser
TheRootDaemon Aug 2, 2026
e95d823
lint: Tests for parse
TheRootDaemon Aug 2, 2026
5df0712
lint: Tests for parse_lines
TheRootDaemon Aug 2, 2026
2f4bd54
lint: Tests for parse_sections
TheRootDaemon Aug 2, 2026
3e2e9eb
lint: Tests for title_rules
TheRootDaemon Aug 2, 2026
10f03c5
lint: Title rules
TheRootDaemon Aug 2, 2026
8c20774
lint: File rules
TheRootDaemon Aug 4, 2026
0bae0de
lint: Description rules
TheRootDaemon Aug 6, 2026
a7932cb
lint: example rules
TheRootDaemon Aug 6, 2026
bf2fd44
lint: command rules
TheRootDaemon Aug 7, 2026
0e2b2b7
lint: global Lint
TheRootDaemon Aug 7, 2026
aa1f264
lint: Remove unused field
TheRootDaemon Aug 7, 2026
78d53e0
fix(lint): align whitespace rules with reference behavior
TheRootDaemon Aug 8, 2026
d6a2792
fix(lint): report TLDR105 only for extra commands
TheRootDaemon Aug 8, 2026
59212fc
lint: Add specifications
TheRootDaemon Aug 8, 2026
b5db45e
fix(lint): Avoid platform dependent filename checks
TheRootDaemon Aug 10, 2026
f9e2f02
tests(cache): Add deterministic times to avoid brittle edge cases,
TheRootDaemon Aug 10, 2026
9f8d31e
tests(cache): Refactor similar tests into table driven tests
TheRootDaemon Aug 10, 2026
4234b5e
lint: Format
TheRootDaemon Aug 10, 2026
62f2b00
test(lint): Tests for error strings
TheRootDaemon Aug 10, 2026
ce58d3d
cmd: Add flags/modifiers for --lint, --format
TheRootDaemon Aug 10, 2026
10d62f4
test(cache): Fix brittle test on info_test
TheRootDaemon Aug 10, 2026
fb6b127
chore
TheRootDaemon Aug 12, 2026
1db1280
fix(lint): Reach flag dependency error for --output before parsing
TheRootDaemon Aug 12, 2026
1d69364
feat(app): Lint
TheRootDaemon Aug 12, 2026
9804675
chore(help): Add help strings, completions
TheRootDaemon Aug 12, 2026
a74a9f9
app: Format handlers, linting refactors
TheRootDaemon Aug 13, 2026
e1b8d57
fix(lint): 1-indexed linting
TheRootDaemon Aug 14, 2026
b11e343
chore: Add help strings, completions for --format
TheRootDaemon Aug 14, 2026
fba6015
chore: Improve TUI
TheRootDaemon Aug 14, 2026
f5cb14e
chore: Move error as the last argument
TheRootDaemon Aug 14, 2026
1297764
chore: Format test files
TheRootDaemon Aug 15, 2026
b49b0c9
feat(cmd): Enforce parent-child flag dependencies
TheRootDaemon Aug 16, 2026
50bacbf
fix(app): Auto Update edge cases, Fixes #21
TheRootDaemon Aug 17, 2026
3eb5b47
fix(app): Auto update fires only for the commands that need them
TheRootDaemon Aug 22, 2026
ad05f15
fix(cache): Skip miscellaneous files
TheRootDaemon Aug 22, 2026
fc7908b
feat(cache): Page stats
TheRootDaemon Aug 23, 2026
99fdce6
fix: Update stats
TheRootDaemon Aug 29, 2026
0bc68ed
fix: Incorrect remaining time
TheRootDaemon Aug 29, 2026
ce7202f
tests(cache): Refactor tests, add tests fro page stats
TheRootDaemon Sep 2, 2026
5f59c02
feat(cache): Update cache during info
TheRootDaemon Sep 2, 2026
b0fa50c
fix: GOSEC 304, refactor hashPages
TheRootDaemon Sep 2, 2026
b36ee8e
fix: Fix windows paths in the lookup map
TheRootDaemon Sep 2, 2026
b3c8f72
docs: Add man page, update README
TheRootDaemon Sep 4, 2026
eac8a7e
fix: Auto updates, Fixes #21
TheRootDaemon Sep 4, 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
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
* text=auto eol=lf

# Required for TLDR010 (Only Unix-style line endings allowed)
internal/lint/specs/pages/failing/010.md binary
17 changes: 5 additions & 12 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@ PKGS_WITH_TESTS := $(shell go list -f '{{if .TestGoFiles}}{{.ImportPath}}{{end}}

VERSION := $(shell git describe --tags --always 2>/dev/null || echo "dev")

#
# Build targets
#

.PHONY: build
build:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
Expand All @@ -25,10 +22,7 @@ install:
-ldflags="-s -w -X github.com/TheRootDaemon/tlgc/version.Version=$(VERSION)" \
.

#
# Development targets
#

.PHONY: run
run:
go run ./main.go
Expand All @@ -37,10 +31,7 @@ run:
tidy:
go mod tidy

#
# Quality targets
#

.PHONY: check
check: fmt lint sec test vet

Expand Down Expand Up @@ -81,10 +72,12 @@ test:
vet:
go vet $(PKGS)

#
# Maintanence targets
#
# Documentation targets
.PHONY: man
man:
scdoc < tldr.md > tldr.1

# Maintanence targets
.PHONY: clean
clean:
rm -rf bin coverage.out
233 changes: 229 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,237 @@

A tldr client written in Go.

## Install
## Usage

See `tldr --help` for all options.

## Configuration

tlgc can be customized with a [TOML](https://toml.io) configuration file. To get the default path for your system, run:

```sh
go install github.com/TheRootDaemon/tlgc@latest
tldr --config-path
```

## Usage
To generate a default config file, run:

```sh
tldr --gen-config > "$(tldr --config-path)"
```

or copy the below example.

### Configuration options

```toml
[cache]
# Override the cache directory ('~' will be expanded to your home directory).
dir = "~/.cache/tlgc"
# Override the base URL used for downloading tldr pages.
# The mirror must provide files with the same names as the official tldr pages repository:
# mirror/tldr.sha256sums must point to the SHA256 checksums of all assets
# mirror/tldr-pages.LANGUAGE.zip must point to a zip archive that contains platform directories with pages in LANGUAGE
mirror = "https://github.com/tldr-pages/tldr/releases/latest/download"
# Automatically update the cache if it's older than max_age hours.
auto_update = true
# Perform the automatic update after the page is shown (the default is to update first, then show the page).
defer_auto_update = false
max_age = 336 # 336 hours = 2 weeks
# Specify a list of desired page languages. If it's empty, languages specified in
# the LANG and LANGUAGE environment variables are downloaded.
# English is implied and will always be downloaded.
# You can see a list of language codes here: https://github.com/tldr-pages/tldr
# Example: ["de", "pl"]
languages = []

[output]
# Show the title in the rendered page.
show_title = true
# Show the platform name ('common', 'linux', etc.) in the title.
platform_title = false
# Prefix descriptions of examples with hyphens.
show_hyphens = false
# Display a link to edit the shown page on GitHub.
edit_link = false
# Use a custom string instead of a hyphen.
example_prefix = "- "
# Set the max line length. 0 means wrapping is disabled.
# If a line is longer than this value, it will be split into multiple lines.
line_length = 0
# Strip blank separator lines from output.
compact = false
# In option placeholders, show the specified option style.
# Example: {{[-s|--long]}}
# short : -s
# long : --long
# both : [-s|--long]
option_style = "long"
# Print pages in raw markdown.
raw_markdown = false

# Number of spaces to put before each line of the page.
[indent]
# Command name.
title = 2
# Command description.
description = 2
# Descriptions of examples.
bullet = 2
# Example command invocations.
example = 4

# Style for the title of the page (command name).
[style.title]
# Fixed colors: "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white", "default",
# "bright_black", "bright_red", "bright_green", "bright_yellow", "bright_blue",
# "bright_magenta", "bright_cyan", "bright_white"
# 256color ANSI code: "color256:50"
# RGB: "rgb:0,255,255"
# Hex: "#ffffff"
color = "magenta"
background = "default"
bold = true
underline = false
italic = false
dim = false
strikethrough = false

# Style for the description of the page.
[style.description]
color = "magenta"
background = "default"
bold = false
underline = false
italic = false
dim = false
strikethrough = false

# Style for descriptions of examples.
[style.bullet]
color = "green"
background = "default"
bold = false
underline = false
italic = false
dim = false
strikethrough = false

# Style for command examples.
[style.example]
color = "cyan"
background = "default"
bold = false
underline = false
italic = false
dim = false
strikethrough = false

# Style for URLs inside the description.
[style.url]
color = "red"
background = "default"
bold = false
underline = false
italic = true
dim = false
strikethrough = false

# Style for text surrounded by backticks (`).
[style.inline_code]
color = "yellow"
background = "default"
bold = false
underline = false
italic = true
dim = false
strikethrough = false

# Style for placeholders inside command examples.
[style.placeholder]
color = "red"
background = "default"
bold = false
underline = false
italic = true
dim = false
strikethrough = false
```

## Linting

tlgc implements a built-in linter and a formatter for tldr pages.
It is intended to be a successor of [tldr-lint](https://github.com/tldr-pages/tldr-lint).

Validate one or more pages or directories:

```sh
tldr --lint path/to/page.md
```

Reformat pages to canonical style (stdout by default):

```sh
tldr --format path/to/page.md
```

Write the result back to the original file or to a new file:

```sh
tldr --format --in-place path/to/page.md
tldr --format --output path/to/result.md path/to/page.md
```

Modifiers:

- `--tabular` — display errors in a tabular format
- `--ignore TLDR005,TLDR019` — suppress specific error codes

### Error codes

#### Content rules

| Code | Description |
| ------- | ------------------------------------------------------------------------------------- |
| TLDR001 | File should contain no leading whitespace. |
| TLDR002 | A single space should precede a sentence. |
| TLDR003 | Descriptions should start with a capital letter. |
| TLDR004 | Command descriptions should end in a period. |
| TLDR005 | Example descriptions should end in a colon with no trailing characters. |
| TLDR006 | Command name and description should be separated by an empty line. |
| TLDR007 | Example descriptions should be surrounded by empty lines. |
| TLDR008 | File should contain no trailing whitespace. |
| TLDR009 | Page should contain a newline at end of file. |
| TLDR010 | Only Unix-style line endings allowed. |
| TLDR011 | Page should never contain more than a single empty line. |
| TLDR012 | Page should contain no tabs. |
| TLDR013 | Title should be alphanumeric with dashes, underscores, spaces, or allowed characters. |
| TLDR014 | Page should contain no trailing whitespace. |
| TLDR015 | Example descriptions should start with a capital letter. |
| TLDR016 | Label for information link should be spelled exactly `More information: `. |
| TLDR017 | Information link should be surrounded with angle brackets. |
| TLDR018 | Page should only include a single information link. |
| TLDR019 | Page should only include a maximum of 8 examples. |
| TLDR020 | Label for additional notes should be spelled exactly `Note: `. |
| TLDR021 | Command example should not begin or end in whitespace. |

#### Hint rules

| Code | Description |
| ------- | ---------------------------------------------------------------------------------------- |
| TLDR101 | Command description probably not properly annotated. |
| TLDR102 | Example description probably not properly annotated. |
| TLDR103 | Command example is missing its closing backtick. |
| TLDR104 | Example descriptions should prefer infinitive tense (e.g. write) over present or gerund. |
| TLDR105 | There should be only one command per example. |

#### Filename rules

See `tlgc --help` for all options.
| Code | Description |
| ------- | -------------------------------------------------------------------------------------------- |
| TLDR106 | Page title should start with a hash (`#`). |
| TLDR107 | File name should end with `.md` extension. |
| TLDR108 | File name should not contain whitespace. |
| TLDR109 | File name should be lowercase. |
| TLDR110 | Command example should not be empty. |
| TLDR111 | File name should not contain any Windows-forbidden character. |
| TLDR112 | Terms `stdin`, `stdout`, `stderr`, and `regex` should be lowercase and wrapped in backticks. |
17 changes: 17 additions & 0 deletions browser/browse.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package browser

import "os/exec"

// browse starts the named program with the given arguments
// and waits for it to complete.
var browse = func(name string, args ...string) error {
// #nosec G204
// executable names are hardcoded by this package
// and never originate from user input.
cmd := exec.Command(name, args...)
cmd.Stdin = nil
cmd.Stdout = nil
cmd.Stderr = nil

return cmd.Run()
}
51 changes: 51 additions & 0 deletions browser/browse_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package browser

import (
"runtime"
"testing"

"github.com/stretchr/testify/assert"
)

func TestRun(t *testing.T) {
tests := []struct {
name string
command string
args []string
wantErr bool
}{
{
name: "valid_command",
command: "echo",
args: []string{"hello"},
wantErr: false,
},
{
name: "invalid_command",
command: "nonexistent-command-xyz",
args: nil,
wantErr: true,
},
{
name: "command_with_empty_args",
command: "echo",
args: []string{},
wantErr: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
if runtime.GOOS == "windows" && tt.command == "echo" {
t.Skip("echo is not a standalone executable on Windows")
}
err := browse(tt.command, tt.args...)
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
Loading
Loading