mirror of
https://github.com/charmbracelet/glow.git
synced 2026-08-10 02:09:10 +02:00
Compare commits
151 commits
proposals-
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5cb757278 |
||
|
|
53788271b3 | ||
|
|
f57087499b |
||
|
|
1130f2d89f |
||
|
|
fac0fb456d |
||
|
|
752de97c5a |
||
|
|
7903cdbc5f |
||
|
|
a861530349 |
||
|
|
ba37804fd5 |
||
|
|
dbf59aafa9 |
||
|
|
07d4e1de3f |
||
|
|
e9840f0735 |
||
|
|
283d20b83c |
||
|
|
36bf1711bf |
||
|
|
f9ec6882c1 |
||
|
|
1afeed4c56 |
||
|
|
677fcb3eee | ||
|
|
a440b3319b |
||
|
|
1c32d1377c |
||
|
|
963219b96e |
||
|
|
901b99931e |
||
|
|
9432fd920c |
||
|
|
deaa57fe00 |
||
|
|
e61f329428 |
||
|
|
bf0a9b4d3d |
||
|
|
e010ed866e |
||
|
|
0e76788a6a |
||
|
|
66b389ef17 |
||
|
|
18a739c091 |
||
|
|
9240ab4625 |
||
|
|
a6ed0f2430 |
||
|
|
3079ce20b9 |
||
|
|
bcd1296ce5 |
||
|
|
e54618ef13 |
||
|
|
d37e988787 |
||
|
|
e83c6edf93 |
||
|
|
e23f237fb7 | ||
|
|
ee27edd3b1 | ||
|
|
5b2dfddcda |
||
|
|
0880dea1be |
||
|
|
ef85822a00 |
||
|
|
cdeceb9a7f |
||
|
|
366e9a0d65 |
||
|
|
76e08e7dce |
||
|
|
e60e83c6aa |
||
|
|
bb9fcb439b |
||
|
|
6303d0b784 |
||
|
|
d6b9ddf345 |
||
|
|
470fa3d718 |
||
|
|
2c0e4d878a |
||
|
|
d2e04adbfe |
||
|
|
c84a03bbfd |
||
|
|
e8049d79f2 |
||
|
|
41d70ee83b | ||
|
|
0a6732f089 | ||
|
|
230dbc79b2 | ||
|
|
81c64fdd2d | ||
|
|
c48ee3d98c | ||
|
|
23f9873f6a | ||
|
|
05872a506a |
||
|
|
50a763ef94 |
||
|
|
4d59a973a7 |
||
|
|
d54666ddf7 |
||
|
|
0b668e1593 |
||
|
|
6558aaab69 |
||
|
|
85668a6a21 |
||
|
|
411cdb142f |
||
|
|
6cd480d68d |
||
|
|
652010599b |
||
|
|
eeb0fdd06f |
||
|
|
f88a901caa |
||
|
|
7dc4b9c87f |
||
|
|
2da054e802 |
||
|
|
7154552fa0 |
||
|
|
0401d55125 |
||
|
|
6175a7989e |
||
|
|
5526167854 |
||
|
|
f0f1201ba5 |
||
|
|
490cfcafd0 |
||
|
|
7fbc040cc8 | ||
|
|
9dd8a6f3fd | ||
|
|
1b840ba227 |
||
|
|
436aa682be |
||
|
|
305ea9d447 |
||
|
|
929a9e326f |
||
|
|
8850757e80 |
||
|
|
4db0304573 |
||
|
|
19c6af747f |
||
|
|
b17f04c0ef |
||
|
|
546b6287d8 |
||
|
|
f689c90084 |
||
|
|
51fa19b8b4 | ||
|
|
05e5b2e279 | ||
|
|
e44e4684e1 | ||
|
|
2671509003 | ||
|
|
8e363644f6 | ||
|
|
e03817b2cb |
||
|
|
009c37602a |
||
|
|
3989fe0d5e |
||
|
|
7b3bfac306 |
||
|
|
f255f3a603 |
||
|
|
ae57cced06 |
||
|
|
f1b565125f |
||
|
|
67243bb6fb |
||
|
|
f02c9a8fdc |
||
|
|
b26341ec7b | ||
|
|
736e77dbc6 |
||
|
|
cd99146e9e |
||
|
|
3f3b2c4586 |
||
|
|
c89c8bd6e7 |
||
|
|
4578bc149a |
||
|
|
0dcf1271dd |
||
|
|
94835d99ee |
||
|
|
96d2a38e67 |
||
|
|
2d5bd3b799 |
||
|
|
247c707b25 |
||
|
|
c356273a76 |
||
|
|
3f9102d116 |
||
|
|
a1a85636ea |
||
|
|
b80e8312f2 |
||
|
|
39c336f823 |
||
|
|
e4cacf7319 |
||
|
|
ad01f380bd |
||
|
|
597e56bad4 |
||
|
|
400228cff6 |
||
|
|
1407793c5d |
||
|
|
401610afa4 |
||
|
|
9ebe39cd09 |
||
|
|
ab94744c39 |
||
|
|
2288311e82 |
||
|
|
dbf6e3e799 |
||
|
|
1133e5047b |
||
|
|
f44e5b5f5c |
||
|
|
d89d79a00c |
||
|
|
fe066f2140 |
||
|
|
b1d377237d |
||
|
|
0a7db86725 |
||
|
|
f1cf0ba785 |
||
|
|
d7032fe57f |
||
|
|
821d51da13 |
||
|
|
dbfcc27dad |
||
|
|
380f71086c |
||
|
|
08c564c858 |
||
|
|
e31dc2c890 |
||
|
|
8e51396575 |
||
|
|
f3d0cb89da |
||
|
|
7e471f1787 |
||
|
|
3ee20895b1 |
||
|
|
c47e2acd30 |
||
|
|
fce3edf7db |
||
|
|
d2e7742a6a |
46 changed files with 1566 additions and 3256 deletions
18
.editorconfig
Normal file
18
.editorconfig
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# https://editorconfig.org/
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.go]
|
||||
indent_style = tab
|
||||
indent_size = 8
|
||||
|
||||
[*.golden]
|
||||
insert_final_newline = false
|
||||
trim_trailing_whitespace = false
|
||||
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
|
|
@ -1 +0,0 @@
|
|||
* @meowgorithm @muesli
|
||||
41
.github/dependabot.yml
vendored
41
.github/dependabot.yml
vendored
|
|
@ -1,20 +1,57 @@
|
|||
version: 2
|
||||
|
||||
updates:
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
interval: "weekly"
|
||||
day: "monday"
|
||||
time: "05:00"
|
||||
timezone: "America/New_York"
|
||||
labels:
|
||||
- "dependencies"
|
||||
commit-message:
|
||||
prefix: "chore"
|
||||
include: "scope"
|
||||
groups:
|
||||
all:
|
||||
patterns:
|
||||
- "*"
|
||||
ignore:
|
||||
- dependency-name: github.com/charmbracelet/bubbletea/v2
|
||||
versions:
|
||||
- v2.0.0-beta1
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
interval: "weekly"
|
||||
day: "monday"
|
||||
time: "05:00"
|
||||
timezone: "America/New_York"
|
||||
labels:
|
||||
- "dependencies"
|
||||
commit-message:
|
||||
prefix: "chore"
|
||||
include: "scope"
|
||||
groups:
|
||||
all:
|
||||
patterns:
|
||||
- "*"
|
||||
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "monday"
|
||||
time: "05:00"
|
||||
timezone: "America/New_York"
|
||||
labels:
|
||||
- "dependencies"
|
||||
commit-message:
|
||||
prefix: "chore"
|
||||
include: "scope"
|
||||
groups:
|
||||
all:
|
||||
patterns:
|
||||
- "*"
|
||||
|
|
|
|||
13
.github/workflows/build.yml
vendored
13
.github/workflows/build.yml
vendored
|
|
@ -10,3 +10,16 @@ jobs:
|
|||
uses: charmbracelet/meta/.github/workflows/snapshot.yml@main
|
||||
secrets:
|
||||
goreleaser_key: ${{ secrets.GORELEASER_KEY }}
|
||||
|
||||
govulncheck:
|
||||
uses: charmbracelet/meta/.github/workflows/govulncheck.yml@main
|
||||
with:
|
||||
go-version: stable
|
||||
|
||||
semgrep:
|
||||
uses: charmbracelet/meta/.github/workflows/semgrep.yml@main
|
||||
|
||||
ruleguard:
|
||||
uses: charmbracelet/meta/.github/workflows/ruleguard.yml@main
|
||||
with:
|
||||
go-version: stable
|
||||
|
|
|
|||
18
.github/workflows/coverage.yml
vendored
18
.github/workflows/coverage.yml
vendored
|
|
@ -3,21 +3,17 @@ on: [push, pull_request]
|
|||
|
||||
jobs:
|
||||
coverage:
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [^1]
|
||||
os: [ubuntu-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GO111MODULE: "on"
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: stable
|
||||
|
||||
- name: Coverage
|
||||
env:
|
||||
|
|
|
|||
17
.github/workflows/dependabot-sync.yml
vendored
Normal file
17
.github/workflows/dependabot-sync.yml
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
name: dependabot-sync
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * 0" # every Sunday at midnight
|
||||
workflow_dispatch: # allows manual triggering
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
dependabot-sync:
|
||||
uses: charmbracelet/meta/.github/workflows/dependabot-sync.yml@main
|
||||
with:
|
||||
repo_name: ${{ github.event.repository.name }}
|
||||
secrets:
|
||||
gh_token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
||||
4
.github/workflows/goreleaser.yml
vendored
4
.github/workflows/goreleaser.yml
vendored
|
|
@ -1,3 +1,5 @@
|
|||
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
|
||||
|
||||
name: goreleaser
|
||||
|
||||
on:
|
||||
|
|
@ -21,5 +23,3 @@ jobs:
|
|||
nfpm_gpg_key: ${{ secrets.NFPM_GPG_KEY }}
|
||||
nfpm_passphrase: ${{ secrets.NFPM_PASSPHRASE }}
|
||||
snapcraft_token: ${{ secrets.SNAPCRAFT_TOKEN }}
|
||||
|
||||
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
|
||||
|
|
|
|||
13
.github/workflows/lint-sync.yml
vendored
Normal file
13
.github/workflows/lint-sync.yml
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
name: lint-sync
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * 0" # every sunday at midnight
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
uses: charmbracelet/meta/.github/workflows/lint-sync.yml@main
|
||||
4
.github/workflows/lint.yml
vendored
4
.github/workflows/lint.yml
vendored
|
|
@ -8,9 +8,9 @@ jobs:
|
|||
name: lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v4
|
||||
uses: golangci/golangci-lint-action@v9.2.0
|
||||
with:
|
||||
# Optional: golangci-lint command line arguments.
|
||||
args: --issues-exit-code=0
|
||||
|
|
|
|||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -2,4 +2,4 @@ glow
|
|||
dist/
|
||||
.envrc
|
||||
completions/
|
||||
manpages/
|
||||
manpages/
|
||||
|
|
|
|||
|
|
@ -1,34 +1,44 @@
|
|||
version: "2"
|
||||
run:
|
||||
tests: false
|
||||
|
||||
issues:
|
||||
include:
|
||||
- EXC0001
|
||||
- EXC0005
|
||||
- EXC0011
|
||||
- EXC0012
|
||||
- EXC0013
|
||||
|
||||
max-issues-per-linter: 0
|
||||
max-same-issues: 0
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- bodyclose
|
||||
- dupl
|
||||
- exportloopref
|
||||
- exhaustive
|
||||
- goconst
|
||||
- godot
|
||||
- godox
|
||||
- goimports
|
||||
- gomoddirectives
|
||||
- goprintffuncname
|
||||
- gosec
|
||||
- ifshort
|
||||
- misspell
|
||||
- nakedret
|
||||
- nestif
|
||||
- nilerr
|
||||
- noctx
|
||||
- nolintlint
|
||||
- prealloc
|
||||
- revive
|
||||
- rowserrcheck
|
||||
- sqlclosecheck
|
||||
- tparallel
|
||||
- unconvert
|
||||
- unparam
|
||||
- whitespace
|
||||
- wrapcheck
|
||||
exclusions:
|
||||
rules:
|
||||
- text: '(slog|log)\.\w+'
|
||||
linters:
|
||||
- noctx
|
||||
generated: lax
|
||||
presets:
|
||||
- common-false-positives
|
||||
issues:
|
||||
max-issues-per-linter: 0
|
||||
max-same-issues: 0
|
||||
formatters:
|
||||
enable:
|
||||
- gofumpt
|
||||
- goimports
|
||||
exclusions:
|
||||
generated: lax
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
# yaml-language-server: $schema=https://goreleaser.com/static/schema-pro.json
|
||||
|
||||
version: 2
|
||||
|
||||
includes:
|
||||
- from_url:
|
||||
url: charmbracelet/meta/main/goreleaser-glow.yaml
|
||||
|
||||
variables:
|
||||
main: "."
|
||||
binary_name: glow
|
||||
description: "Render markdown on the CLI, with pizzazz!"
|
||||
github_url: "https://github.com/charmbracelet/soft-serve"
|
||||
github_url: "https://github.com/charmbracelet/glow"
|
||||
maintainer: "Christian Muehlhaeuser <muesli@charm.sh>"
|
||||
brew_commit_author_name: "Christian Muehlhaeuser"
|
||||
brew_commit_author_email: "muesli@charm.sh"
|
||||
|
||||
# yaml-language-server: $schema=https://goreleaser.com/static/schema-pro.json
|
||||
|
|
|
|||
2
LICENSE
2
LICENSE
|
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2019-2023 Charmbracelet, Inc
|
||||
Copyright (c) 2019-2024 Charmbracelet, Inc
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
|
|||
20
Makefile
20
Makefile
|
|
@ -1,20 +0,0 @@
|
|||
# This Makefile is just for development purposes
|
||||
|
||||
.PHONY: default clean glow run log
|
||||
|
||||
LOGFILE := debug.log
|
||||
|
||||
default: glow
|
||||
|
||||
clean:
|
||||
rm -f ./glow
|
||||
|
||||
glow:
|
||||
go build
|
||||
|
||||
run: clean glow
|
||||
GLOW_LOGFILE=$(LOGFILE) ./glow
|
||||
|
||||
log:
|
||||
> $(LOGFILE)
|
||||
tail -f $(LOGFILE)
|
||||
99
README.md
99
README.md
|
|
@ -11,7 +11,7 @@ Render markdown on the CLI, with _pizzazz_!
|
|||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="https://stuff.charm.sh/glow/glow-1.3-trailer-github.gif" width="600" alt="Glow UI Demo">
|
||||
<img src="https://github.com/user-attachments/assets/c2246366-f84b-4847-b431-32a61ca07b74" width="800" alt="Glow UI Demo">
|
||||
</p>
|
||||
|
||||
## What is it?
|
||||
|
|
@ -20,13 +20,9 @@ Glow is a terminal based markdown reader designed from the ground up to bring
|
|||
out the beauty—and power—of the CLI.
|
||||
|
||||
Use it to discover markdown files, read documentation directly on the command
|
||||
line and stash markdown files to your own private collection, so you can read
|
||||
them anywhere. Glow will find local markdown files in subdirectories or a local
|
||||
line. Glow will find local markdown files in subdirectories or a local
|
||||
Git repository.
|
||||
|
||||
By the way, all data stashed is encrypted end-to-end: only you can decrypt it.
|
||||
More on that below.
|
||||
|
||||
## Installation
|
||||
|
||||
### Package Manager
|
||||
|
|
@ -34,39 +30,64 @@ More on that below.
|
|||
```bash
|
||||
# macOS or Linux
|
||||
brew install glow
|
||||
```
|
||||
|
||||
```bash
|
||||
# macOS (with MacPorts)
|
||||
sudo port install glow
|
||||
```
|
||||
|
||||
```bash
|
||||
# Arch Linux (btw)
|
||||
pacman -S glow
|
||||
```
|
||||
|
||||
```bash
|
||||
# Void Linux
|
||||
xbps-install -S glow
|
||||
```
|
||||
|
||||
# Nix
|
||||
nix-env -iA nixpkgs.glow
|
||||
```bash
|
||||
# Nix shell
|
||||
nix-shell -p glow --command glow
|
||||
```
|
||||
|
||||
```bash
|
||||
# FreeBSD
|
||||
pkg install glow
|
||||
```
|
||||
|
||||
```bash
|
||||
# Solus
|
||||
eopkg install glow
|
||||
```
|
||||
|
||||
```bash
|
||||
# Windows (with Chocolatey, Scoop, or Winget)
|
||||
choco install glow
|
||||
scoop install glow
|
||||
winget install charmbracelet.glow
|
||||
```
|
||||
|
||||
```bash
|
||||
# Android (with termux)
|
||||
pkg install glow
|
||||
```
|
||||
|
||||
```bash
|
||||
# Ubuntu (Snapcraft)
|
||||
sudo snap install glow
|
||||
```
|
||||
|
||||
```bash
|
||||
# Debian/Ubuntu
|
||||
sudo mkdir -p /etc/apt/keyrings
|
||||
curl -fsSL https://repo.charm.sh/apt/gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/charm.gpg
|
||||
echo "deb [signed-by=/etc/apt/keyrings/charm.gpg] https://repo.charm.sh/apt/ * *" | sudo tee /etc/apt/sources.list.d/charm.list
|
||||
sudo apt update && sudo apt install glow
|
||||
```
|
||||
|
||||
```bash
|
||||
# Fedora/RHEL
|
||||
echo '[charm]
|
||||
name=Charm
|
||||
|
|
@ -84,11 +105,12 @@ packages. ARM builds are also available for macOS, Linux, FreeBSD and OpenBSD.
|
|||
### Go
|
||||
|
||||
Or just install it with `go`:
|
||||
|
||||
```bash
|
||||
go install github.com/charmbracelet/glow@latest
|
||||
go install github.com/charmbracelet/glow/v2@latest
|
||||
```
|
||||
|
||||
### Build (requires Go 1.13+)
|
||||
### Build (requires Go 1.21+)
|
||||
|
||||
```bash
|
||||
git clone https://github.com/charmbracelet/glow.git
|
||||
|
|
@ -98,11 +120,10 @@ go build
|
|||
|
||||
[releases]: https://github.com/charmbracelet/glow/releases
|
||||
|
||||
|
||||
## The TUI
|
||||
|
||||
Simply run `glow` without arguments to start the textual user interface and
|
||||
browse local and stashed markdown. Glow will find local markdown files in the
|
||||
browse local. Glow will find local markdown files in the
|
||||
current directory and below or, if you’re in a Git repository, Glow will search
|
||||
the repo.
|
||||
|
||||
|
|
@ -110,15 +131,6 @@ Markdown files can be read with Glow's high-performance pager. Most of the
|
|||
keystrokes you know from `less` are the same, but you can press `?` to list
|
||||
the hotkeys.
|
||||
|
||||
### Stashing
|
||||
|
||||
Glow works with the Charm Cloud to allow you to store any markdown files in
|
||||
your own private collection. You can stash a local document from the Glow TUI by
|
||||
pressing `s`.
|
||||
|
||||
Stashing is private, its contents will not be exposed publicly, and it's
|
||||
encrypted end-to-end. More on encryption below.
|
||||
|
||||
## The CLI
|
||||
|
||||
In addition to a TUI, Glow has a CLI for working with Markdown. To format a
|
||||
|
|
@ -138,18 +150,6 @@ glow github.com/charmbracelet/glow
|
|||
glow https://host.tld/file.md
|
||||
```
|
||||
|
||||
### Stashing
|
||||
|
||||
You can also stash documents from the CLI:
|
||||
|
||||
```bash
|
||||
glow stash README.md
|
||||
```
|
||||
|
||||
Then, when you run `glow` without arguments will you can browse through your
|
||||
stashed documents. This is a great way to keep track of things that you need to
|
||||
reference often.
|
||||
|
||||
### Word Wrapping
|
||||
|
||||
The `-w` flag lets you set a maximum width at which the output will be wrapped:
|
||||
|
|
@ -201,42 +201,39 @@ Here's an example config:
|
|||
```yaml
|
||||
# style name or JSON path (default "auto")
|
||||
style: "light"
|
||||
# show local files only; no network (TUI-mode only)
|
||||
local: true
|
||||
# mouse support (TUI-mode only)
|
||||
# mouse wheel support (TUI-mode only)
|
||||
mouse: true
|
||||
# use pager to display markdown
|
||||
pager: true
|
||||
# word-wrap at width
|
||||
# at which column should we word wrap?
|
||||
width: 80
|
||||
# show all files, including hidden and ignored.
|
||||
all: false
|
||||
# show line numbers (TUI-mode only)
|
||||
showLineNumbers: false
|
||||
# preserve newlines in the output
|
||||
preserveNewLines: false
|
||||
```
|
||||
|
||||
## 🔒 Encryption: How It Works
|
||||
## Contributing
|
||||
|
||||
Encryption works by issuing symmetric keys (basically a generated password) and
|
||||
encrypting it with the local SSH public key generated by the open-source
|
||||
[charm][charmlib] library. That encrypted key is then sent up to our server.
|
||||
We can’t read it since we don’t have your private key. When you want to decrypt
|
||||
something or view your stash, that key is downloaded from our server and
|
||||
decrypted locally using the SSH private key. When you link accounts, the
|
||||
symmetric key is encrypted for each new public key. This happens on your
|
||||
machine and not our server, so we never see any unencrypted data.
|
||||
See [contributing][contribute].
|
||||
|
||||
[charmlib]: https://github.com/charmbracelet/charm
|
||||
[contribute]: https://github.com/charmbracelet/glow/contribute
|
||||
|
||||
## Feedback
|
||||
|
||||
We’d love to hear your thoughts on this project. Feel free to drop us a note!
|
||||
|
||||
* [Twitter](https://twitter.com/charmcli)
|
||||
* [The Fediverse](https://mastodon.social/@charmcli)
|
||||
* [Discord](https://charm.sh/chat)
|
||||
- [Twitter](https://twitter.com/charmcli)
|
||||
- [The Fediverse](https://mastodon.social/@charmcli)
|
||||
- [Discord](https://charm.sh/chat)
|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://github.com/charmbracelet/glow/raw/master/LICENSE)
|
||||
|
||||
***
|
||||
---
|
||||
|
||||
Part of [Charm](https://charm.sh).
|
||||
|
||||
|
|
|
|||
23
Taskfile.yaml
Normal file
23
Taskfile.yaml
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# https://taskfile.dev
|
||||
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
lint:
|
||||
desc: Run base linters
|
||||
cmds:
|
||||
- golangci-lint run
|
||||
|
||||
test:
|
||||
desc: Run tests
|
||||
cmds:
|
||||
- go test ./... {{.CLI_ARGS}}
|
||||
|
||||
log:
|
||||
desc: Watch for glow logs
|
||||
aliases: [tail]
|
||||
cmds:
|
||||
- cmd: tail -f ~/Library/Caches/glow/glow.log
|
||||
platforms: [darwin]
|
||||
- cmd: tail -f ~/.cache/glow/glow.log
|
||||
platforms: [linux, windows]
|
||||
|
|
@ -1,82 +1,88 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/charmbracelet/x/editor"
|
||||
gap "github.com/muesli/go-app-paths"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const defaultConfig = `# style name or JSON path (default "auto")
|
||||
style: "auto"
|
||||
# show local files only; no network (TUI-mode only)
|
||||
local: false
|
||||
# mouse support (TUI-mode only)
|
||||
mouse: false
|
||||
# use pager to display markdown
|
||||
pager: false
|
||||
# word-wrap at width
|
||||
width: 80`
|
||||
width: 80
|
||||
# show all files, including hidden and ignored.
|
||||
all: false
|
||||
`
|
||||
|
||||
var configCmd = &cobra.Command{
|
||||
Use: "config",
|
||||
Hidden: false,
|
||||
Short: "Edit the glow config file",
|
||||
Long: paragraph(fmt.Sprintf("\n%s the glow config file. We’ll use EDITOR to determine which editor to use. If the config file doesn't exist, it will be created.", keyword("Edit"))),
|
||||
Example: paragraph("glow config\nglow config --config path/to/config.yml"),
|
||||
Args: cobra.NoArgs,
|
||||
SilenceUsage: true,
|
||||
RunE: func(_ *cobra.Command, _ []string) error {
|
||||
if configFile == "" {
|
||||
scope := gap.NewScope(gap.User, "glow")
|
||||
|
||||
var err error
|
||||
configFile, err = scope.ConfigPath("glow.yml")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if ext := path.Ext(configFile); ext != ".yaml" && ext != ".yml" {
|
||||
return fmt.Errorf("'%s' is not a supported config type: use '%s' or '%s'", ext, ".yaml", ".yml")
|
||||
}
|
||||
|
||||
if _, err := os.Stat(configFile); os.IsNotExist(err) {
|
||||
// File doesn't exist yet, create all necessary directories and
|
||||
// write the default config file
|
||||
if err := os.MkdirAll(filepath.Dir(configFile), 0o700); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f, err := os.Create(configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = f.Close() }()
|
||||
|
||||
if _, err := f.WriteString(defaultConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err != nil { // some other error occurred
|
||||
Use: "config",
|
||||
Hidden: false,
|
||||
Short: "Edit the glow config file",
|
||||
Long: paragraph(fmt.Sprintf("\n%s the glow config file. We’ll use EDITOR to determine which editor to use. If the config file doesn't exist, it will be created.", keyword("Edit"))),
|
||||
Example: paragraph("glow config\nglow config --config path/to/config.yml"),
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(*cobra.Command, []string) error {
|
||||
if err := ensureConfigFile(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c, err := editor.Cmd("Glow", configFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not edit %s: %w", configFile, err)
|
||||
return fmt.Errorf("unable to set config file: %w", err)
|
||||
}
|
||||
c.Stdin = os.Stdin
|
||||
c.Stdout = os.Stdout
|
||||
c.Stderr = os.Stderr
|
||||
if err := c.Run(); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("unable to run command: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("Wrote config file to:", configFile)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func ensureConfigFile() error {
|
||||
if configFile == "" {
|
||||
configFile = viper.GetViper().ConfigFileUsed()
|
||||
if err := os.MkdirAll(filepath.Dir(configFile), 0o755); err != nil { //nolint:gosec
|
||||
return fmt.Errorf("could not write configuration file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if ext := path.Ext(configFile); ext != ".yaml" && ext != ".yml" {
|
||||
return fmt.Errorf("'%s' is not a supported configuration type: use '%s' or '%s'", ext, ".yaml", ".yml")
|
||||
}
|
||||
|
||||
if _, err := os.Stat(configFile); errors.Is(err, fs.ErrNotExist) {
|
||||
// File doesn't exist yet, create all necessary directories and
|
||||
// write the default config file
|
||||
if err := os.MkdirAll(filepath.Dir(configFile), 0o700); err != nil {
|
||||
return fmt.Errorf("unable create directory: %w", err)
|
||||
}
|
||||
|
||||
f, err := os.Create(configFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create config file: %w", err)
|
||||
}
|
||||
defer func() { _ = f.Close() }()
|
||||
|
||||
if _, err := f.WriteString(defaultConfig); err != nil {
|
||||
return fmt.Errorf("unable to write config file: %w", err)
|
||||
}
|
||||
} else if err != nil { // some other error occurred
|
||||
return fmt.Errorf("unable to stat config file: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
50
github.go
50
github.go
|
|
@ -1,47 +1,55 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// isGitHubURL tests a string to determine if it is a well-structured GitHub URL.
|
||||
func isGitHubURL(s string) (string, bool) {
|
||||
if strings.HasPrefix(s, "github.com/") {
|
||||
s = "https://" + s
|
||||
// findGitHubREADME tries to find the correct README filename in a repository using GitHub API.
|
||||
func findGitHubREADME(u *url.URL) (*source, error) {
|
||||
owner, repo, ok := strings.Cut(strings.TrimPrefix(u.Path, "/"), "/")
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid url: %s", u.String())
|
||||
}
|
||||
|
||||
u, err := url.ParseRequestURI(s)
|
||||
type readme struct {
|
||||
DownloadURL string `json:"download_url"`
|
||||
}
|
||||
|
||||
apiURL := fmt.Sprintf("https://api.%s/repos/%s/%s/readme", u.Hostname(), owner, repo)
|
||||
|
||||
//nolint:bodyclose
|
||||
// it is closed on the caller
|
||||
res, err := http.Get(apiURL) //nolint: gosec,noctx
|
||||
if err != nil {
|
||||
return "", false
|
||||
return nil, fmt.Errorf("unable to get url: %w", err)
|
||||
}
|
||||
|
||||
return u.String(), strings.ToLower(u.Host) == "github.com"
|
||||
}
|
||||
|
||||
// findGitHubREADME tries to find the correct README filename in a repository.
|
||||
func findGitHubREADME(s string) (*source, error) {
|
||||
u, err := url.ParseRequestURI(s)
|
||||
body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("unable to read http response body: %w", err)
|
||||
}
|
||||
u.Host = "raw.githubusercontent.com"
|
||||
|
||||
for _, r := range readmeNames {
|
||||
v := u
|
||||
v.Path += "/master/" + r
|
||||
var result readme
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
return nil, fmt.Errorf("unable to parse json: %w", err)
|
||||
}
|
||||
|
||||
// nolint:bodyclose
|
||||
if res.StatusCode == http.StatusOK {
|
||||
//nolint:bodyclose
|
||||
// it is closed on the caller
|
||||
resp, err := http.Get(v.String())
|
||||
resp, err := http.Get(result.DownloadURL) //nolint: noctx
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("unable to get url: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
return &source{resp.Body, v.String()}, nil
|
||||
return &source{resp.Body, result.DownloadURL}, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
53
gitlab.go
53
gitlab.go
|
|
@ -1,46 +1,59 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// isGitLabURL tests a string to determine if it is a well-structured GitLab URL.
|
||||
func isGitLabURL(s string) (string, bool) {
|
||||
if strings.HasPrefix(s, "gitlab.com/") {
|
||||
s = "https://" + s
|
||||
// findGitLabREADME tries to find the correct README filename in a repository using GitLab API.
|
||||
func findGitLabREADME(u *url.URL) (*source, error) {
|
||||
owner, repo, ok := strings.Cut(strings.TrimPrefix(u.Path, "/"), "/")
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid url: %s", u.String())
|
||||
}
|
||||
|
||||
u, err := url.ParseRequestURI(s)
|
||||
projectPath := url.QueryEscape(owner + "/" + repo)
|
||||
|
||||
type readme struct {
|
||||
ReadmeURL string `json:"readme_url"`
|
||||
}
|
||||
|
||||
apiURL := fmt.Sprintf("https://%s/api/v4/projects/%s", u.Hostname(), projectPath)
|
||||
|
||||
//nolint:bodyclose
|
||||
// it is closed on the caller
|
||||
res, err := http.Get(apiURL) //nolint: gosec,noctx
|
||||
if err != nil {
|
||||
return "", false
|
||||
return nil, fmt.Errorf("unable to get url: %w", err)
|
||||
}
|
||||
|
||||
return u.String(), strings.ToLower(u.Host) == "gitlab.com"
|
||||
}
|
||||
|
||||
// findGitLabREADME tries to find the correct README filename in a repository.
|
||||
func findGitLabREADME(s string) (*source, error) {
|
||||
u, err := url.ParseRequestURI(s)
|
||||
body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("unable to read http response body: %w", err)
|
||||
}
|
||||
|
||||
for _, r := range readmeNames {
|
||||
v := u
|
||||
v.Path += "/raw/master/" + r
|
||||
var result readme
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
return nil, fmt.Errorf("unable to parse json: %w", err)
|
||||
}
|
||||
|
||||
// nolint:bodyclose
|
||||
readmeRawURL := strings.ReplaceAll(result.ReadmeURL, "blob", "raw")
|
||||
|
||||
if res.StatusCode == http.StatusOK {
|
||||
//nolint:bodyclose
|
||||
// it is closed on the caller
|
||||
resp, err := http.Get(v.String())
|
||||
resp, err := http.Get(readmeRawURL) //nolint: gosec,noctx
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("unable to get url: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
return &source{resp.Body, v.String()}, nil
|
||||
return &source{resp.Body, readmeRawURL}, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
22
glow_test.go
22
glow_test.go
|
|
@ -1,31 +1,9 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGlowSources(t *testing.T) {
|
||||
tt := []string{
|
||||
".",
|
||||
"README.md",
|
||||
"github.com/charmbracelet/glow",
|
||||
"https://github.com/charmbracelet/glow",
|
||||
}
|
||||
|
||||
for _, v := range tt {
|
||||
buf := &bytes.Buffer{}
|
||||
err := executeArg(rootCmd, v, buf)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Error during execution (args: %s): %v", v, err)
|
||||
}
|
||||
if buf.Len() == 0 {
|
||||
t.Errorf("Output buffer should not be empty (args: %s)", v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGlowFlags(t *testing.T) {
|
||||
tt := []struct {
|
||||
args []string
|
||||
|
|
|
|||
105
go.mod
105
go.mod
|
|
@ -1,76 +1,75 @@
|
|||
module github.com/charmbracelet/glow
|
||||
module github.com/charmbracelet/glow/v2
|
||||
|
||||
go 1.18
|
||||
go 1.25.12
|
||||
|
||||
toolchain go1.26.5
|
||||
|
||||
require (
|
||||
github.com/atotto/clipboard v0.1.4
|
||||
github.com/charmbracelet/bubbles v0.18.1-0.20240514172444-4491293fd12c
|
||||
github.com/charmbracelet/bubbletea v0.26.3-0.20240514172421-c69b27d7eeb8
|
||||
github.com/charmbracelet/charm v0.8.7
|
||||
github.com/charmbracelet/glamour v0.6.0
|
||||
github.com/charmbracelet/lipgloss v0.10.1-0.20240514172355-88ecbb398449
|
||||
github.com/charmbracelet/x/editor v0.0.0-20231116172829-450eedbca1ab
|
||||
github.com/caarlos0/env/v11 v11.3.1
|
||||
github.com/charmbracelet/bubbles v0.21.0
|
||||
github.com/charmbracelet/bubbletea v1.3.10
|
||||
github.com/charmbracelet/glamour v0.10.0
|
||||
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834
|
||||
github.com/charmbracelet/log v0.4.2
|
||||
github.com/charmbracelet/x/editor v0.1.0
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/mattn/go-runewidth v0.0.15
|
||||
github.com/meowgorithm/babyenv v1.3.1
|
||||
github.com/fsnotify/fsnotify v1.9.0
|
||||
github.com/mattn/go-runewidth v0.0.19
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/muesli/gitcha v0.2.0
|
||||
github.com/muesli/gitcha v0.3.0
|
||||
github.com/muesli/go-app-paths v0.2.2
|
||||
github.com/muesli/mango-cobra v1.3.0
|
||||
github.com/muesli/reflow v0.3.0
|
||||
github.com/muesli/roff v0.1.0
|
||||
github.com/muesli/termenv v0.16.0
|
||||
github.com/sahilm/fuzzy v0.1.1
|
||||
github.com/segmentio/ksuid v1.0.4
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/spf13/viper v1.14.0
|
||||
golang.org/x/sys v0.20.0
|
||||
golang.org/x/term v0.15.0
|
||||
golang.org/x/text v0.14.0
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/spf13/viper v1.21.0
|
||||
golang.org/x/sys v0.43.0
|
||||
golang.org/x/term v0.42.0
|
||||
golang.org/x/text v0.39.0
|
||||
mvdan.cc/sh/v3 v3.13.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/alecthomas/chroma v0.10.0 // indirect
|
||||
github.com/alecthomas/chroma/v2 v2.14.0 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/calmh/randomart v1.1.0 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.1.1 // indirect
|
||||
github.com/charmbracelet/x/input v0.1.0 // indirect
|
||||
github.com/charmbracelet/x/term v0.1.1 // indirect
|
||||
github.com/charmbracelet/x/windows v0.1.0 // indirect
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
|
||||
github.com/dlclark/regexp2 v1.4.0 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
|
||||
github.com/charmbracelet/x/ansi v0.10.1 // indirect
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
|
||||
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf // indirect
|
||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.2.0 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.0 // indirect
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/gorilla/css v1.0.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/gorilla/css v1.0.1 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/magiconair/properties v1.8.6 // indirect
|
||||
github.com/mattn/go-isatty v0.0.18 // indirect
|
||||
github.com/microcosm-cc/bluemonday v1.0.21 // indirect
|
||||
github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/muesli/sasquatch v0.0.0-20200811221207-66979d92330a // indirect
|
||||
github.com/muesli/termenv v0.15.2 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
|
||||
github.com/muesli/mango v0.2.0 // indirect
|
||||
github.com/muesli/mango-pflag v0.1.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94 // indirect
|
||||
github.com/spf13/afero v1.9.2 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/subosito/gotenv v1.4.1 // indirect
|
||||
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
github.com/yuin/goldmark v1.5.2 // indirect
|
||||
github.com/yuin/goldmark-emoji v1.0.1 // indirect
|
||||
golang.org/x/crypto v0.17.0 // indirect
|
||||
golang.org/x/net v0.17.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
github.com/yuin/goldmark v1.7.17 // indirect
|
||||
github.com/yuin/goldmark-emoji v1.0.5 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 // indirect
|
||||
golang.org/x/net v0.53.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
)
|
||||
|
|
|
|||
787
go.sum
787
go.sum
|
|
@ -1,718 +1,177 @@
|
|||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
||||
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
||||
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
|
||||
github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/atotto/clipboard v0.1.2/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||
github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE=
|
||||
github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||
github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E=
|
||||
github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I=
|
||||
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
||||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||
github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
|
||||
github.com/aymanbagabas/go-osc52 v1.2.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
|
||||
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/calmh/randomart v1.1.0 h1:evl+iwc10LXtHdMZhzLxmsCQVmWnkXs44SbC6Uk0Il8=
|
||||
github.com/calmh/randomart v1.1.0/go.mod h1:DQUbPVyP+7PAs21w/AnfMKG5NioxS3TbZ2F9MSK/jFM=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/charmbracelet/bubbles v0.7.5/go.mod h1:IRTORFvhEI6OUH7WhN2Ks8Z8miNGimk1BE6cmHijOkM=
|
||||
github.com/charmbracelet/bubbles v0.18.1-0.20240514172235-c87a626aa317 h1:wfqvjswMgiO4vE2PzLt/bz+MK3an7HLqo93OYKiSx3Q=
|
||||
github.com/charmbracelet/bubbles v0.18.1-0.20240514172235-c87a626aa317/go.mod h1:RkbLvNDCdo63q1Ub4hTPx5gOC3f2dVs4i2l4/vx9g14=
|
||||
github.com/charmbracelet/bubbles v0.18.1-0.20240514172444-4491293fd12c h1:IWA/iYgK3qDnIkhlC2Sd7ApzU0JeNm67rixi3xl6wA4=
|
||||
github.com/charmbracelet/bubbles v0.18.1-0.20240514172444-4491293fd12c/go.mod h1:K+buXQ4vMkpr6ay/ilfTAdNVnQfx8BJCPg22SmOfLkg=
|
||||
github.com/charmbracelet/bubbletea v0.12.2/go.mod h1:3gZkYELUOiEUOp0bTInkxguucy/xRbGSOcbMs1geLxg=
|
||||
github.com/charmbracelet/bubbletea v0.23.2/go.mod h1:FaP3WUivcTM0xOKNmhciz60M6I+weYLF76mr1JyI7sM=
|
||||
github.com/charmbracelet/bubbletea v0.26.3-0.20240514172205-72ce959b78e3 h1:GugYAtdwJBA1e8+y/diVe41cPS8dgdML7+atTeLS4R8=
|
||||
github.com/charmbracelet/bubbletea v0.26.3-0.20240514172205-72ce959b78e3/go.mod h1:FixrOn+mReyxbVP0qZcyDsHLKeaZbMHf3wZOxLm8wj4=
|
||||
github.com/charmbracelet/bubbletea v0.26.3-0.20240514172421-c69b27d7eeb8 h1:uq7c8wSq2/wK/KyvQ3KMQGZp0LFrKhOTAnHK6rNSMG0=
|
||||
github.com/charmbracelet/bubbletea v0.26.3-0.20240514172421-c69b27d7eeb8/go.mod h1:69vZAJlmlVOxh7Y/Ndvr0csqEP2coOgZolwXx6qvPCk=
|
||||
github.com/charmbracelet/charm v0.8.7 h1:FJ9b7IxWUWHOPR72zS/QJLEqtudOB2Mwfc+Sir0eZR8=
|
||||
github.com/charmbracelet/charm v0.8.7/go.mod h1:ApJYwJljEjODkOYJgFDzbUqztLrCWQct9zyPD+xcVr4=
|
||||
github.com/charmbracelet/glamour v0.6.0 h1:wi8fse3Y7nfcabbbDuwolqTqMQPMnVPeZhDM273bISc=
|
||||
github.com/charmbracelet/glamour v0.6.0/go.mod h1:taqWV4swIMMbWALc0m7AfE9JkPSU8om2538k9ITBxOc=
|
||||
github.com/charmbracelet/lipgloss v0.10.1-0.20240514172133-9a1d854f90dd h1:y23y/2Sa7Wa3nseIm0KTP1R0BA44QyFhRB/rvJKOyjI=
|
||||
github.com/charmbracelet/lipgloss v0.10.1-0.20240514172133-9a1d854f90dd/go.mod h1:/WJOlugg8bddqSP52FFiGXWVIfXsO+/+KzOGAT7opnw=
|
||||
github.com/charmbracelet/lipgloss v0.10.1-0.20240514172355-88ecbb398449 h1:jy8zvXRALm2v8aK4ivo4m1+/E1/p8nejp5hWRudx2VA=
|
||||
github.com/charmbracelet/lipgloss v0.10.1-0.20240514172355-88ecbb398449/go.mod h1:/WJOlugg8bddqSP52FFiGXWVIfXsO+/+KzOGAT7opnw=
|
||||
github.com/charmbracelet/x/ansi v0.1.1 h1:CGAduulr6egay/YVbGc8Hsu8deMg1xZ/bkaXTPi1JDk=
|
||||
github.com/charmbracelet/x/ansi v0.1.1/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
|
||||
github.com/charmbracelet/x/editor v0.0.0-20231116172829-450eedbca1ab h1:95WbogoQheYFuAUy1olU8OgxrHk2K86zA7mSNELiMfU=
|
||||
github.com/charmbracelet/x/editor v0.0.0-20231116172829-450eedbca1ab/go.mod h1:lrin7iXW742pX5pePBEWhLPDTp53YW15r/Lp4Rcfg0M=
|
||||
github.com/charmbracelet/x/input v0.1.0 h1:TEsGSfZYQyOtp+STIjyBq6tpRaorH0qpwZUj8DavAhQ=
|
||||
github.com/charmbracelet/x/input v0.1.0/go.mod h1:ZZwaBxPF7IG8gWWzPUVqHEtWhc1+HXJPNuerJGRGZ28=
|
||||
github.com/charmbracelet/x/term v0.1.1 h1:3cosVAiPOig+EV4X9U+3LDgtwwAoEzJjNdwbXDjF6yI=
|
||||
github.com/charmbracelet/x/term v0.1.1/go.mod h1:wB1fHt5ECsu3mXYusyzcngVWWlu1KKUmmLhfgr/Flxw=
|
||||
github.com/charmbracelet/x/windows v0.1.0 h1:gTaxdvzDM5oMa/I2ZNF7wN78X/atWemG9Wph7Ika2k4=
|
||||
github.com/charmbracelet/x/windows v0.1.0/go.mod h1:GLEO/l+lizvFDBPLIOk+49gdX49L9YWMB5t+DZd0jkQ=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw=
|
||||
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA=
|
||||
github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U=
|
||||
github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs=
|
||||
github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg=
|
||||
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
|
||||
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
|
||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
|
||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
|
||||
github.com/charmbracelet/glamour v0.10.0 h1:MtZvfwsYCx8jEPFJm3rIBFIMZUfUJ765oX8V6kXldcY=
|
||||
github.com/charmbracelet/glamour v0.10.0/go.mod h1:f+uf+I/ChNmqo087elLnVdCiVgjSKWuXa/l6NU2ndYk=
|
||||
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 h1:ZR7e0ro+SZZiIZD7msJyA+NjkCNNavuiPBLgerbOziE=
|
||||
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA=
|
||||
github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig=
|
||||
github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw=
|
||||
github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ=
|
||||
github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
||||
github.com/charmbracelet/x/editor v0.1.0 h1:p69/dpvlwRTs9uYiPeAWruwsHqTFzHhTvQOd/WVSX98=
|
||||
github.com/charmbracelet/x/editor v0.1.0/go.mod h1:oivrEbcP/AYt/Hpvk5pwDXXrQ933gQS6UzL6fxqAGSA=
|
||||
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ=
|
||||
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
|
||||
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf h1:rLG0Yb6MQSDKdB52aGX55JT1oi0P0Kuaj7wi1bLUpnI=
|
||||
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf/go.mod h1:B3UgsnsBZS/eX42BlaNiJkD1pPOUa+oF1IYC6Yd2CEU=
|
||||
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
||||
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
||||
github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY=
|
||||
github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
|
||||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
||||
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
||||
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
||||
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
|
||||
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
|
||||
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
||||
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
|
||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
|
||||
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
|
||||
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/meowgorithm/babyenv v1.3.0/go.mod h1:lwNX+J6AGBFqNrMZ2PTLkM6SO+W4X8DOg9zBDO4j3Ig=
|
||||
github.com/meowgorithm/babyenv v1.3.1 h1:18ZEYIgbzoFQfRLF9+lxjRfk/ui6w8U0FWl07CgWvvc=
|
||||
github.com/meowgorithm/babyenv v1.3.1/go.mod h1:lwNX+J6AGBFqNrMZ2PTLkM6SO+W4X8DOg9zBDO4j3Ig=
|
||||
github.com/microcosm-cc/bluemonday v1.0.21 h1:dNH3e4PSyE4vNX+KlRGHT5KrSvjeUkoNPwEORjffHJg=
|
||||
github.com/microcosm-cc/bluemonday v1.0.21/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM=
|
||||
github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a h1:eU8j/ClY2Ty3qdHnn0TyW3ivFoPC/0F1gQZz8yTxbbE=
|
||||
github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a/go.mod h1:v8eSC2SMp9/7FTKUncp7fH9IwPfw+ysMObcEz5FWheQ=
|
||||
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
|
||||
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/muesli/gitcha v0.2.0 h1:+wOgT2dI9s2Tznj1t1rb/qkK5e0cb6qD8c4IX2TR/YY=
|
||||
github.com/muesli/gitcha v0.2.0/go.mod h1:Ri8m9TZS4+ORG4JVmVKUQcWZuxDvUW3UKxMdQfzG2zI=
|
||||
github.com/muesli/go-app-paths v0.2.1/go.mod h1:SxS3Umca63pcFcLtbjVb+J0oD7cl4ixQWoBKhGEtEho=
|
||||
github.com/muesli/gitcha v0.3.0 h1:+PJkVKrDXVB0VgRn/yVx2CqSVSDGMSepzvohsCrPYtQ=
|
||||
github.com/muesli/gitcha v0.3.0/go.mod h1:vX3jFL+XcEUq1uY74RCjLSZfAV+ZuvLg70/NGPdXn84=
|
||||
github.com/muesli/go-app-paths v0.2.2 h1:NqG4EEZwNIhBq/pREgfBmgDmt3h1Smr1MjZiXbpZUnI=
|
||||
github.com/muesli/go-app-paths v0.2.2/go.mod h1:SxS3Umca63pcFcLtbjVb+J0oD7cl4ixQWoBKhGEtEho=
|
||||
github.com/muesli/mango v0.2.0 h1:iNNc0c5VLQ6fsMgAqGQofByNUBH2Q2nEbD6TaI+5yyQ=
|
||||
github.com/muesli/mango v0.2.0/go.mod h1:5XFpbC8jY5UUv89YQciiXNlbi+iJgt29VDC5xbzrLL4=
|
||||
github.com/muesli/mango-cobra v1.3.0 h1:vQy5GvPg3ndOSpduxutqFoINhWk3vD5K2dXo5E8pqec=
|
||||
github.com/muesli/mango-cobra v1.3.0/go.mod h1:Cj1ZrBu3806Qw7UjxnAUgE+7tllUBj1NCLQDwwGx19E=
|
||||
github.com/muesli/mango-pflag v0.1.0 h1:UADqbYgpUyRoBja3g6LUL+3LErjpsOwaC9ywvBWe7Sg=
|
||||
github.com/muesli/mango-pflag v0.1.0/go.mod h1:YEQomTxaCUp8PrbhFh10UfbhbQrM/xJ4i2PB8VTLLW0=
|
||||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||
github.com/muesli/sasquatch v0.0.0-20200811221207-66979d92330a h1:Hw/15RYEOUD6T9UCRkUmNBa33kJkH33Fui6hE4sRLKU=
|
||||
github.com/muesli/sasquatch v0.0.0-20200811221207-66979d92330a/go.mod h1:+XG0ne5zXWBTSbbe7Z3/RWxaT8PZY6zaZ1dX6KjprYY=
|
||||
github.com/muesli/termenv v0.7.2/go.mod h1:ct2L5N2lmix82RaY3bMWwVu/jUFc9Ule0KGDCiKYPh8=
|
||||
github.com/muesli/termenv v0.7.4/go.mod h1:pZ7qY9l3F7e5xsAOS0zCew2tME+p7bWeBkotCEcIIcc=
|
||||
github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc=
|
||||
github.com/muesli/termenv v0.14.0/go.mod h1:kG/pF1E7fh949Xhe156crRUrHNyK221IuGO7Ez60Uc8=
|
||||
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
||||
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
|
||||
github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
||||
github.com/muesli/roff v0.1.0 h1:YD0lalCotmYuF5HhZliKWlIx7IEhiXeSfq7hNjFqGF8=
|
||||
github.com/muesli/roff v0.1.0/go.mod h1:pjAHQM9hdUUwm/krAfrLGgJkXJ+YuhtsfZ42kieB2Ig=
|
||||
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
||||
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94 h1:G04eS0JkAIVZfaJLjla9dNxkJCPiKIGZlw9AfOhzOD0=
|
||||
github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94/go.mod h1:b18R55ulyQ/h3RaWyloPyER7fWQVZvimKKhnI5OfrJQ=
|
||||
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
||||
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
||||
github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA=
|
||||
github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
|
||||
github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c=
|
||||
github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw=
|
||||
github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
|
||||
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
|
||||
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
|
||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
||||
github.com/spf13/viper v1.14.0 h1:Rg7d3Lo706X9tHsJMUjdiwMpHB7W8WnSVOssIY+JElU=
|
||||
github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
||||
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
||||
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=
|
||||
github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.5.2 h1:ALmeCk/px5FSm1MAcFBAsVKZjDuMVj8Tm7FFIlMJnqU=
|
||||
github.com/yuin/goldmark v1.5.2/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18Wa1os=
|
||||
github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201020230747-6e5568b54d1a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
||||
github.com/yuin/goldmark v1.7.17 h1:p36OVWwRb246iHxA/U4p8OPEpOTESm4n+g+8t0EE5uA=
|
||||
github.com/yuin/goldmark v1.7.17/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
github.com/yuin/goldmark-emoji v1.0.5 h1:EMVWyCGPlXJfUXBXpuMu+ii3TIaxbVBnEX9uaDC4cIk=
|
||||
github.com/yuin/goldmark-emoji v1.0.5/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 h1:LoYXNGAShUG3m/ehNk4iFctuhGX/+R1ZpfJ4/ia80JM=
|
||||
golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
|
||||
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
|
||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
||||
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
|
||||
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
|
||||
golang.org/x/text v0.39.0 h1:UbZz4pLOvn600D6Oh6GGEI6VAmndrEBLv8/6BEXzyus=
|
||||
golang.org/x/text v0.39.0/go.mod h1:3UwRclnC2g0TU9x8PZiyfOajCd1zaUNHF9cvqcQZ+ZM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
mvdan.cc/sh/v3 v3.13.1 h1:DP3TfgZhDkT7lerUdnp6PTGKyxxzz6T+cOlY/xEvfWk=
|
||||
mvdan.cc/sh/v3 v3.13.1/go.mod h1:lXJ8SexMvEVcHCoDvAGLZgFJ9Wsm2sulmoNEXGhYZD0=
|
||||
|
|
|
|||
40
log.go
Normal file
40
log.go
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/charmbracelet/log"
|
||||
gap "github.com/muesli/go-app-paths"
|
||||
)
|
||||
|
||||
func getLogFilePath() (string, error) {
|
||||
dir, err := gap.NewScope(gap.User, "glow").CacheDir()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to get cache dir: %w", err)
|
||||
}
|
||||
return filepath.Join(dir, "glow.log"), nil
|
||||
}
|
||||
|
||||
func setupLog() (func() error, error) {
|
||||
log.SetOutput(io.Discard)
|
||||
// Log to file, if set
|
||||
logFile, err := getLogFilePath()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(logFile), 0o755); err != nil { //nolint:gosec
|
||||
// log disabled
|
||||
return func() error { return nil }, nil //nolint:nilerr
|
||||
}
|
||||
f, err := os.OpenFile(logFile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o644) //nolint:gosec
|
||||
if err != nil {
|
||||
// log disabled
|
||||
return func() error { return nil }, nil //nolint:nilerr
|
||||
}
|
||||
log.SetOutput(f)
|
||||
log.SetLevel(log.DebugLevel)
|
||||
return f.Close, nil
|
||||
}
|
||||
352
main.go
352
main.go
|
|
@ -1,9 +1,11 @@
|
|||
// Package main provides the entry point for the Glow CLI application.
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
|
|
@ -11,11 +13,15 @@ import (
|
|||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"mvdan.cc/sh/v3/shell"
|
||||
|
||||
"github.com/caarlos0/env/v11"
|
||||
"github.com/charmbracelet/glamour"
|
||||
"github.com/charmbracelet/glow/ui"
|
||||
"github.com/charmbracelet/glow/utils"
|
||||
"github.com/meowgorithm/babyenv"
|
||||
"github.com/charmbracelet/glamour/styles"
|
||||
"github.com/charmbracelet/glow/v2/ui"
|
||||
"github.com/charmbracelet/glow/v2/utils"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/charmbracelet/log"
|
||||
gap "github.com/muesli/go-app-paths"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
|
@ -28,23 +34,34 @@ var (
|
|||
// CommitSHA as provided by goreleaser.
|
||||
CommitSHA = ""
|
||||
|
||||
readmeNames = []string{"README.md", "README"}
|
||||
configFile string
|
||||
pager bool
|
||||
style string
|
||||
width uint
|
||||
showAllFiles bool
|
||||
localOnly bool
|
||||
mouse bool
|
||||
readmeNames = []string{"README.md", "README", "Readme.md", "Readme", "readme.md", "readme"}
|
||||
configFile string
|
||||
pager bool
|
||||
tui bool
|
||||
style string
|
||||
width uint
|
||||
showAllFiles bool
|
||||
showLineNumbers bool
|
||||
preserveNewLines bool
|
||||
mouse bool
|
||||
|
||||
rootCmd = &cobra.Command{
|
||||
Use: "glow [SOURCE|DIR]",
|
||||
Short: "Render markdown on the CLI, with pizzazz!",
|
||||
Long: paragraph(fmt.Sprintf("\nRender markdown on the CLI, %s!", keyword("with pizzazz"))),
|
||||
Use: "glow [SOURCE|DIR]",
|
||||
Short: "Render markdown on the CLI, with pizzazz!",
|
||||
Long: paragraph(
|
||||
fmt.Sprintf("\nRender markdown on the CLI, %s!", keyword("with pizzazz")),
|
||||
),
|
||||
SilenceErrors: false,
|
||||
SilenceUsage: false,
|
||||
SilenceUsage: true,
|
||||
TraverseChildren: true,
|
||||
RunE: execute,
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
ValidArgsFunction: func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
|
||||
return nil, cobra.ShellCompDirectiveDefault
|
||||
},
|
||||
PersistentPreRunE: func(cmd *cobra.Command, _ []string) error {
|
||||
return validateOptions(cmd)
|
||||
},
|
||||
RunE: execute,
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -62,31 +79,22 @@ func sourceFromArg(arg string) (*source, error) {
|
|||
}
|
||||
|
||||
// a GitHub or GitLab URL (even without the protocol):
|
||||
if u, ok := isGitHubURL(arg); ok {
|
||||
src, err := findGitHubREADME(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return src, nil
|
||||
}
|
||||
if u, ok := isGitLabURL(arg); ok {
|
||||
src, err := findGitLabREADME(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
src, err := readmeURL(arg)
|
||||
if src != nil && err == nil {
|
||||
// if there's an error, try next methods...
|
||||
return src, nil
|
||||
}
|
||||
|
||||
// HTTP(S) URLs:
|
||||
if u, err := url.ParseRequestURI(arg); err == nil && strings.Contains(arg, "://") {
|
||||
if u, err := url.ParseRequestURI(arg); err == nil && strings.Contains(arg, "://") { //nolint:nestif
|
||||
if u.Scheme != "" {
|
||||
if u.Scheme != "http" && u.Scheme != "https" {
|
||||
return nil, fmt.Errorf("%s is not a supported protocol", u.Scheme)
|
||||
}
|
||||
// consumer of the source is responsible for closing the ReadCloser.
|
||||
resp, err := http.Get(u.String()) // nolint:bodyclose
|
||||
resp, err := http.Get(u.String()) //nolint: noctx,bodyclose
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("unable to get url: %w", err)
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("HTTP status %d", resp.StatusCode)
|
||||
|
|
@ -101,9 +109,12 @@ func sourceFromArg(arg string) (*source, error) {
|
|||
arg = "."
|
||||
}
|
||||
st, err := os.Stat(arg)
|
||||
if err == nil && st.IsDir() {
|
||||
if err == nil && st.IsDir() { //nolint:nestif
|
||||
var src *source
|
||||
_ = filepath.Walk(arg, func(path string, info os.FileInfo, err error) error {
|
||||
_ = filepath.Walk(arg, func(path string, _ os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range readmeNames {
|
||||
if strings.EqualFold(filepath.Base(path), v) {
|
||||
r, err := os.Open(path)
|
||||
|
|
@ -128,28 +139,49 @@ func sourceFromArg(arg string) (*source, error) {
|
|||
return nil, errors.New("missing markdown source")
|
||||
}
|
||||
|
||||
// a file:
|
||||
r, err := os.Open(arg)
|
||||
u, _ := filepath.Abs(arg)
|
||||
return &source{r, u}, err
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to open file: %w", err)
|
||||
}
|
||||
u, err := filepath.Abs(arg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get absolute path: %w", err)
|
||||
}
|
||||
return &source{r, u}, nil
|
||||
}
|
||||
|
||||
// validateStyle checks if the style is a default style, if not, checks that
|
||||
// the custom style exists.
|
||||
func validateStyle(style string) error {
|
||||
if style != "auto" && styles.DefaultStyles[style] == nil {
|
||||
style = utils.ExpandPath(style)
|
||||
if _, err := os.Stat(style); errors.Is(err, fs.ErrNotExist) {
|
||||
return fmt.Errorf("specified style does not exist: %s", style)
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("unable to stat file: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateOptions(cmd *cobra.Command) error {
|
||||
// grab config values from Viper
|
||||
width = viper.GetUint("width")
|
||||
localOnly = viper.GetBool("local")
|
||||
mouse = viper.GetBool("mouse")
|
||||
pager = viper.GetBool("pager")
|
||||
tui = viper.GetBool("tui")
|
||||
showAllFiles = viper.GetBool("all")
|
||||
preserveNewLines = viper.GetBool("preserveNewLines")
|
||||
showLineNumbers = viper.GetBool("showLineNumbers")
|
||||
|
||||
if pager && tui {
|
||||
return errors.New("cannot use both pager and tui")
|
||||
}
|
||||
|
||||
// validate the glamour style
|
||||
style = viper.GetString("style")
|
||||
if style != "auto" && glamour.DefaultStyles[style] == nil {
|
||||
style = utils.ExpandPath(style)
|
||||
if _, err := os.Stat(style); os.IsNotExist(err) {
|
||||
return fmt.Errorf("Specified style does not exist: %s", style)
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateStyle(style); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
isTerminal := term.IsTerminal(int(os.Stdout.Fd()))
|
||||
|
|
@ -160,18 +192,20 @@ func validateOptions(cmd *cobra.Command) error {
|
|||
}
|
||||
|
||||
// Detect terminal width
|
||||
if isTerminal && width == 0 && !cmd.Flags().Changed("width") {
|
||||
w, _, err := term.GetSize(int(os.Stdout.Fd()))
|
||||
if err == nil {
|
||||
width = uint(w)
|
||||
}
|
||||
if !cmd.Flags().Changed("width") { //nolint:nestif
|
||||
if isTerminal && width == 0 {
|
||||
w, _, err := term.GetSize(int(os.Stdout.Fd()))
|
||||
if err == nil {
|
||||
width = uint(w) //nolint:gosec
|
||||
}
|
||||
|
||||
if width > 120 {
|
||||
width = 120
|
||||
if width > 120 {
|
||||
width = 120
|
||||
}
|
||||
}
|
||||
if width == 0 {
|
||||
width = 80
|
||||
}
|
||||
}
|
||||
if width == 0 {
|
||||
width = 80
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -179,7 +213,7 @@ func validateOptions(cmd *cobra.Command) error {
|
|||
func stdinIsPipe() (bool, error) {
|
||||
stat, err := os.Stdin.Stat()
|
||||
if err != nil {
|
||||
return false, err
|
||||
return false, fmt.Errorf("unable to open file: %w", err)
|
||||
}
|
||||
if stat.Mode()&os.ModeCharDevice == 0 || stat.Size() > 0 {
|
||||
return true, nil
|
||||
|
|
@ -188,11 +222,6 @@ func stdinIsPipe() (bool, error) {
|
|||
}
|
||||
|
||||
func execute(cmd *cobra.Command, args []string) error {
|
||||
initConfig()
|
||||
if err := validateOptions(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if stdin is a pipe then use stdin for input. note that you can also
|
||||
// explicitly use a - to read from stdin.
|
||||
if yes, err := stdinIsPipe(); err != nil {
|
||||
|
|
@ -206,7 +235,7 @@ func execute(cmd *cobra.Command, args []string) error {
|
|||
switch len(args) {
|
||||
// TUI running on cwd
|
||||
case 0:
|
||||
return runTUI("", false)
|
||||
return runTUI("", "")
|
||||
|
||||
// TUI with possible dir argument
|
||||
case 1:
|
||||
|
|
@ -216,7 +245,7 @@ func execute(cmd *cobra.Command, args []string) error {
|
|||
if err == nil && info.IsDir() {
|
||||
p, err := filepath.Abs(args[0])
|
||||
if err == nil {
|
||||
return runTUI(p, false)
|
||||
return runTUI(p, "")
|
||||
}
|
||||
}
|
||||
fallthrough
|
||||
|
|
@ -246,7 +275,7 @@ func executeArg(cmd *cobra.Command, arg string, w io.Writer) error {
|
|||
func executeCLI(cmd *cobra.Command, src *source, w io.Writer) error {
|
||||
b, err := io.ReadAll(src.reader)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("unable to read from reader: %w", err)
|
||||
}
|
||||
|
||||
b = utils.RemoveFrontmatter(b)
|
||||
|
|
@ -259,104 +288,106 @@ func executeCLI(cmd *cobra.Command, src *source, w io.Writer) error {
|
|||
baseURL = u.String() + "/"
|
||||
}
|
||||
|
||||
// initialize glamour
|
||||
var gs glamour.TermRendererOption
|
||||
if style == "auto" {
|
||||
gs = glamour.WithEnvironmentConfig()
|
||||
} else {
|
||||
gs = glamour.WithStylePath(style)
|
||||
}
|
||||
isCode := !utils.IsMarkdownFile(src.URL)
|
||||
|
||||
// initialize glamour
|
||||
r, err := glamour.NewTermRenderer(
|
||||
gs,
|
||||
glamour.WithWordWrap(int(width)),
|
||||
glamour.WithColorProfile(lipgloss.ColorProfile()),
|
||||
utils.GlamourStyle(style, isCode),
|
||||
glamour.WithWordWrap(int(width)), //nolint:gosec
|
||||
glamour.WithBaseURL(baseURL),
|
||||
glamour.WithPreservedNewLines(),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("unable to create renderer: %w", err)
|
||||
}
|
||||
|
||||
out, err := r.RenderBytes(b)
|
||||
content := string(b)
|
||||
ext := filepath.Ext(src.URL)
|
||||
if isCode {
|
||||
content = utils.WrapCodeBlock(string(b), ext)
|
||||
}
|
||||
|
||||
out, err := r.Render(content)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("unable to render markdown: %w", err)
|
||||
}
|
||||
|
||||
// trim lines
|
||||
lines := strings.Split(string(out), "\n")
|
||||
var cb strings.Builder
|
||||
for i, s := range lines {
|
||||
cb.WriteString(strings.TrimSpace(s))
|
||||
|
||||
// don't add an artificial newline after the last split
|
||||
if i+1 < len(lines) {
|
||||
cb.WriteString("\n")
|
||||
}
|
||||
}
|
||||
content := cb.String()
|
||||
|
||||
// display
|
||||
if pager || cmd.Flags().Changed("pager") {
|
||||
switch {
|
||||
case pager || cmd.Flags().Changed("pager"):
|
||||
pagerCmd := os.Getenv("PAGER")
|
||||
if pagerCmd == "" {
|
||||
pagerCmd = "less -r"
|
||||
}
|
||||
|
||||
pa := strings.Split(pagerCmd, " ")
|
||||
c := exec.Command(pa[0], pa[1:]...) // nolint:gosec
|
||||
c.Stdin = strings.NewReader(content)
|
||||
fields, err := shell.Fields(pagerCmd, os.Getenv)
|
||||
if err != nil || len(fields) == 0 {
|
||||
return fmt.Errorf("unable to parse PAGER command: %s", pagerCmd)
|
||||
}
|
||||
c := exec.Command(fields[0], fields[1:]...) //nolint:gosec
|
||||
c.Stdin = strings.NewReader(out)
|
||||
c.Stdout = os.Stdout
|
||||
return c.Run()
|
||||
if err := c.Run(); err != nil {
|
||||
return fmt.Errorf("unable to run command: %w", err)
|
||||
}
|
||||
return nil
|
||||
case tui || cmd.Flags().Changed("tui"):
|
||||
path := ""
|
||||
if !isURL(src.URL) {
|
||||
path = src.URL
|
||||
}
|
||||
return runTUI(path, content)
|
||||
default:
|
||||
if _, err = fmt.Fprint(w, out); err != nil {
|
||||
return fmt.Errorf("unable to write to writer: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Fprint(w, content)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runTUI(workingDirectory string, stashedOnly bool) error {
|
||||
func runTUI(path string, content string) error {
|
||||
// Read environment to get debugging stuff
|
||||
var cfg ui.Config
|
||||
if err := babyenv.Parse(&cfg); err != nil {
|
||||
cfg, err := env.ParseAs[ui.Config]()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing config: %v", err)
|
||||
}
|
||||
|
||||
// Log to file, if set
|
||||
if cfg.Logfile != "" {
|
||||
f, err := tea.LogToFile(cfg.Logfile, "glow")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close() //nolint:errcheck
|
||||
// use style set in env, or auto if unset
|
||||
if err := validateStyle(cfg.GlamourStyle); err != nil {
|
||||
cfg.GlamourStyle = style
|
||||
}
|
||||
|
||||
cfg.WorkingDirectory = workingDirectory
|
||||
cfg.DocumentTypes = ui.NewDocTypeSet()
|
||||
cfg.Path = path
|
||||
cfg.ShowAllFiles = showAllFiles
|
||||
cfg.ShowLineNumbers = showLineNumbers
|
||||
cfg.GlamourMaxWidth = width
|
||||
cfg.GlamourStyle = style
|
||||
cfg.EnableMouse = mouse
|
||||
|
||||
if stashedOnly {
|
||||
cfg.DocumentTypes.Add(ui.StashedDoc, ui.NewsDoc)
|
||||
} else if localOnly {
|
||||
cfg.DocumentTypes.Add(ui.LocalDoc)
|
||||
}
|
||||
cfg.PreserveNewLines = preserveNewLines
|
||||
|
||||
// Run Bubble Tea program
|
||||
if _, err := ui.NewProgram(cfg).Run(); err != nil {
|
||||
return err
|
||||
if _, err := ui.NewProgram(cfg, content).Run(); err != nil {
|
||||
return fmt.Errorf("unable to run tui program: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
closer, err := setupLog()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
_ = closer()
|
||||
os.Exit(1)
|
||||
}
|
||||
_ = closer()
|
||||
}
|
||||
|
||||
func init() {
|
||||
tryLoadConfigFromDefaultPlaces()
|
||||
if len(CommitSHA) >= 7 {
|
||||
vt := rootCmd.VersionTemplate()
|
||||
rootCmd.SetVersionTemplate(vt[:len(vt)-1] + " (" + CommitSHA[0:7] + ")\n")
|
||||
|
|
@ -365,63 +396,78 @@ func init() {
|
|||
Version = "unknown (built from source)"
|
||||
}
|
||||
rootCmd.Version = Version
|
||||
|
||||
scope := gap.NewScope(gap.User, "glow")
|
||||
defaultConfigFile, _ := scope.ConfigPath("glow.yml")
|
||||
rootCmd.InitDefaultCompletionCmd()
|
||||
|
||||
// "Glow Classic" cli arguments
|
||||
rootCmd.PersistentFlags().StringVar(&configFile, "config", "", fmt.Sprintf("config file (default %s)", defaultConfigFile))
|
||||
rootCmd.PersistentFlags().StringVar(&configFile, "config", "", fmt.Sprintf("config file (default %s)", viper.GetViper().ConfigFileUsed()))
|
||||
rootCmd.Flags().BoolVarP(&pager, "pager", "p", false, "display with pager")
|
||||
rootCmd.Flags().StringVarP(&style, "style", "s", "auto", "style name or JSON path")
|
||||
rootCmd.Flags().UintVarP(&width, "width", "w", 0, "word-wrap at width")
|
||||
rootCmd.Flags().BoolVarP(&tui, "tui", "t", false, "display with tui")
|
||||
rootCmd.Flags().StringVarP(&style, "style", "s", styles.AutoStyle, "style name or JSON path")
|
||||
rootCmd.Flags().UintVarP(&width, "width", "w", 0, "word-wrap at width (set to 0 to disable)")
|
||||
rootCmd.Flags().BoolVarP(&showAllFiles, "all", "a", false, "show system files and directories (TUI-mode only)")
|
||||
rootCmd.Flags().BoolVarP(&localOnly, "local", "l", false, "show local files only; no network (TUI-mode only)")
|
||||
rootCmd.Flags().BoolVarP(&showLineNumbers, "line-numbers", "l", false, "show line numbers (TUI-mode only)")
|
||||
rootCmd.Flags().BoolVarP(&preserveNewLines, "preserve-new-lines", "n", false, "preserve newlines in the output")
|
||||
rootCmd.Flags().BoolVarP(&mouse, "mouse", "m", false, "enable mouse wheel (TUI-mode only)")
|
||||
_ = rootCmd.Flags().MarkHidden("mouse")
|
||||
|
||||
// Config bindings
|
||||
_ = viper.BindPFlag("pager", rootCmd.Flags().Lookup("pager"))
|
||||
_ = viper.BindPFlag("tui", rootCmd.Flags().Lookup("tui"))
|
||||
_ = viper.BindPFlag("style", rootCmd.Flags().Lookup("style"))
|
||||
_ = viper.BindPFlag("width", rootCmd.Flags().Lookup("width"))
|
||||
_ = viper.BindPFlag("local", rootCmd.Flags().Lookup("local"))
|
||||
_ = viper.BindPFlag("debug", rootCmd.Flags().Lookup("debug"))
|
||||
_ = viper.BindPFlag("mouse", rootCmd.Flags().Lookup("mouse"))
|
||||
viper.SetDefault("style", "auto")
|
||||
_ = viper.BindPFlag("preserveNewLines", rootCmd.Flags().Lookup("preserve-new-lines"))
|
||||
_ = viper.BindPFlag("showLineNumbers", rootCmd.Flags().Lookup("line-numbers"))
|
||||
_ = viper.BindPFlag("all", rootCmd.Flags().Lookup("all"))
|
||||
|
||||
viper.SetDefault("style", styles.AutoStyle)
|
||||
viper.SetDefault("width", 0)
|
||||
viper.SetDefault("local", "false")
|
||||
viper.SetDefault("all", true)
|
||||
|
||||
// Stash
|
||||
stashCmd.PersistentFlags().StringVarP(&memo, "memo", "m", "", "memo/note for stashing")
|
||||
rootCmd.AddCommand(stashCmd)
|
||||
|
||||
rootCmd.AddCommand(configCmd)
|
||||
rootCmd.AddCommand(configCmd, manCmd)
|
||||
}
|
||||
|
||||
func initConfig() {
|
||||
if configFile != "" {
|
||||
viper.SetConfigFile(configFile)
|
||||
} else {
|
||||
scope := gap.NewScope(gap.User, "glow")
|
||||
dirs, err := scope.ConfigDirs()
|
||||
if err != nil {
|
||||
fmt.Println("Can't retrieve default config. Please manually pass a config file with '--config'")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
for _, v := range dirs {
|
||||
viper.AddConfigPath(v)
|
||||
}
|
||||
viper.SetConfigName("glow")
|
||||
viper.SetConfigType("yaml")
|
||||
func tryLoadConfigFromDefaultPlaces() {
|
||||
scope := gap.NewScope(gap.User, "glow")
|
||||
dirs, err := scope.ConfigDirs()
|
||||
if err != nil {
|
||||
fmt.Println("Could not load find configuration directory.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if c := os.Getenv("XDG_CONFIG_HOME"); c != "" {
|
||||
dirs = append([]string{filepath.Join(c, "glow")}, dirs...)
|
||||
}
|
||||
|
||||
if c := os.Getenv("GLOW_CONFIG_HOME"); c != "" {
|
||||
dirs = append([]string{c}, dirs...)
|
||||
}
|
||||
|
||||
for _, v := range dirs {
|
||||
viper.AddConfigPath(v)
|
||||
}
|
||||
|
||||
viper.SetConfigName("glow")
|
||||
viper.SetConfigType("yaml")
|
||||
viper.SetEnvPrefix("glow")
|
||||
viper.AutomaticEnv()
|
||||
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
|
||||
fmt.Println("Error parsing config:", err)
|
||||
os.Exit(1)
|
||||
log.Warn("Could not parse configuration file", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// fmt.Println("Using config file:", viper.ConfigFileUsed())
|
||||
if used := viper.ConfigFileUsed(); used != "" {
|
||||
log.Debug("Using configuration file", "path", viper.ConfigFileUsed())
|
||||
return
|
||||
}
|
||||
|
||||
if viper.ConfigFileUsed() == "" {
|
||||
configFile = filepath.Join(dirs[0], "glow.yml")
|
||||
}
|
||||
if err := ensureConfigFile(); err != nil {
|
||||
log.Error("Could not create default configuration", "error", err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
29
man_cmd.go
Normal file
29
man_cmd.go
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
mcobra "github.com/muesli/mango-cobra"
|
||||
"github.com/muesli/roff"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var manCmd = &cobra.Command{
|
||||
Use: "man",
|
||||
Short: "Generates manpages",
|
||||
SilenceUsage: true,
|
||||
DisableFlagsInUseLine: true,
|
||||
Hidden: true,
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(*cobra.Command, []string) error {
|
||||
manPage, err := mcobra.NewManPage(1, rootCmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to instantiate man page: %w", err)
|
||||
}
|
||||
if _, err := fmt.Fprint(os.Stdout, manPage.Build(roff.NewDocument())); err != nil {
|
||||
return fmt.Errorf("unable to build man page: %w", err)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
82
stash_cmd.go
82
stash_cmd.go
|
|
@ -1,82 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/charm"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
memo string
|
||||
dot = lipgloss.NewStyle().Foreground(lipgloss.Color("#04B575")).Render("•")
|
||||
|
||||
stashCmd = &cobra.Command{
|
||||
Use: "stash [SOURCE]",
|
||||
Hidden: false,
|
||||
Short: "Stash a markdown",
|
||||
Long: paragraph(fmt.Sprintf("\nDo %s stuff. Run with no arguments to browse your stash or pass a path to a markdown file to stash it.", keyword("stash"))),
|
||||
Example: paragraph("glow stash\nglow stash README.md\nglow stash -m \"secret notes\" path/to/notes.md"),
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
initConfig()
|
||||
if len(args) == 0 {
|
||||
return runTUI("", true)
|
||||
}
|
||||
|
||||
filePath := args[0]
|
||||
|
||||
if memo == "" {
|
||||
memo = strings.Replace(path.Base(filePath), path.Ext(filePath), "", 1)
|
||||
}
|
||||
|
||||
cc := initCharmClient()
|
||||
f, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bad filename")
|
||||
}
|
||||
|
||||
defer f.Close() //nolint:errcheck
|
||||
b, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading file")
|
||||
}
|
||||
|
||||
_, err = cc.StashMarkdown(memo, string(b))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error stashing markdown")
|
||||
}
|
||||
|
||||
fmt.Println(dot + " Stashed!")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func getCharmConfig() *charm.Config {
|
||||
cfg, err := charm.ConfigFromEnv()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
func initCharmClient() *charm.Client {
|
||||
cfg := getCharmConfig()
|
||||
cc, err := charm.NewClient(cfg)
|
||||
if err == charm.ErrMissingSSHAuth {
|
||||
fmt.Println(paragraph("We had some trouble authenticating via SSH. If this continues to happen the Charm tool may be able to help you. More info at https://github.com/charmbracelet/charm."))
|
||||
os.Exit(1)
|
||||
} else if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return cc
|
||||
}
|
||||
8
style.go
8
style.go
|
|
@ -1,13 +1,13 @@
|
|||
package main
|
||||
|
||||
import . "github.com/charmbracelet/lipgloss" //nolint:revive
|
||||
import "github.com/charmbracelet/lipgloss"
|
||||
|
||||
var (
|
||||
keyword = NewStyle().
|
||||
Foreground(Color("#04B575")).
|
||||
keyword = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("#04B575")).
|
||||
Render
|
||||
|
||||
paragraph = NewStyle().
|
||||
paragraph = lipgloss.NewStyle().
|
||||
Width(78).
|
||||
Padding(0, 0, 0, 2).
|
||||
Render
|
||||
|
|
|
|||
34
ui/config.go
34
ui/config.go
|
|
@ -2,29 +2,19 @@ package ui
|
|||
|
||||
// Config contains TUI-specific configuration.
|
||||
type Config struct {
|
||||
ShowAllFiles bool
|
||||
Gopath string `env:"GOPATH"`
|
||||
HomeDir string `env:"HOME"`
|
||||
GlamourMaxWidth uint
|
||||
GlamourStyle string
|
||||
EnableMouse bool
|
||||
ShowAllFiles bool
|
||||
ShowLineNumbers bool
|
||||
Gopath string `env:"GOPATH"`
|
||||
HomeDir string `env:"HOME"`
|
||||
GlamourMaxWidth uint
|
||||
GlamourStyle string `env:"GLAMOUR_STYLE"`
|
||||
EnableMouse bool
|
||||
PreserveNewLines bool
|
||||
|
||||
// Which directory should we start from?
|
||||
WorkingDirectory string
|
||||
|
||||
// Which document types shall we show?
|
||||
DocumentTypes DocTypeSet
|
||||
// Working directory or file path
|
||||
Path string
|
||||
|
||||
// For debugging the UI
|
||||
Logfile string `env:"GLOW_LOGFILE"`
|
||||
HighPerformancePager bool `env:"GLOW_HIGH_PERFORMANCE_PAGER" default:"true"`
|
||||
GlamourEnabled bool `env:"GLOW_ENABLE_GLAMOUR" default:"true"`
|
||||
}
|
||||
|
||||
func (c Config) localOnly() bool {
|
||||
return c.DocumentTypes.Equals(NewDocTypeSet(LocalDoc))
|
||||
}
|
||||
|
||||
func (c Config) stashedOnly() bool {
|
||||
return c.DocumentTypes.Contains(StashedDoc) && !c.DocumentTypes.Contains(LocalDoc)
|
||||
HighPerformancePager bool `env:"GLOW_HIGH_PERFORMANCE_PAGER" envDefault:"true"`
|
||||
GlamourEnabled bool `env:"GLOW_ENABLE_GLAMOUR" envDefault:"true"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package ui
|
||||
|
||||
const (
|
||||
pagerStashIcon = "🔒"
|
||||
)
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
// +build windows
|
||||
|
||||
package ui
|
||||
|
||||
const (
|
||||
pagerStashIcon = "•"
|
||||
)
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
package ui
|
||||
|
||||
// DocType represents a type of markdown document.
|
||||
type DocType int
|
||||
|
||||
// Available document types.
|
||||
const (
|
||||
NoDocType DocType = iota
|
||||
LocalDoc
|
||||
StashedDoc
|
||||
ConvertedDoc
|
||||
NewsDoc
|
||||
)
|
||||
|
||||
func (d DocType) String() string {
|
||||
return [...]string{
|
||||
"none",
|
||||
"local",
|
||||
"stashed",
|
||||
"converted",
|
||||
"news",
|
||||
}[d]
|
||||
}
|
||||
|
||||
// DocTypeSet is a set (in the mathematic sense) of document types.
|
||||
type DocTypeSet map[DocType]struct{}
|
||||
|
||||
// NewDocTypeSet returns a set of document types.
|
||||
func NewDocTypeSet(t ...DocType) DocTypeSet {
|
||||
d := DocTypeSet(make(map[DocType]struct{}))
|
||||
if len(t) > 0 {
|
||||
d.Add(t...)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// Add adds a document type of the set.
|
||||
func (d *DocTypeSet) Add(t ...DocType) int {
|
||||
for _, v := range t {
|
||||
(*d)[v] = struct{}{}
|
||||
}
|
||||
return len(*d)
|
||||
}
|
||||
|
||||
// Contains returns whether or not the set contains the given DocTypes.
|
||||
func (d DocTypeSet) Contains(m ...DocType) bool {
|
||||
matches := 0
|
||||
for _, t := range m {
|
||||
if _, found := d[t]; found {
|
||||
matches++
|
||||
}
|
||||
}
|
||||
return matches == len(m)
|
||||
}
|
||||
|
||||
// Difference return a DocumentType set that does not contain the given types.
|
||||
func (d DocTypeSet) Difference(t ...DocType) DocTypeSet {
|
||||
c := copyDocumentTypes(d)
|
||||
for k := range c {
|
||||
for _, docType := range t {
|
||||
if k == docType {
|
||||
delete(c, k)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// Equals returns whether or not the two sets are equal.
|
||||
func (d DocTypeSet) Equals(other DocTypeSet) bool {
|
||||
return d.Contains(other.AsSlice()...) && len(d) == len(other)
|
||||
}
|
||||
|
||||
// AsSlice returns the set as a slice of document types.
|
||||
func (d DocTypeSet) AsSlice() (agg []DocType) {
|
||||
for k := range d {
|
||||
agg = append(agg, k)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Return a copy of the given DoctTypes map.
|
||||
func copyDocumentTypes(d DocTypeSet) DocTypeSet {
|
||||
c := make(map[DocType]struct{})
|
||||
for k, v := range d {
|
||||
c[k] = v
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDocTypeContains(t *testing.T) {
|
||||
d := NewDocTypeSet(LocalDoc)
|
||||
|
||||
if !d.Contains(LocalDoc) {
|
||||
t.Error("Contains reported it doesn't contain a value which it absolutely does contain")
|
||||
}
|
||||
|
||||
if d.Contains(NewsDoc) {
|
||||
t.Error("Contains reported the set contains a value it certainly does not")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocTypeDifference(t *testing.T) {
|
||||
original := NewDocTypeSet(LocalDoc, StashedDoc, ConvertedDoc, NewsDoc)
|
||||
difference := original.Difference(LocalDoc, NewsDoc)
|
||||
expected := NewDocTypeSet(StashedDoc, ConvertedDoc)
|
||||
|
||||
// Make sure the difference operation worked
|
||||
if !reflect.DeepEqual(difference, expected) {
|
||||
t.Errorf("difference returned %+v; expected %+v", difference, expected)
|
||||
}
|
||||
|
||||
// Make sure original set was not mutated
|
||||
if reflect.DeepEqual(original, difference) {
|
||||
t.Errorf("original set was mutated when it should not have been")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocTypeEquality(t *testing.T) {
|
||||
a := NewDocTypeSet(LocalDoc, StashedDoc)
|
||||
b := NewDocTypeSet(LocalDoc, StashedDoc)
|
||||
c := NewDocTypeSet(LocalDoc)
|
||||
|
||||
if !a.Equals(b) {
|
||||
t.Errorf("Equality test failed for %+v and %+v; expected true, got false", a, b)
|
||||
}
|
||||
|
||||
if a.Equals(c) {
|
||||
t.Errorf("Equality test failed for %+v and %+v; expected false, got true", a, c)
|
||||
}
|
||||
}
|
||||
11
ui/editor.go
11
ui/editor.go
|
|
@ -7,16 +7,13 @@ import (
|
|||
|
||||
type editorFinishedMsg struct{ err error }
|
||||
|
||||
func openEditor(path string) tea.Cmd {
|
||||
func openEditor(path string, lineno int) tea.Cmd {
|
||||
cb := func(err error) tea.Msg {
|
||||
return editorFinishedMsg{err}
|
||||
}
|
||||
|
||||
editor, err := editor.Cmd("Glow", path)
|
||||
cmd, err := editor.Cmd("Glow", path, editor.LineNumber(uint(lineno))) //nolint:gosec
|
||||
if err != nil {
|
||||
return func() tea.Msg {
|
||||
return errMsg{err}
|
||||
}
|
||||
return func() tea.Msg { return cb(err) }
|
||||
}
|
||||
return tea.ExecProcess(editor, cb)
|
||||
return tea.ExecProcess(cmd, cb)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package ui
|
||||
|
||||
import "path/filepath"
|
||||
|
||||
func ignorePatterns(m model) []string {
|
||||
func ignorePatterns(m commonModel) []string {
|
||||
return []string{
|
||||
filepath.Join(m.common.cfg.HomeDir, "Library"),
|
||||
m.common.cfg.Gopath,
|
||||
filepath.Join(m.cfg.HomeDir, "Library"),
|
||||
m.cfg.Gopath,
|
||||
"node_modules",
|
||||
".*",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@
|
|||
|
||||
package ui
|
||||
|
||||
func ignorePatterns(m model) []string {
|
||||
func ignorePatterns(m commonModel) []string {
|
||||
return []string{
|
||||
m.common.cfg.Gopath,
|
||||
m.cfg.Gopath,
|
||||
"node_modules",
|
||||
".*",
|
||||
}
|
||||
|
|
|
|||
130
ui/markdown.go
130
ui/markdown.go
|
|
@ -1,42 +1,19 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"log"
|
||||
"fmt"
|
||||
"math"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/charmbracelet/charm"
|
||||
"github.com/charmbracelet/log"
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/segmentio/ksuid"
|
||||
"golang.org/x/text/runes"
|
||||
"golang.org/x/text/transform"
|
||||
"golang.org/x/text/unicode/norm"
|
||||
)
|
||||
|
||||
// markdown wraps charm.Markdown.
|
||||
type markdown struct {
|
||||
docType DocType
|
||||
|
||||
// Stash identifier. This exists so we can keep track of documents stashed
|
||||
// in-session as they relate to their original, non-stashed counterparts.
|
||||
// All documents have a stashID, however when a document is stashed that
|
||||
// document inherits the stashID of the original.
|
||||
stashID ksuid.KSUID
|
||||
|
||||
// Unique identifier. Unlike the stash identifier, this value should always
|
||||
// be unique so we can confidently find it an operate on it (versus stashID,
|
||||
// which could match both an original or stashed document).
|
||||
uniqueID ksuid.KSUID
|
||||
|
||||
// Some of the document's original values before this document was stashed.
|
||||
// These are irrelevant if this document was not stashed in this session.
|
||||
originalDocType DocType
|
||||
originalTimestamp time.Time
|
||||
originalNote string
|
||||
|
||||
// Full path of a local markdown file. Only relevant to local documents and
|
||||
// those that have been stashed in this session.
|
||||
localPath string
|
||||
|
|
@ -46,102 +23,24 @@ type markdown struct {
|
|||
// field is ephemeral, and should only be referenced during filtering.
|
||||
filterValue string
|
||||
|
||||
charm.Markdown
|
||||
}
|
||||
|
||||
func (m *markdown) generateIDs() {
|
||||
if m.stashID.IsNil() {
|
||||
m.stashID = ksuid.New()
|
||||
}
|
||||
m.uniqueID = ksuid.New()
|
||||
}
|
||||
|
||||
// convertToStashed converts this document into its stashed state.
|
||||
func (m *markdown) convertToStashed() {
|
||||
if m.docType == ConvertedDoc {
|
||||
if debug {
|
||||
log.Println("not converting already converted document:", m)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
m.originalDocType = m.docType
|
||||
m.originalTimestamp = m.CreatedAt
|
||||
m.originalNote = m.Note
|
||||
|
||||
if m.docType == LocalDoc {
|
||||
m.Note = strings.Replace(path.Base(m.localPath), path.Ext(m.localPath), "", 1)
|
||||
}
|
||||
m.CreatedAt = time.Now()
|
||||
m.docType = ConvertedDoc
|
||||
}
|
||||
|
||||
// revert reverts this document from its stashed state.
|
||||
func (m *markdown) revertFromStashed() {
|
||||
if m.docType != ConvertedDoc {
|
||||
log.Printf("not reverting document of type %s: %v", m.docType, m)
|
||||
}
|
||||
|
||||
m.docType = m.originalDocType
|
||||
m.CreatedAt = m.originalTimestamp
|
||||
m.Note = m.originalNote
|
||||
Body string
|
||||
Note string
|
||||
Modtime time.Time
|
||||
}
|
||||
|
||||
// Generate the value we're doing to filter against.
|
||||
func (m *markdown) buildFilterValue() {
|
||||
note, err := normalize(m.Note)
|
||||
if err != nil {
|
||||
if debug {
|
||||
log.Printf("error normalizing '%s': %v", m.Note, err)
|
||||
}
|
||||
log.Error("error normalizing", "note", m.Note, "error", err)
|
||||
m.filterValue = m.Note
|
||||
}
|
||||
|
||||
m.filterValue = note
|
||||
}
|
||||
|
||||
// shouldSortAsLocal returns whether or not this markdown should be sorted as though
|
||||
// it's a local markdown document.
|
||||
func (m markdown) shouldSortAsLocal() bool {
|
||||
return m.docType == LocalDoc || m.docType == ConvertedDoc
|
||||
}
|
||||
|
||||
// Sort documents with local files first, then by date.
|
||||
type markdownsByLocalFirst []*markdown
|
||||
|
||||
func (m markdownsByLocalFirst) Len() int { return len(m) }
|
||||
func (m markdownsByLocalFirst) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
|
||||
func (m markdownsByLocalFirst) Less(i, j int) bool {
|
||||
iIsLocal := m[i].shouldSortAsLocal()
|
||||
jIsLocal := m[j].shouldSortAsLocal()
|
||||
|
||||
// Local files (and files that used to be local) come first
|
||||
if iIsLocal && !jIsLocal {
|
||||
return true
|
||||
}
|
||||
if !iIsLocal && jIsLocal {
|
||||
return false
|
||||
}
|
||||
|
||||
// If both are local files, sort by filename. Note that we should never
|
||||
// hit equality here since two files can't have the same path.
|
||||
if iIsLocal && jIsLocal {
|
||||
return strings.Compare(m[i].localPath, m[j].localPath) == -1
|
||||
}
|
||||
|
||||
// Neither are local files so sort by date descending
|
||||
if !m[i].CreatedAt.Equal(m[j].CreatedAt) {
|
||||
return m[i].CreatedAt.After(m[j].CreatedAt)
|
||||
}
|
||||
|
||||
// If the times also match, sort by unique ID.
|
||||
ids := []ksuid.KSUID{m[i].uniqueID, m[j].uniqueID}
|
||||
ksuid.Sort(ids)
|
||||
return ids[0] == m[i].uniqueID
|
||||
}
|
||||
|
||||
func (m markdown) relativeTime() string {
|
||||
return relativeTime(m.CreatedAt)
|
||||
return relativeTime(m.Modtime)
|
||||
}
|
||||
|
||||
// Normalize text to aid in the filtering process. In particular, we remove
|
||||
|
|
@ -150,19 +49,10 @@ func (m markdown) relativeTime() string {
|
|||
func normalize(in string) (string, error) {
|
||||
t := transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC)
|
||||
out, _, err := transform.String(t, in)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// wrapMarkdowns wraps a *charm.Markdown with a *markdown in order to add some
|
||||
// extra metadata.
|
||||
func wrapMarkdowns(t DocType, md []*charm.Markdown) (m []*markdown) {
|
||||
for _, v := range md {
|
||||
m = append(m, &markdown{
|
||||
docType: t,
|
||||
Markdown: *v,
|
||||
})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error normalizing: %w", err)
|
||||
}
|
||||
return m
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Return the time in a human-readable format relative to the current time.
|
||||
|
|
|
|||
505
ui/pager.go
505
ui/pager.go
|
|
@ -2,25 +2,29 @@ package ui
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"math"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/atotto/clipboard"
|
||||
"github.com/charmbracelet/bubbles/spinner"
|
||||
"github.com/charmbracelet/bubbles/textinput"
|
||||
"github.com/charmbracelet/bubbles/viewport"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/charm"
|
||||
"github.com/charmbracelet/glamour"
|
||||
"github.com/charmbracelet/glow/v2/utils"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/charmbracelet/log"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
"github.com/muesli/reflow/ansi"
|
||||
"github.com/muesli/reflow/truncate"
|
||||
"github.com/muesli/termenv"
|
||||
)
|
||||
|
||||
const statusBarHeight = 1
|
||||
const (
|
||||
statusBarHeight = 1
|
||||
lineNumberWidth = 4
|
||||
)
|
||||
|
||||
var (
|
||||
pagerHelpHeight int
|
||||
|
|
@ -28,11 +32,7 @@ var (
|
|||
mintGreen = lipgloss.AdaptiveColor{Light: "#89F0CB", Dark: "#89F0CB"}
|
||||
darkGreen = lipgloss.AdaptiveColor{Light: "#1C8760", Dark: "#1C8760"}
|
||||
|
||||
noteHeading = lipgloss.NewStyle().
|
||||
Foreground(cream).
|
||||
Background(green).
|
||||
Padding(0, 1).
|
||||
SetString("Set Memo")
|
||||
lineNumberFg = lipgloss.AdaptiveColor{Light: "#656565", Dark: "#7D7D7D"}
|
||||
|
||||
statusBarNoteFg = lipgloss.AdaptiveColor{Light: "#656565", Dark: "#7D7D7D"}
|
||||
statusBarBg = lipgloss.AdaptiveColor{Light: "#E6E6E6", Dark: "#242424"}
|
||||
|
|
@ -52,21 +52,11 @@ var (
|
|||
Background(lipgloss.AdaptiveColor{Light: "#DCDCDC", Dark: "#323232"}).
|
||||
Render
|
||||
|
||||
statusBarStashDotStyle = lipgloss.NewStyle().
|
||||
Foreground(green).
|
||||
Background(statusBarBg).
|
||||
Render
|
||||
|
||||
statusBarMessageStyle = lipgloss.NewStyle().
|
||||
Foreground(mintGreen).
|
||||
Background(darkGreen).
|
||||
Render
|
||||
|
||||
statusBarMessageStashIconStyle = lipgloss.NewStyle().
|
||||
Foreground(mintGreen).
|
||||
Background(darkGreen).
|
||||
Render
|
||||
|
||||
statusBarMessageScrollPosStyle = lipgloss.NewStyle().
|
||||
Foreground(mintGreen).
|
||||
Background(darkGreen).
|
||||
|
|
@ -82,47 +72,28 @@ var (
|
|||
Background(lipgloss.AdaptiveColor{Light: "#f2f2f2", Dark: "#1B1B1B"}).
|
||||
Render
|
||||
|
||||
spinnerStyle = lipgloss.NewStyle().
|
||||
Foreground(statusBarNoteFg).
|
||||
Background(statusBarBg)
|
||||
|
||||
pagerNoteInputPromptStyle = lipgloss.NewStyle().
|
||||
Foreground(darkGray).
|
||||
Background(yellowGreen).
|
||||
Padding(0, 1)
|
||||
|
||||
pagerNoteInputStyle = lipgloss.NewStyle().
|
||||
Foreground(darkGray).
|
||||
Background(yellowGreen)
|
||||
|
||||
pagerNoteInputCursorStyle = lipgloss.NewStyle().
|
||||
Foreground(fuchsia)
|
||||
lineNumberStyle = lipgloss.NewStyle().
|
||||
Foreground(lineNumberFg).
|
||||
Render
|
||||
)
|
||||
|
||||
type (
|
||||
contentRenderedMsg string
|
||||
noteSavedMsg *charm.Markdown
|
||||
reloadMsg struct{}
|
||||
)
|
||||
|
||||
type pagerState int
|
||||
|
||||
const (
|
||||
pagerStateBrowse pagerState = iota
|
||||
pagerStateSetNote
|
||||
pagerStateStashing
|
||||
pagerStateStashSuccess
|
||||
pagerStateStatusMessage
|
||||
)
|
||||
|
||||
type pagerModel struct {
|
||||
common *commonModel
|
||||
viewport viewport.Model
|
||||
state pagerState
|
||||
showHelp bool
|
||||
textInput textinput.Model
|
||||
|
||||
spinner spinner.Model
|
||||
spinnerStart time.Time
|
||||
common *commonModel
|
||||
viewport viewport.Model
|
||||
state pagerState
|
||||
showHelp bool
|
||||
|
||||
statusMessage string
|
||||
statusMessageTimer *time.Timer
|
||||
|
|
@ -131,45 +102,27 @@ type pagerModel struct {
|
|||
// it here so we can re-render it on resize.
|
||||
currentDocument markdown
|
||||
|
||||
// Newly stashed markdown. We store it here temporarily so we can replace
|
||||
// currentDocument above after a stash.
|
||||
stashedDocument *markdown
|
||||
watcher *fsnotify.Watcher
|
||||
}
|
||||
|
||||
func newPagerModel(common *commonModel) pagerModel {
|
||||
// Init viewport
|
||||
vp := viewport.New(common.ctx, 0, 0)
|
||||
vp := viewport.New(0, 0)
|
||||
vp.YPosition = 0
|
||||
vp.HighPerformanceRendering = config.HighPerformancePager
|
||||
|
||||
// Text input for notes/memos
|
||||
ti := textinput.New(common.ctx)
|
||||
ti.Prompt = " > "
|
||||
ti.PromptStyle = pagerNoteInputPromptStyle
|
||||
ti.TextStyle = pagerNoteInputStyle
|
||||
ti.CursorStyle = pagerNoteInputCursorStyle
|
||||
ti.CharLimit = noteCharacterLimit
|
||||
ti.Focus()
|
||||
|
||||
// Text input for search
|
||||
sp := spinner.New(common.ctx)
|
||||
sp.Style = spinnerStyle
|
||||
|
||||
return pagerModel{
|
||||
common: common,
|
||||
state: pagerStateBrowse,
|
||||
textInput: ti,
|
||||
viewport: vp,
|
||||
spinner: sp,
|
||||
m := pagerModel{
|
||||
common: common,
|
||||
state: pagerStateBrowse,
|
||||
viewport: vp,
|
||||
}
|
||||
m.initWatcher()
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *pagerModel) setSize(w, h int) {
|
||||
m.viewport.Width = w
|
||||
m.viewport.Height = h - statusBarHeight
|
||||
m.textInput.Width = w -
|
||||
ansi.PrintableRuneWidth(noteHeading.String()) -
|
||||
ansi.PrintableRuneWidth(m.textInput.Prompt) - 1
|
||||
|
||||
if m.showHelp {
|
||||
if pagerHelpHeight == 0 {
|
||||
|
|
@ -191,13 +144,18 @@ func (m *pagerModel) toggleHelp() {
|
|||
}
|
||||
}
|
||||
|
||||
type pagerStatusMessage struct {
|
||||
message string
|
||||
isError bool
|
||||
}
|
||||
|
||||
// Perform stuff that needs to happen after a successful markdown stash. Note
|
||||
// that the the returned command should be sent back the through the pager
|
||||
// that the returned command should be sent back the through the pager
|
||||
// update function.
|
||||
func (m *pagerModel) showStatusMessage(statusMessage string) tea.Cmd {
|
||||
func (m *pagerModel) showStatusMessage(msg pagerStatusMessage) tea.Cmd {
|
||||
// Show a success message to the user
|
||||
m.state = pagerStateStatusMessage
|
||||
m.statusMessage = statusMessage
|
||||
m.statusMessage = msg.message
|
||||
if m.statusMessageTimer != nil {
|
||||
m.statusMessageTimer.Stop()
|
||||
}
|
||||
|
|
@ -207,6 +165,7 @@ func (m *pagerModel) showStatusMessage(statusMessage string) tea.Cmd {
|
|||
}
|
||||
|
||||
func (m *pagerModel) unload() {
|
||||
log.Debug("unload")
|
||||
if m.showHelp {
|
||||
m.toggleHelp()
|
||||
}
|
||||
|
|
@ -216,10 +175,10 @@ func (m *pagerModel) unload() {
|
|||
m.state = pagerStateBrowse
|
||||
m.viewport.SetContent("")
|
||||
m.viewport.YOffset = 0
|
||||
m.textInput.Reset()
|
||||
m.unwatchFile()
|
||||
}
|
||||
|
||||
func (m pagerModel) update(ctx tea.Context, msg tea.Msg) (pagerModel, tea.Cmd) {
|
||||
func (m pagerModel) update(msg tea.Msg) (pagerModel, tea.Cmd) {
|
||||
var (
|
||||
cmd tea.Cmd
|
||||
cmds []tea.Cmd
|
||||
|
|
@ -227,134 +186,77 @@ func (m pagerModel) update(ctx tea.Context, msg tea.Msg) (pagerModel, tea.Cmd) {
|
|||
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch m.state {
|
||||
case pagerStateSetNote:
|
||||
switch msg.String() {
|
||||
case keyEsc:
|
||||
switch msg.String() {
|
||||
case "q", keyEsc:
|
||||
if m.state != pagerStateBrowse {
|
||||
m.state = pagerStateBrowse
|
||||
return m, nil
|
||||
case keyEnter:
|
||||
var cmd tea.Cmd
|
||||
if m.textInput.Value() != m.currentDocument.Note { // don't update if the note didn't change
|
||||
m.currentDocument.Note = m.textInput.Value() // update optimistically
|
||||
cmd = saveDocumentNote(m.common.cc, m.currentDocument.ID, m.currentDocument.Note)
|
||||
}
|
||||
m.state = pagerStateBrowse
|
||||
m.textInput.Reset()
|
||||
return m, cmd
|
||||
}
|
||||
default:
|
||||
switch msg.String() {
|
||||
case "q", keyEsc:
|
||||
if m.state != pagerStateBrowse {
|
||||
m.state = pagerStateBrowse
|
||||
return m, nil
|
||||
}
|
||||
case "home", "g":
|
||||
m.viewport.GotoTop()
|
||||
if m.viewport.HighPerformanceRendering {
|
||||
cmds = append(cmds, viewport.Sync(m.viewport))
|
||||
}
|
||||
case "end", "G":
|
||||
m.viewport.GotoBottom()
|
||||
if m.viewport.HighPerformanceRendering {
|
||||
cmds = append(cmds, viewport.Sync(m.viewport))
|
||||
}
|
||||
case "m":
|
||||
isStashed := m.currentDocument.docType == StashedDoc ||
|
||||
m.currentDocument.docType == ConvertedDoc
|
||||
|
||||
// Users can only set the note on user-stashed markdown
|
||||
if !isStashed {
|
||||
break
|
||||
}
|
||||
|
||||
m.state = pagerStateSetNote
|
||||
|
||||
// Stop the timer for hiding a status message since changing
|
||||
// the state above will have cleared it.
|
||||
if m.statusMessageTimer != nil {
|
||||
m.statusMessageTimer.Stop()
|
||||
}
|
||||
|
||||
// Pre-populate note with existing value
|
||||
if m.textInput.Value() == "" {
|
||||
m.textInput.SetValue(m.currentDocument.Note)
|
||||
m.textInput.CursorEnd()
|
||||
}
|
||||
|
||||
return m, textinput.Blink
|
||||
|
||||
case "e":
|
||||
if m.currentDocument.docType == LocalDoc {
|
||||
return m, openEditor(m.currentDocument.localPath)
|
||||
}
|
||||
|
||||
case "c":
|
||||
err := clipboard.WriteAll(m.currentDocument.Body)
|
||||
if err != nil {
|
||||
cmds = append(cmds, m.showStatusMessage("Unable to copy contents"))
|
||||
} else {
|
||||
cmds = append(cmds, m.showStatusMessage("Copied contents"))
|
||||
}
|
||||
|
||||
case "s":
|
||||
if m.common.authStatus != authOK {
|
||||
break
|
||||
}
|
||||
|
||||
md := m.currentDocument
|
||||
|
||||
_, alreadyStashed := m.common.filesStashed[md.stashID]
|
||||
if alreadyStashed {
|
||||
cmds = append(cmds, m.showStatusMessage("Already stashed"))
|
||||
break
|
||||
}
|
||||
|
||||
// Stash a local document
|
||||
if m.state != pagerStateStashing && stashableDocTypes.Contains(md.docType) {
|
||||
m.state = pagerStateStashing
|
||||
m.spinnerStart = time.Now()
|
||||
cmds = append(
|
||||
cmds,
|
||||
stashDocument(m.common.cc, md),
|
||||
m.spinner.Tick,
|
||||
)
|
||||
}
|
||||
case "?":
|
||||
m.toggleHelp()
|
||||
if m.viewport.HighPerformanceRendering {
|
||||
cmds = append(cmds, viewport.Sync(m.viewport))
|
||||
}
|
||||
case "home", "g":
|
||||
m.viewport.GotoTop()
|
||||
if m.viewport.HighPerformanceRendering {
|
||||
cmds = append(cmds, viewport.Sync(m.viewport))
|
||||
}
|
||||
case "end", "G":
|
||||
m.viewport.GotoBottom()
|
||||
if m.viewport.HighPerformanceRendering {
|
||||
cmds = append(cmds, viewport.Sync(m.viewport))
|
||||
}
|
||||
}
|
||||
|
||||
case spinner.TickMsg:
|
||||
spinnerMinTimeout := m.spinnerStart.
|
||||
Add(spinnerVisibilityTimeout).
|
||||
Add(spinnerMinLifetime)
|
||||
case "d":
|
||||
m.viewport.HalfViewDown()
|
||||
if m.viewport.HighPerformanceRendering {
|
||||
cmds = append(cmds, viewport.Sync(m.viewport))
|
||||
}
|
||||
|
||||
if m.state == pagerStateStashing || time.Now().Before(spinnerMinTimeout) {
|
||||
// We're either still stashing or we haven't reached the spinner's
|
||||
// full lifetime. In either case we need to spin the spinner
|
||||
// irrespective of it's more fine-grained visibility rules.
|
||||
var cmd tea.Cmd
|
||||
m.spinner, cmd = m.spinner.Update(ctx, msg)
|
||||
cmds = append(cmds, cmd)
|
||||
} else if m.state == pagerStateStashSuccess {
|
||||
// Successful stash. Stop spinning and update accordingly.
|
||||
m.state = pagerStateBrowse
|
||||
m.currentDocument = *m.stashedDocument
|
||||
m.stashedDocument = nil
|
||||
cmds = append(cmds, m.showStatusMessage("Stashed!"))
|
||||
case "u":
|
||||
m.viewport.HalfViewUp()
|
||||
if m.viewport.HighPerformanceRendering {
|
||||
cmds = append(cmds, viewport.Sync(m.viewport))
|
||||
}
|
||||
|
||||
case "e":
|
||||
lineno := int(math.RoundToEven(float64(m.viewport.TotalLineCount()) * m.viewport.ScrollPercent()))
|
||||
if m.viewport.AtTop() {
|
||||
lineno = 0
|
||||
}
|
||||
log.Info(
|
||||
"opening editor",
|
||||
"file", m.currentDocument.localPath,
|
||||
"line", fmt.Sprintf("%d/%d", lineno, m.viewport.TotalLineCount()),
|
||||
)
|
||||
return m, openEditor(m.currentDocument.localPath, lineno)
|
||||
|
||||
case "c":
|
||||
// Copy using OSC 52
|
||||
termenv.Copy(m.currentDocument.Body)
|
||||
// Copy using native system clipboard
|
||||
_ = clipboard.WriteAll(m.currentDocument.Body)
|
||||
cmds = append(cmds, m.showStatusMessage(pagerStatusMessage{"Copied contents", false}))
|
||||
|
||||
case "r":
|
||||
return m, loadLocalMarkdown(&m.currentDocument)
|
||||
|
||||
case "?":
|
||||
m.toggleHelp()
|
||||
if m.viewport.HighPerformanceRendering {
|
||||
cmds = append(cmds, viewport.Sync(m.viewport))
|
||||
}
|
||||
}
|
||||
|
||||
// Glow has rendered the content
|
||||
case contentRenderedMsg:
|
||||
log.Info("content rendered", "state", m.state)
|
||||
|
||||
m.setContent(string(msg))
|
||||
if m.viewport.HighPerformanceRendering {
|
||||
cmds = append(cmds, viewport.Sync(m.viewport))
|
||||
}
|
||||
cmds = append(cmds, m.watchFile)
|
||||
|
||||
// The file was changed on disk and we're reloading it
|
||||
case reloadMsg:
|
||||
return m, loadLocalMarkdown(&m.currentDocument)
|
||||
|
||||
// We've finished editing the document, potentially making changes. Let's
|
||||
// retrieve the latest version of the document so that we display
|
||||
|
|
@ -362,69 +264,27 @@ func (m pagerModel) update(ctx tea.Context, msg tea.Msg) (pagerModel, tea.Cmd) {
|
|||
case editorFinishedMsg:
|
||||
return m, loadLocalMarkdown(&m.currentDocument)
|
||||
|
||||
// We've reveived terminal dimensions, either for the first time or
|
||||
// We've received terminal dimensions, either for the first time or
|
||||
// after a resize
|
||||
case tea.WindowSizeMsg:
|
||||
return m, renderWithGlamour(m, m.currentDocument.Body)
|
||||
|
||||
case stashSuccessMsg:
|
||||
// Stashing was successful. Convert the loaded document to a stashed
|
||||
// one and show a status message. Note that we're also handling this
|
||||
// message in the main update function where we're adding this stashed
|
||||
// item to the stash listing.
|
||||
m.state = pagerStateStashSuccess
|
||||
|
||||
if !m.spinnerVisible() {
|
||||
// The spinner has finished spinning, so tell the user the stash
|
||||
// was successful.
|
||||
m.state = pagerStateBrowse
|
||||
m.currentDocument = markdown(msg)
|
||||
cmds = append(cmds, m.showStatusMessage("Stashed!"))
|
||||
} else {
|
||||
// The spinner is still spinning, so just take note of the newly
|
||||
// stashed document for now.
|
||||
md := markdown(msg)
|
||||
m.stashedDocument = &md
|
||||
}
|
||||
|
||||
case stashFailMsg:
|
||||
delete(m.common.filesStashed, msg.markdown.stashID)
|
||||
|
||||
case statusMessageTimeoutMsg:
|
||||
m.state = pagerStateBrowse
|
||||
}
|
||||
|
||||
switch m.state {
|
||||
case pagerStateSetNote:
|
||||
m.textInput, cmd = m.textInput.Update(ctx, msg)
|
||||
cmds = append(cmds, cmd)
|
||||
default:
|
||||
m.viewport, cmd = m.viewport.Update(ctx, msg)
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
m.viewport, cmd = m.viewport.Update(msg)
|
||||
cmds = append(cmds, cmd)
|
||||
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
// spinnerVisible returns whether or not the spinner should be drawn.
|
||||
func (m pagerModel) spinnerVisible() bool {
|
||||
windowStart := m.spinnerStart.Add(spinnerVisibilityTimeout)
|
||||
windowEnd := windowStart.Add(spinnerMinLifetime)
|
||||
now := time.Now()
|
||||
return now.After(windowStart) && now.Before(windowEnd)
|
||||
}
|
||||
|
||||
func (m pagerModel) View(ctx tea.Context) string {
|
||||
func (m pagerModel) View() string {
|
||||
var b strings.Builder
|
||||
fmt.Fprint(&b, m.viewport.View(ctx)+"\n")
|
||||
fmt.Fprint(&b, m.viewport.View()+"\n")
|
||||
|
||||
// Footer
|
||||
switch m.state {
|
||||
case pagerStateSetNote:
|
||||
m.setNoteView(&b)
|
||||
default:
|
||||
m.statusBarView(ctx, &b)
|
||||
}
|
||||
m.statusBarView(&b)
|
||||
|
||||
if m.showHelp {
|
||||
fmt.Fprint(&b, "\n"+m.helpView())
|
||||
|
|
@ -433,18 +293,17 @@ func (m pagerModel) View(ctx tea.Context) string {
|
|||
return b.String()
|
||||
}
|
||||
|
||||
func (m pagerModel) statusBarView(ctx tea.Context, b *strings.Builder) {
|
||||
func (m pagerModel) statusBarView(b *strings.Builder) {
|
||||
const (
|
||||
minPercent float64 = 0.0
|
||||
maxPercent float64 = 1.0
|
||||
percentToStringMagnitude float64 = 100.0
|
||||
)
|
||||
|
||||
isStashed := m.currentDocument.docType == StashedDoc || m.currentDocument.docType == ConvertedDoc
|
||||
showStatusMessage := m.state == pagerStateStatusMessage
|
||||
|
||||
// Logo
|
||||
logo := m.common.styles.LogoStyle.Render(" Glow ")
|
||||
logo := glowLogoView()
|
||||
|
||||
// Scroll percent
|
||||
percent := math.Max(minPercent, math.Min(maxPercent, m.viewport.ScrollPercent()))
|
||||
|
|
@ -463,34 +322,16 @@ func (m pagerModel) statusBarView(ctx tea.Context, b *strings.Builder) {
|
|||
helpNote = statusBarHelpStyle(" ? Help ")
|
||||
}
|
||||
|
||||
// Status indicator; spinner or stash dot
|
||||
var statusIndicator string
|
||||
if m.state == pagerStateStashing || m.state == pagerStateStashSuccess {
|
||||
var spinner string
|
||||
if m.spinnerVisible() {
|
||||
spinner = m.spinner.View(ctx)
|
||||
}
|
||||
statusIndicator = statusBarNoteStyle(" ") + spinner
|
||||
} else if isStashed && showStatusMessage {
|
||||
statusIndicator = statusBarMessageStashIconStyle(" " + pagerStashIcon)
|
||||
} else if isStashed {
|
||||
statusIndicator = statusBarStashDotStyle(" " + pagerStashIcon)
|
||||
}
|
||||
|
||||
// Note
|
||||
var note string
|
||||
if showStatusMessage {
|
||||
note = m.statusMessage
|
||||
} else {
|
||||
note = m.currentDocument.Note
|
||||
if len(note) == 0 {
|
||||
note = "(No memo)"
|
||||
}
|
||||
}
|
||||
note = truncate.StringWithTail(" "+note+" ", uint(max(0,
|
||||
note = truncate.StringWithTail(" "+note+" ", uint(max(0, //nolint:gosec
|
||||
m.common.width-
|
||||
ansi.PrintableRuneWidth(logo)-
|
||||
ansi.PrintableRuneWidth(statusIndicator)-
|
||||
ansi.PrintableRuneWidth(scrollPercent)-
|
||||
ansi.PrintableRuneWidth(helpNote),
|
||||
)), ellipsis)
|
||||
|
|
@ -504,7 +345,6 @@ func (m pagerModel) statusBarView(ctx tea.Context, b *strings.Builder) {
|
|||
padding := max(0,
|
||||
m.common.width-
|
||||
ansi.PrintableRuneWidth(logo)-
|
||||
ansi.PrintableRuneWidth(statusIndicator)-
|
||||
ansi.PrintableRuneWidth(note)-
|
||||
ansi.PrintableRuneWidth(scrollPercent)-
|
||||
ansi.PrintableRuneWidth(helpNote),
|
||||
|
|
@ -516,9 +356,8 @@ func (m pagerModel) statusBarView(ctx tea.Context, b *strings.Builder) {
|
|||
emptySpace = statusBarNoteStyle(emptySpace)
|
||||
}
|
||||
|
||||
fmt.Fprintf(b, "%s%s%s%s%s%s",
|
||||
fmt.Fprintf(b, "%s%s%s%s%s",
|
||||
logo,
|
||||
statusIndicator,
|
||||
note,
|
||||
emptySpace,
|
||||
scrollPercent,
|
||||
|
|
@ -526,36 +365,17 @@ func (m pagerModel) statusBarView(ctx tea.Context, b *strings.Builder) {
|
|||
)
|
||||
}
|
||||
|
||||
func (m pagerModel) setNoteView(b *strings.Builder) {
|
||||
fmt.Fprint(b, noteHeading)
|
||||
fmt.Fprint(b, m.textInput.View(m.common.ctx))
|
||||
}
|
||||
|
||||
func (m pagerModel) helpView() (s string) {
|
||||
memoOrStash := "m set memo"
|
||||
if m.common.authStatus == authOK && m.currentDocument.docType == LocalDoc {
|
||||
memoOrStash = "s stash this document"
|
||||
}
|
||||
|
||||
editOrBlank := "e edit this document"
|
||||
if m.currentDocument.docType != LocalDoc || m.currentDocument.localPath == "" {
|
||||
editOrBlank = ""
|
||||
}
|
||||
|
||||
col1 := []string{
|
||||
"g/home go to top",
|
||||
"G/end go to bottom",
|
||||
"c copy contents",
|
||||
editOrBlank,
|
||||
memoOrStash,
|
||||
"e edit this document",
|
||||
"r reload this document",
|
||||
"esc back to files",
|
||||
"q quit",
|
||||
}
|
||||
|
||||
if m.currentDocument.docType == NewsDoc {
|
||||
deleteFromStringSlice(col1, 3)
|
||||
}
|
||||
|
||||
s += "\n"
|
||||
s += "k/↑ up " + col1[0] + "\n"
|
||||
s += "j/↓ down " + col1[1] + "\n"
|
||||
|
|
@ -591,9 +411,7 @@ func renderWithGlamour(m pagerModel, md string) tea.Cmd {
|
|||
return func() tea.Msg {
|
||||
s, err := glamourRender(m, md)
|
||||
if err != nil {
|
||||
if debug {
|
||||
log.Println("error rendering with Glamour:", err)
|
||||
}
|
||||
log.Error("error rendering with Glamour", "error", err)
|
||||
return errMsg{err}
|
||||
}
|
||||
return contentRenderedMsg(s)
|
||||
|
|
@ -602,53 +420,116 @@ func renderWithGlamour(m pagerModel, md string) tea.Cmd {
|
|||
|
||||
// This is where the magic happens.
|
||||
func glamourRender(m pagerModel, markdown string) (string, error) {
|
||||
trunc := lipgloss.NewStyle().MaxWidth(m.viewport.Width - lineNumberWidth).Render
|
||||
|
||||
if !config.GlamourEnabled {
|
||||
return markdown, nil
|
||||
}
|
||||
|
||||
// initialize glamour
|
||||
var gs glamour.TermRendererOption
|
||||
if m.common.cfg.GlamourStyle == "auto" {
|
||||
gs = glamour.WithAutoStyle()
|
||||
} else {
|
||||
gs = glamour.WithStylePath(m.common.cfg.GlamourStyle)
|
||||
isCode := !utils.IsMarkdownFile(m.currentDocument.Note)
|
||||
width := max(0, min(int(m.common.cfg.GlamourMaxWidth), m.viewport.Width)) //nolint:gosec
|
||||
if isCode {
|
||||
width = 0
|
||||
}
|
||||
|
||||
width := max(0, min(int(m.common.cfg.GlamourMaxWidth), m.viewport.Width))
|
||||
r, err := glamour.NewTermRenderer(
|
||||
gs,
|
||||
options := []glamour.TermRendererOption{
|
||||
utils.GlamourStyle(m.common.cfg.GlamourStyle, isCode),
|
||||
glamour.WithWordWrap(width),
|
||||
)
|
||||
}
|
||||
|
||||
if m.common.cfg.PreserveNewLines {
|
||||
options = append(options, glamour.WithPreservedNewLines())
|
||||
}
|
||||
r, err := glamour.NewTermRenderer(options...)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", fmt.Errorf("error creating glamour renderer: %w", err)
|
||||
}
|
||||
|
||||
if isCode {
|
||||
markdown = utils.WrapCodeBlock(markdown, filepath.Ext(m.currentDocument.Note))
|
||||
}
|
||||
|
||||
out, err := r.Render(markdown)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", fmt.Errorf("error rendering markdown: %w", err)
|
||||
}
|
||||
|
||||
if isCode {
|
||||
out = strings.TrimSpace(out)
|
||||
}
|
||||
|
||||
// trim lines
|
||||
lines := strings.Split(out, "\n")
|
||||
|
||||
var content string
|
||||
var content strings.Builder
|
||||
for i, s := range lines {
|
||||
content += strings.TrimSpace(s)
|
||||
if isCode || m.common.cfg.ShowLineNumbers {
|
||||
content.WriteString(lineNumberStyle(fmt.Sprintf("%"+fmt.Sprint(lineNumberWidth)+"d", i+1)))
|
||||
content.WriteString(trunc(s))
|
||||
} else {
|
||||
content.WriteString(s)
|
||||
}
|
||||
|
||||
// don't add an artificial newline after the last split
|
||||
if i+1 < len(lines) {
|
||||
content += "\n"
|
||||
content.WriteRune('\n')
|
||||
}
|
||||
}
|
||||
|
||||
return content, nil
|
||||
return content.String(), nil
|
||||
}
|
||||
|
||||
// ETC
|
||||
|
||||
// Note: this runs in linear time; O(n).
|
||||
func deleteFromStringSlice(a []string, i int) []string {
|
||||
copy(a[i:], a[i+1:])
|
||||
a[len(a)-1] = ""
|
||||
return a[:len(a)-1]
|
||||
func (m *pagerModel) initWatcher() {
|
||||
var err error
|
||||
m.watcher, err = fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
log.Error("error creating fsnotify watcher", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *pagerModel) watchFile() tea.Msg {
|
||||
dir := m.localDir()
|
||||
|
||||
if err := m.watcher.Add(dir); err != nil {
|
||||
log.Error("error adding dir to fsnotify watcher", "error", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Info("fsnotify watching dir", "dir", dir)
|
||||
|
||||
for {
|
||||
select {
|
||||
case event, ok := <-m.watcher.Events:
|
||||
if !ok || event.Name != m.currentDocument.localPath {
|
||||
continue
|
||||
}
|
||||
|
||||
if !event.Has(fsnotify.Write) && !event.Has(fsnotify.Create) {
|
||||
continue
|
||||
}
|
||||
|
||||
log.Debug("fsnotify event", "file", event.Name, "event", event.Op)
|
||||
return reloadMsg{}
|
||||
case err, ok := <-m.watcher.Errors:
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
log.Debug("fsnotify error", "dir", dir, "error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *pagerModel) unwatchFile() {
|
||||
dir := m.localDir()
|
||||
|
||||
err := m.watcher.Remove(dir)
|
||||
if err == nil {
|
||||
log.Debug("fsnotify dir unwatched", "dir", dir)
|
||||
} else {
|
||||
log.Error("fsnotify fail to unwatch dir", "dir", dir, "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *pagerModel) localDir() string {
|
||||
return filepath.Dir(m.currentDocument.localPath)
|
||||
}
|
||||
|
|
|
|||
12
ui/sort.go
Normal file
12
ui/sort.go
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"slices"
|
||||
)
|
||||
|
||||
func sortMarkdowns(mds []*markdown) {
|
||||
slices.SortStableFunc(mds, func(a, b *markdown) int {
|
||||
return cmp.Compare(a.Note, b.Note)
|
||||
})
|
||||
}
|
||||
829
ui/stash.go
829
ui/stash.go
File diff suppressed because it is too large
Load diff
|
|
@ -30,7 +30,7 @@ func newHelpColumn(pairs ...string) (h helpColumn) {
|
|||
}
|
||||
|
||||
// render returns styled and formatted rows from keys and values.
|
||||
func (h helpColumn) render(st *styles, height int) (rows []string) {
|
||||
func (h helpColumn) render(height int) (rows []string) {
|
||||
keyWidth, valWidth := h.maxWidths()
|
||||
|
||||
for i := 0; i < height; i++ {
|
||||
|
|
@ -44,11 +44,11 @@ func (h helpColumn) render(st *styles, height int) (rows []string) {
|
|||
|
||||
switch k {
|
||||
case "s":
|
||||
k = st.GreenFg(k)
|
||||
v = st.SemiDimGreenFg(v)
|
||||
k = greenFg(k)
|
||||
v = semiDimGreenFg(v)
|
||||
default:
|
||||
k = st.GrayFg(k)
|
||||
v = st.MidGrayFg(v)
|
||||
k = grayFg(k)
|
||||
v = midGrayFg(v)
|
||||
}
|
||||
}
|
||||
b.WriteString(k)
|
||||
|
|
@ -99,18 +99,7 @@ func (m stashModel) helpView() (string, int) {
|
|||
return m.renderHelp(h)
|
||||
}
|
||||
|
||||
// Help for when we're interacting with a single document
|
||||
switch m.selectionState {
|
||||
case selectionSettingNote:
|
||||
return m.renderHelp([]string{"enter", "confirm", "esc", "cancel"}, []string{"q", "quit"})
|
||||
case selectionPromptingDelete:
|
||||
return m.renderHelp([]string{"y", "delete", "n", "cancel"}, []string{"q", "quit"})
|
||||
}
|
||||
|
||||
var (
|
||||
isStashed bool
|
||||
isStashable bool
|
||||
isEditable bool
|
||||
navHelp []string
|
||||
filterHelp []string
|
||||
selectionHelp []string
|
||||
|
|
@ -119,13 +108,6 @@ func (m stashModel) helpView() (string, int) {
|
|||
appHelp []string
|
||||
)
|
||||
|
||||
if numDocs > 0 {
|
||||
md := m.selectedMarkdown()
|
||||
isStashed = md != nil && md.docType == StashedDoc
|
||||
isStashable = md != nil && md.docType == LocalDoc && m.online()
|
||||
isEditable = md != nil && md.docType == LocalDoc && md.localPath != ""
|
||||
}
|
||||
|
||||
if numDocs > 0 && m.showFullHelp {
|
||||
navHelp = []string{"enter", "open", "j/k ↑/↓", "choose"}
|
||||
}
|
||||
|
|
@ -143,27 +125,23 @@ func (m stashModel) helpView() (string, int) {
|
|||
}
|
||||
|
||||
// If we're browsing a filtered set
|
||||
if m.filterState == filterApplied {
|
||||
filterHelp = []string{"/", "edit search", "esc", "clear search"}
|
||||
if m.filterApplied() {
|
||||
filterHelp = []string{"/", "edit search", "esc", "clear filter"}
|
||||
} else {
|
||||
filterHelp = []string{"/", "find"}
|
||||
}
|
||||
|
||||
if isStashed {
|
||||
selectionHelp = []string{"x", "delete", "m", "set memo"}
|
||||
} else if isStashable {
|
||||
selectionHelp = []string{"s", "stash"}
|
||||
}
|
||||
|
||||
if isEditable {
|
||||
editHelp = []string{"e", "edit"}
|
||||
}
|
||||
|
||||
// If there are errors
|
||||
if m.err != nil {
|
||||
appHelp = append(appHelp, "!", "errors")
|
||||
}
|
||||
|
||||
appHelp = append(appHelp, "r", "refresh")
|
||||
|
||||
if numDocs > 0 {
|
||||
appHelp = append(appHelp, "e", "edit")
|
||||
}
|
||||
|
||||
appHelp = append(appHelp, "q", "quit")
|
||||
|
||||
// Detailed help
|
||||
|
|
@ -181,13 +159,15 @@ func (m stashModel) helpView() (string, int) {
|
|||
return m.renderHelp(navHelp, filterHelp, selectionHelp, editHelp, sectionHelp, appHelp)
|
||||
}
|
||||
|
||||
const minHelpViewHeight = 5
|
||||
|
||||
// renderHelp returns the rendered help view and associated line height for
|
||||
// the given groups of help items.
|
||||
func (m stashModel) renderHelp(groups ...[]string) (string, int) {
|
||||
if m.showFullHelp {
|
||||
str := m.fullHelpView(groups...)
|
||||
numLines := strings.Count(str, "\n") + 1
|
||||
return str, numLines
|
||||
return str, max(numLines, minHelpViewHeight)
|
||||
}
|
||||
return m.miniHelpView(concatStringSlices(groups...)...), 1
|
||||
}
|
||||
|
|
@ -196,13 +176,12 @@ func (m stashModel) renderHelp(groups ...[]string) (string, int) {
|
|||
// would otherwise wrap to two lines. Help view entries should come in as pairs,
|
||||
// with the first being the key and the second being the help text.
|
||||
func (m stashModel) miniHelpView(entries ...string) string {
|
||||
st := &m.common.styles
|
||||
if len(entries) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
var (
|
||||
truncationChar = st.SubtleStyle.Render("…")
|
||||
truncationChar = subtleStyle.Render("…")
|
||||
truncationWidth = ansi.PrintableRuneWidth(truncationChar)
|
||||
)
|
||||
|
||||
|
|
@ -220,19 +199,13 @@ func (m stashModel) miniHelpView(entries ...string) string {
|
|||
k := entries[i]
|
||||
v := entries[i+1]
|
||||
|
||||
switch k {
|
||||
case "s":
|
||||
k = st.GreenFg(k)
|
||||
v = st.SemiDimGreenFg(v)
|
||||
default:
|
||||
k = st.GrayFg(k)
|
||||
v = st.MidGrayFg(v)
|
||||
}
|
||||
k = grayFg(k)
|
||||
v = midGrayFg(v)
|
||||
|
||||
next = fmt.Sprintf("%s %s", k, v)
|
||||
|
||||
if i < len(entries)-2 {
|
||||
next += st.DividerDot
|
||||
next += dividerDot.String()
|
||||
}
|
||||
|
||||
// Only this (and the following) help text items if we have the
|
||||
|
|
@ -270,7 +243,7 @@ func (m stashModel) fullHelpView(groups ...[]string) string {
|
|||
|
||||
// Build columns
|
||||
for _, c := range columns {
|
||||
renderedCols = append(renderedCols, c.render(&m.common.styles, tallestCol))
|
||||
renderedCols = append(renderedCols, c.render(tallestCol))
|
||||
}
|
||||
|
||||
// Merge columns
|
||||
|
|
|
|||
143
ui/stashitem.go
143
ui/stashitem.go
|
|
@ -2,138 +2,99 @@ package ui
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/muesli/reflow/ansi"
|
||||
"github.com/charmbracelet/log"
|
||||
"github.com/muesli/reflow/truncate"
|
||||
"github.com/sahilm/fuzzy"
|
||||
)
|
||||
|
||||
const (
|
||||
verticalLine = "│"
|
||||
noMemoTitle = "No Memo"
|
||||
fileListingStashIcon = "• "
|
||||
)
|
||||
|
||||
func stashItemView(b *strings.Builder, m stashModel, index int, md *markdown) {
|
||||
var (
|
||||
truncateTo = uint(m.common.width - stashViewHorizontalPadding*2)
|
||||
gutter string
|
||||
title = md.Note
|
||||
date = md.relativeTime()
|
||||
icon = ""
|
||||
truncateTo = uint(m.common.width - stashViewHorizontalPadding*2) //nolint:gosec
|
||||
gutter string
|
||||
title = truncate.StringWithTail(md.Note, truncateTo, ellipsis)
|
||||
date = md.relativeTime()
|
||||
editedBy = ""
|
||||
hasEditedBy = false
|
||||
icon = ""
|
||||
separator = ""
|
||||
)
|
||||
|
||||
switch md.docType {
|
||||
case NewsDoc:
|
||||
if title == "" {
|
||||
title = "News"
|
||||
} else {
|
||||
title = truncate.StringWithTail(title, truncateTo, ellipsis)
|
||||
}
|
||||
case StashedDoc, ConvertedDoc:
|
||||
icon = fileListingStashIcon
|
||||
if title == "" {
|
||||
title = noMemoTitle
|
||||
}
|
||||
title = truncate.StringWithTail(title, truncateTo-uint(ansi.PrintableRuneWidth(icon)), ellipsis)
|
||||
default:
|
||||
title = truncate.StringWithTail(title, truncateTo, ellipsis)
|
||||
}
|
||||
|
||||
isSelected := index == m.cursor()
|
||||
isFiltering := m.filterState == filtering
|
||||
singleFilteredItem := isFiltering && len(m.getVisibleMarkdowns()) == 1
|
||||
styles := &m.common.styles
|
||||
|
||||
// If there are multiple items being filtered don't highlight a selected
|
||||
// item in the results. If we've filtered down to one item, however,
|
||||
// highlight that first item since pressing return will open it.
|
||||
if isSelected && !isFiltering || singleFilteredItem {
|
||||
if isSelected && !isFiltering || singleFilteredItem { //nolint:nestif
|
||||
// Selected item
|
||||
|
||||
switch m.selectionState {
|
||||
case selectionPromptingDelete:
|
||||
gutter = styles.FaintRedFg(verticalLine)
|
||||
icon = styles.FaintRedFg(icon)
|
||||
title = styles.RedFg(title)
|
||||
date = styles.FaintRedFg(date)
|
||||
case selectionSettingNote:
|
||||
gutter = styles.DullYellowFg(verticalLine)
|
||||
icon = ""
|
||||
title = m.noteInput.View(m.common.ctx)
|
||||
date = styles.DullYellowFg(date)
|
||||
default:
|
||||
if m.common.latestFileStashed == md.stashID &&
|
||||
m.statusMessage == stashingStatusMessage {
|
||||
gutter = styles.GreenFg(verticalLine)
|
||||
icon = styles.DimGreenFg(icon)
|
||||
title = styles.GreenFg(title)
|
||||
date = styles.SemiDimGreenFg(date)
|
||||
if m.statusMessage == stashingStatusMessage {
|
||||
gutter = greenFg(verticalLine)
|
||||
icon = dimGreenFg(icon)
|
||||
title = greenFg(title)
|
||||
date = semiDimGreenFg(date)
|
||||
editedBy = semiDimGreenFg(editedBy)
|
||||
separator = semiDimGreenFg(separator)
|
||||
} else {
|
||||
gutter = dullFuchsiaFg(verticalLine)
|
||||
if m.currentSection().key == filterSection &&
|
||||
m.filterState == filterApplied || singleFilteredItem {
|
||||
s := lipgloss.NewStyle().Foreground(fuchsia)
|
||||
title = styleFilteredText(title, m.filterInput.Value(), s, s.Underline(true))
|
||||
} else {
|
||||
gutter = styles.DullFuchsiaFg(verticalLine)
|
||||
icon = styles.DullFuchsiaFg(icon)
|
||||
if m.currentSection().key == filterSection &&
|
||||
m.filterState == filterApplied || singleFilteredItem {
|
||||
s := lipgloss.NewStyle().Foreground(fuchsia)
|
||||
title = styleFilteredText(title, m.filterInput.Value(), s, s.Copy().Underline(true))
|
||||
} else {
|
||||
title = styles.FuchsiaFg(title)
|
||||
}
|
||||
date = styles.DullFuchsiaFg(date)
|
||||
title = fuchsiaFg(title)
|
||||
icon = fuchsiaFg(icon)
|
||||
}
|
||||
date = dimFuchsiaFg(date)
|
||||
editedBy = dimDullFuchsiaFg(editedBy)
|
||||
separator = dullFuchsiaFg(separator)
|
||||
}
|
||||
} else {
|
||||
// Regular (non-selected) items
|
||||
|
||||
gutter = " "
|
||||
|
||||
if m.common.latestFileStashed == md.stashID &&
|
||||
m.statusMessage == stashingStatusMessage {
|
||||
icon = styles.DimGreenFg(icon)
|
||||
title = styles.GreenFg(title)
|
||||
date = styles.SemiDimGreenFg(date)
|
||||
} else if md.docType == NewsDoc {
|
||||
if isFiltering && m.filterInput.Value() == "" {
|
||||
title = styles.DimIndigoFg(title)
|
||||
date = styles.DimSubtleIndigoFg(date)
|
||||
} else {
|
||||
s := lipgloss.NewStyle().Foreground(indigo)
|
||||
title = styleFilteredText(title, m.filterInput.Value(), s, s.Copy().Underline(true))
|
||||
date = styles.SubtleIndigoFg(date)
|
||||
}
|
||||
if m.statusMessage == stashingStatusMessage {
|
||||
icon = dimGreenFg(icon)
|
||||
title = greenFg(title)
|
||||
date = semiDimGreenFg(date)
|
||||
editedBy = semiDimGreenFg(editedBy)
|
||||
separator = semiDimGreenFg(separator)
|
||||
} else if isFiltering && m.filterInput.Value() == "" {
|
||||
icon = styles.DimGreenFg(icon)
|
||||
if title == noMemoTitle {
|
||||
title = styles.DimBrightGrayFg(title)
|
||||
} else {
|
||||
title = styles.DimNormalFg(title)
|
||||
}
|
||||
date = styles.DimBrightGrayFg(date)
|
||||
icon = dimGreenFg(icon)
|
||||
title = dimNormalFg(title)
|
||||
date = dimBrightGrayFg(date)
|
||||
editedBy = dimBrightGrayFg(editedBy)
|
||||
separator = dimBrightGrayFg(separator)
|
||||
} else {
|
||||
icon = styles.GreenFg(icon)
|
||||
if title == noMemoTitle {
|
||||
title = styles.BrightGrayFg(title)
|
||||
} else {
|
||||
s := lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#1a1a1a", Dark: "#dddddd"})
|
||||
title = styleFilteredText(title, m.filterInput.Value(), s, s.Copy().Underline(true))
|
||||
}
|
||||
date = styles.BrightGrayFg(date)
|
||||
icon = greenFg(icon)
|
||||
|
||||
s := lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#1a1a1a", Dark: "#dddddd"})
|
||||
title = styleFilteredText(title, m.filterInput.Value(), s, s.Underline(true))
|
||||
date = grayFg(date)
|
||||
editedBy = midGrayFg(editedBy)
|
||||
separator = brightGrayFg(separator)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(b, "%s %s%s\n", gutter, icon, title)
|
||||
fmt.Fprintf(b, "%s %s%s%s%s\n", gutter, icon, separator, separator, title)
|
||||
fmt.Fprintf(b, "%s %s", gutter, date)
|
||||
if hasEditedBy {
|
||||
fmt.Fprintf(b, " %s", editedBy)
|
||||
}
|
||||
}
|
||||
|
||||
func styleFilteredText(haystack, needles string, defaultStyle, matchedStyle lipgloss.Style) string {
|
||||
b := strings.Builder{}
|
||||
|
||||
normalizedHay, err := normalize(haystack)
|
||||
if err != nil && debug {
|
||||
log.Printf("error normalizing '%s': %v", haystack, err)
|
||||
if err != nil {
|
||||
log.Error("error normalizing", "haystack", haystack, "error", err)
|
||||
}
|
||||
|
||||
matches := fuzzy.Find(needles, []string{normalizedHay})
|
||||
|
|
|
|||
188
ui/styles.go
188
ui/styles.go
|
|
@ -1,158 +1,46 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
. "github.com/charmbracelet/lipgloss"
|
||||
) //nolint: revive
|
||||
import "github.com/charmbracelet/lipgloss"
|
||||
|
||||
// Colors.
|
||||
var (
|
||||
normal = AdaptiveColor{Light: "#1A1A1A", Dark: "#dddddd"}
|
||||
normalDim = AdaptiveColor{Light: "#A49FA5", Dark: "#777777"}
|
||||
gray = AdaptiveColor{Light: "#909090", Dark: "#626262"}
|
||||
midGray = AdaptiveColor{Light: "#B2B2B2", Dark: "#4A4A4A"}
|
||||
darkGray = AdaptiveColor{Light: "#DDDADA", Dark: "#3C3C3C"}
|
||||
brightGray = AdaptiveColor{Light: "#847A85", Dark: "#979797"}
|
||||
dimBrightGray = AdaptiveColor{Light: "#C2B8C2", Dark: "#4D4D4D"}
|
||||
indigo = AdaptiveColor{Light: "#5A56E0", Dark: "#7571F9"}
|
||||
dimIndigo = AdaptiveColor{Light: "#9498FF", Dark: "#494690"}
|
||||
subtleIndigo = AdaptiveColor{Light: "#7D79F6", Dark: "#514DC1"}
|
||||
dimSubtleIndigo = AdaptiveColor{Light: "#BBBDFF", Dark: "#383584"}
|
||||
cream = AdaptiveColor{Light: "#FFFDF5", Dark: "#FFFDF5"}
|
||||
yellowGreen = AdaptiveColor{Light: "#04B575", Dark: "#ECFD65"}
|
||||
dullYellowGreen = AdaptiveColor{Light: "#6BCB94", Dark: "#9BA92F"}
|
||||
fuchsia = AdaptiveColor{Light: "#EE6FF8", Dark: "#EE6FF8"}
|
||||
dimFuchsia = AdaptiveColor{Light: "#F1A8FF", Dark: "#99519E"}
|
||||
dullFuchsia = AdaptiveColor{Dark: "#AD58B4", Light: "#F793FF"}
|
||||
dimDullFuchsia = AdaptiveColor{Light: "#F6C9FF", Dark: "#6B3A6F"}
|
||||
green = Color("#04B575")
|
||||
red = AdaptiveColor{Light: "#FF4672", Dark: "#ED567A"}
|
||||
faintRed = AdaptiveColor{Light: "#FF6F91", Dark: "#C74665"}
|
||||
|
||||
semiDimGreen = AdaptiveColor{Light: "#35D79C", Dark: "#036B46"}
|
||||
dimGreen = AdaptiveColor{Light: "#72D2B0", Dark: "#0B5137"}
|
||||
normalDim = lipgloss.AdaptiveColor{Light: "#A49FA5", Dark: "#777777"}
|
||||
gray = lipgloss.AdaptiveColor{Light: "#909090", Dark: "#626262"}
|
||||
midGray = lipgloss.AdaptiveColor{Light: "#B2B2B2", Dark: "#4A4A4A"}
|
||||
darkGray = lipgloss.AdaptiveColor{Light: "#DDDADA", Dark: "#3C3C3C"}
|
||||
brightGray = lipgloss.AdaptiveColor{Light: "#847A85", Dark: "#979797"}
|
||||
dimBrightGray = lipgloss.AdaptiveColor{Light: "#C2B8C2", Dark: "#4D4D4D"}
|
||||
cream = lipgloss.AdaptiveColor{Light: "#FFFDF5", Dark: "#FFFDF5"}
|
||||
yellowGreen = lipgloss.AdaptiveColor{Light: "#04B575", Dark: "#ECFD65"}
|
||||
fuchsia = lipgloss.AdaptiveColor{Light: "#EE6FF8", Dark: "#EE6FF8"}
|
||||
dimFuchsia = lipgloss.AdaptiveColor{Light: "#F1A8FF", Dark: "#99519E"}
|
||||
dullFuchsia = lipgloss.AdaptiveColor{Dark: "#AD58B4", Light: "#F793FF"}
|
||||
dimDullFuchsia = lipgloss.AdaptiveColor{Light: "#F6C9FF", Dark: "#7B4380"}
|
||||
green = lipgloss.Color("#04B575")
|
||||
red = lipgloss.AdaptiveColor{Light: "#FF4672", Dark: "#ED567A"}
|
||||
semiDimGreen = lipgloss.AdaptiveColor{Light: "#35D79C", Dark: "#036B46"}
|
||||
dimGreen = lipgloss.AdaptiveColor{Light: "#72D2B0", Dark: "#0B5137"}
|
||||
)
|
||||
|
||||
type styler func(...string) string
|
||||
|
||||
// Ulimately, we'll transition to named styles.
|
||||
// nolint:deadcode,unused,varcheck
|
||||
type styles struct {
|
||||
NormalFg styler
|
||||
DimNormalFg styler
|
||||
|
||||
BrightGrayFg styler
|
||||
DimBrightGrayFg styler
|
||||
|
||||
GrayFg styler
|
||||
MidGrayFg styler
|
||||
DarkGrayFg styler
|
||||
|
||||
GreenFg styler
|
||||
SemiDimGreenFg styler
|
||||
DimGreenFg styler
|
||||
|
||||
FuchsiaFg styler
|
||||
DimFuchsiaFg styler
|
||||
|
||||
DullFuchsiaFg styler
|
||||
DimDullFuchsiaFg styler
|
||||
|
||||
IndigoFg styler
|
||||
DimIndigoFg styler
|
||||
|
||||
SubtleIndigoFg styler
|
||||
DimSubtleIndigoFg styler
|
||||
|
||||
YellowFg styler
|
||||
DullYellowFg styler
|
||||
RedFg styler
|
||||
FaintRedFg styler
|
||||
|
||||
TabStyle Style
|
||||
SelectedTabStyle Style
|
||||
ErrorTitleStyle Style
|
||||
SubtleStyle Style
|
||||
PaginationStyle Style
|
||||
|
||||
LogoStyle Style
|
||||
StashSpinnerStyle Style
|
||||
StashInputPromptStyle Style
|
||||
StashInputCursorStyle Style
|
||||
|
||||
DividerDot string
|
||||
DividerBar string
|
||||
OfflineHeaderNote string
|
||||
}
|
||||
|
||||
func defaultStyles(ctx tea.Context) styles {
|
||||
darkGrayFg := ctx.NewStyle().Foreground(darkGray).Render
|
||||
subtleStyle := ctx.NewStyle().
|
||||
Foreground(AdaptiveColor{Light: "#9B9B9B", Dark: "#5C5C5C"})
|
||||
|
||||
return styles{
|
||||
NormalFg: ctx.NewStyle().Foreground(normal).Render,
|
||||
DimNormalFg: ctx.NewStyle().Foreground(normalDim).Render,
|
||||
|
||||
BrightGrayFg: ctx.NewStyle().Foreground(brightGray).Render,
|
||||
DimBrightGrayFg: ctx.NewStyle().Foreground(dimBrightGray).Render,
|
||||
|
||||
GrayFg: ctx.NewStyle().Foreground(gray).Render,
|
||||
MidGrayFg: ctx.NewStyle().Foreground(midGray).Render,
|
||||
DarkGrayFg: darkGrayFg,
|
||||
|
||||
GreenFg: ctx.NewStyle().Foreground(green).Render,
|
||||
SemiDimGreenFg: ctx.NewStyle().Foreground(semiDimGreen).Render,
|
||||
DimGreenFg: ctx.NewStyle().Foreground(dimGreen).Render,
|
||||
|
||||
FuchsiaFg: ctx.NewStyle().Foreground(fuchsia).Render,
|
||||
DimFuchsiaFg: ctx.NewStyle().Foreground(dimFuchsia).Render,
|
||||
|
||||
DullFuchsiaFg: ctx.NewStyle().Foreground(dullFuchsia).Render,
|
||||
DimDullFuchsiaFg: ctx.NewStyle().Foreground(dimDullFuchsia).Render,
|
||||
|
||||
IndigoFg: ctx.NewStyle().Foreground(fuchsia).Render,
|
||||
DimIndigoFg: ctx.NewStyle().Foreground(dimIndigo).Render,
|
||||
|
||||
SubtleIndigoFg: ctx.NewStyle().Foreground(subtleIndigo).Render,
|
||||
DimSubtleIndigoFg: ctx.NewStyle().Foreground(dimSubtleIndigo).Render,
|
||||
|
||||
YellowFg: ctx.NewStyle().Foreground(yellowGreen).Render, // renders light green on light backgrounds
|
||||
DullYellowFg: ctx.NewStyle().Foreground(dullYellowGreen).Render, // renders light green on light backgrounds
|
||||
RedFg: ctx.NewStyle().Foreground(red).Render,
|
||||
FaintRedFg: ctx.NewStyle().Foreground(faintRed).Render,
|
||||
TabStyle: ctx.NewStyle().
|
||||
Foreground(AdaptiveColor{Light: "#909090", Dark: "#626262"}),
|
||||
|
||||
SelectedTabStyle: ctx.NewStyle().
|
||||
Foreground(AdaptiveColor{Light: "#333333", Dark: "#979797"}),
|
||||
|
||||
ErrorTitleStyle: ctx.NewStyle().
|
||||
Foreground(cream).
|
||||
Background(red).
|
||||
Padding(0, 1),
|
||||
|
||||
SubtleStyle: subtleStyle,
|
||||
|
||||
PaginationStyle: subtleStyle.Copy(),
|
||||
|
||||
LogoStyle: ctx.NewStyle().
|
||||
Foreground(lipgloss.Color("#ECFD65")).
|
||||
Background(fuchsia).
|
||||
Bold(true),
|
||||
|
||||
StashSpinnerStyle: ctx.NewStyle().
|
||||
Foreground(gray),
|
||||
StashInputPromptStyle: ctx.NewStyle().
|
||||
Foreground(yellowGreen).
|
||||
MarginRight(1),
|
||||
StashInputCursorStyle: ctx.NewStyle().
|
||||
Foreground(fuchsia).
|
||||
MarginRight(1),
|
||||
|
||||
DividerDot: darkGrayFg(" • "),
|
||||
DividerBar: darkGrayFg(" │ "),
|
||||
OfflineHeaderNote: darkGrayFg("(Offline)"),
|
||||
}
|
||||
}
|
||||
var (
|
||||
dimNormalFg = lipgloss.NewStyle().Foreground(normalDim).Render
|
||||
brightGrayFg = lipgloss.NewStyle().Foreground(brightGray).Render
|
||||
dimBrightGrayFg = lipgloss.NewStyle().Foreground(dimBrightGray).Render
|
||||
grayFg = lipgloss.NewStyle().Foreground(gray).Render
|
||||
midGrayFg = lipgloss.NewStyle().Foreground(midGray).Render
|
||||
darkGrayFg = lipgloss.NewStyle().Foreground(darkGray)
|
||||
greenFg = lipgloss.NewStyle().Foreground(green).Render
|
||||
semiDimGreenFg = lipgloss.NewStyle().Foreground(semiDimGreen).Render
|
||||
dimGreenFg = lipgloss.NewStyle().Foreground(dimGreen).Render
|
||||
fuchsiaFg = lipgloss.NewStyle().Foreground(fuchsia).Render
|
||||
dimFuchsiaFg = lipgloss.NewStyle().Foreground(dimFuchsia).Render
|
||||
dullFuchsiaFg = lipgloss.NewStyle().Foreground(dullFuchsia).Render
|
||||
dimDullFuchsiaFg = lipgloss.NewStyle().Foreground(dimDullFuchsia).Render
|
||||
redFg = lipgloss.NewStyle().Foreground(red).Render
|
||||
tabStyle = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#909090", Dark: "#626262"})
|
||||
selectedTabStyle = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#333333", Dark: "#979797"})
|
||||
errorTitleStyle = lipgloss.NewStyle().Foreground(cream).Background(red).Padding(0, 1)
|
||||
subtleStyle = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#9B9B9B", Dark: "#5C5C5C"})
|
||||
paginationStyle = subtleStyle
|
||||
)
|
||||
|
|
|
|||
607
ui/ui.go
607
ui/ui.go
|
|
@ -1,32 +1,24 @@
|
|||
// Package ui provides the main UI for the glow application.
|
||||
package ui
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/charm"
|
||||
"github.com/charmbracelet/charm/keygen"
|
||||
"github.com/charmbracelet/glow/utils"
|
||||
"github.com/charmbracelet/glamour/styles"
|
||||
"github.com/charmbracelet/glow/v2/utils"
|
||||
"github.com/charmbracelet/log"
|
||||
"github.com/muesli/gitcha"
|
||||
"github.com/segmentio/ksuid"
|
||||
te "github.com/muesli/termenv"
|
||||
)
|
||||
|
||||
const (
|
||||
noteCharacterLimit = 256 // should match server
|
||||
statusMessageTimeout = time.Second * 2 // how long to show status messages like "stashed!"
|
||||
statusMessageTimeout = time.Second * 3 // how long to show status messages like "stashed!"
|
||||
ellipsis = "…"
|
||||
|
||||
// Only show the spinner if it spins for at least this amount of time.
|
||||
spinnerVisibilityTimeout = time.Millisecond * 140
|
||||
|
||||
// Minimum amount of time the spinner should be visible once it starts.
|
||||
spinnerMinLifetime = time.Millisecond * 550
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -35,29 +27,25 @@ var (
|
|||
markdownExtensions = []string{
|
||||
"*.md", "*.mdown", "*.mkdn", "*.mkd", "*.markdown",
|
||||
}
|
||||
|
||||
// True if we're logging to a file, in which case we'll log more stuff.
|
||||
debug = false
|
||||
|
||||
// Types of documents we allow the user to stash.
|
||||
stashableDocTypes = NewDocTypeSet(LocalDoc, NewsDoc)
|
||||
)
|
||||
|
||||
// NewProgram returns a new Tea program.
|
||||
func NewProgram(cfg Config) *tea.Program {
|
||||
if cfg.Logfile != "" {
|
||||
log.Println("-- Starting Glow ----------------")
|
||||
log.Printf("High performance pager: %v", cfg.HighPerformancePager)
|
||||
log.Printf("Glamour rendering: %v", cfg.GlamourEnabled)
|
||||
log.Println("Bubble Tea now initializing...")
|
||||
debug = true
|
||||
}
|
||||
func NewProgram(cfg Config, content string) *tea.Program {
|
||||
log.Debug(
|
||||
"Starting glow",
|
||||
"high_perf_pager",
|
||||
cfg.HighPerformancePager,
|
||||
"glamour",
|
||||
cfg.GlamourEnabled,
|
||||
)
|
||||
|
||||
config = cfg
|
||||
opts := []tea.ProgramOption{tea.WithAltScreen()}
|
||||
if cfg.EnableMouse {
|
||||
opts = append(opts, tea.WithMouseCellMotion())
|
||||
}
|
||||
return tea.NewProgram(newModel(cfg), opts...)
|
||||
m := newModel(cfg, content)
|
||||
return tea.NewProgram(m, opts...)
|
||||
}
|
||||
|
||||
type errMsg struct{ err error }
|
||||
|
|
@ -65,10 +53,6 @@ type errMsg struct{ err error }
|
|||
func (e errMsg) Error() string { return e.err.Error() }
|
||||
|
||||
type (
|
||||
newCharmClientMsg *charm.Client
|
||||
sshAuthErrMsg struct{}
|
||||
keygenFailedMsg struct{ err error }
|
||||
keygenSuccessMsg struct{}
|
||||
initLocalFileSearchMsg struct {
|
||||
cwd string
|
||||
ch chan gitcha.SearchResult
|
||||
|
|
@ -78,16 +62,7 @@ type (
|
|||
type (
|
||||
foundLocalFileMsg gitcha.SearchResult
|
||||
localFileSearchFinished struct{}
|
||||
gotStashMsg []*charm.Markdown
|
||||
stashLoadErrMsg struct{ err error }
|
||||
gotNewsMsg []*charm.Markdown
|
||||
statusMessageTimeoutMsg applicationContext
|
||||
newsLoadErrMsg struct{ err error }
|
||||
stashSuccessMsg markdown
|
||||
stashFailMsg struct {
|
||||
err error
|
||||
markdown markdown
|
||||
}
|
||||
)
|
||||
|
||||
// applicationContext indicates the area of the application something applies
|
||||
|
|
@ -114,63 +89,18 @@ func (s state) String() string {
|
|||
}[s]
|
||||
}
|
||||
|
||||
type authStatus int
|
||||
|
||||
const (
|
||||
authConnecting authStatus = iota
|
||||
authOK
|
||||
authFailed
|
||||
)
|
||||
|
||||
func (s authStatus) String() string {
|
||||
return map[authStatus]string{
|
||||
authConnecting: "connecting",
|
||||
authOK: "ok",
|
||||
authFailed: "failed",
|
||||
}[s]
|
||||
}
|
||||
|
||||
type keygenState int
|
||||
|
||||
const (
|
||||
keygenUnstarted keygenState = iota
|
||||
keygenRunning
|
||||
keygenFinished
|
||||
)
|
||||
|
||||
// Common stuff we'll need to access in all models.
|
||||
type commonModel struct {
|
||||
ctx tea.Context
|
||||
cfg Config
|
||||
cc *charm.Client
|
||||
cwd string
|
||||
authStatus authStatus
|
||||
width int
|
||||
height int
|
||||
|
||||
// Local IDs of files stashed this session. We treat this like a set,
|
||||
// ignoring the value portion with an empty struct.
|
||||
filesStashed map[ksuid.KSUID]struct{}
|
||||
|
||||
// ID of the most recently stashed markdown
|
||||
latestFileStashed ksuid.KSUID
|
||||
|
||||
// Files currently being stashed. We remove files from this set once
|
||||
// a stash operation has either succeeded or failed.
|
||||
filesStashing map[ksuid.KSUID]struct{}
|
||||
|
||||
styles styles
|
||||
}
|
||||
|
||||
func (c commonModel) isStashing() bool {
|
||||
return len(c.filesStashing) > 0
|
||||
cfg Config
|
||||
cwd string
|
||||
width int
|
||||
height int
|
||||
}
|
||||
|
||||
type model struct {
|
||||
common *commonModel
|
||||
state state
|
||||
keygenState keygenState
|
||||
fatalErr error
|
||||
common *commonModel
|
||||
state state
|
||||
fatalErr error
|
||||
|
||||
// Sub-models
|
||||
stash stashModel
|
||||
|
|
@ -191,7 +121,7 @@ func (m *model) unloadDocument() []tea.Cmd {
|
|||
|
||||
var batch []tea.Cmd
|
||||
if m.pager.viewport.HighPerformanceRendering {
|
||||
batch = append(batch, tea.ClearScrollArea)
|
||||
batch = append(batch, tea.ClearScrollArea) //nolint:staticcheck
|
||||
}
|
||||
|
||||
if !m.stash.shouldSpin() {
|
||||
|
|
@ -200,62 +130,79 @@ func (m *model) unloadDocument() []tea.Cmd {
|
|||
return batch
|
||||
}
|
||||
|
||||
func newModel(cfg Config) tea.Model {
|
||||
if len(cfg.DocumentTypes) == 0 {
|
||||
cfg.DocumentTypes.Add(LocalDoc, StashedDoc, ConvertedDoc, NewsDoc)
|
||||
}
|
||||
func newModel(cfg Config, content string) tea.Model {
|
||||
initSections()
|
||||
|
||||
common := commonModel{
|
||||
cfg: cfg,
|
||||
authStatus: authConnecting,
|
||||
filesStashed: make(map[ksuid.KSUID]struct{}),
|
||||
filesStashing: make(map[ksuid.KSUID]struct{}),
|
||||
}
|
||||
|
||||
return model{
|
||||
common: &common,
|
||||
state: stateShowStash,
|
||||
keygenState: keygenUnstarted,
|
||||
}
|
||||
}
|
||||
|
||||
func (m model) Init(ctx tea.Context) (tea.Model, tea.Cmd) {
|
||||
if m.common.cfg.GlamourStyle == "auto" {
|
||||
if ctx.HasLightBackground() {
|
||||
m.common.cfg.GlamourStyle = "light"
|
||||
if cfg.GlamourStyle == styles.AutoStyle {
|
||||
if te.HasDarkBackground() {
|
||||
cfg.GlamourStyle = styles.DarkStyle
|
||||
} else {
|
||||
m.common.cfg.GlamourStyle = "dark"
|
||||
cfg.GlamourStyle = styles.LightStyle
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize styles & sub-models
|
||||
m.common.ctx = ctx
|
||||
m.common.styles = defaultStyles(ctx)
|
||||
|
||||
// Initialize sections after we have the styles
|
||||
initSections(m.common)
|
||||
|
||||
m.pager = newPagerModel(m.common)
|
||||
m.stash = newStashModel(m.common)
|
||||
|
||||
var cmds []tea.Cmd
|
||||
d := m.common.cfg.DocumentTypes
|
||||
|
||||
if d.Contains(StashedDoc) || d.Contains(NewsDoc) {
|
||||
cmds = append(cmds,
|
||||
newCharmClient,
|
||||
m.stash.spinner.Tick,
|
||||
)
|
||||
common := commonModel{
|
||||
cfg: cfg,
|
||||
}
|
||||
|
||||
if d.Contains(LocalDoc) {
|
||||
cmds = append(cmds, findLocalFiles(m))
|
||||
m := model{
|
||||
common: &common,
|
||||
state: stateShowStash,
|
||||
pager: newPagerModel(&common),
|
||||
stash: newStashModel(&common),
|
||||
}
|
||||
|
||||
return m, tea.Batch(cmds...)
|
||||
path := cfg.Path
|
||||
if path == "" && content != "" {
|
||||
m.state = stateShowDocument
|
||||
m.pager.currentDocument = markdown{Body: content}
|
||||
return m
|
||||
}
|
||||
|
||||
if path == "" {
|
||||
path = "."
|
||||
}
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
log.Error("unable to stat file", "file", path, "error", err)
|
||||
m.fatalErr = err
|
||||
return m
|
||||
}
|
||||
if info.IsDir() {
|
||||
m.state = stateShowStash
|
||||
} else {
|
||||
cwd, _ := os.Getwd()
|
||||
m.state = stateShowDocument
|
||||
m.pager.currentDocument = markdown{
|
||||
localPath: path,
|
||||
Note: stripAbsolutePath(path, cwd),
|
||||
Modtime: info.ModTime(),
|
||||
}
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func (m model) Update(ctx tea.Context, msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
func (m model) Init() tea.Cmd {
|
||||
cmds := []tea.Cmd{m.stash.spinner.Tick}
|
||||
|
||||
switch m.state {
|
||||
case stateShowStash:
|
||||
cmds = append(cmds, findLocalFiles(*m.common))
|
||||
case stateShowDocument:
|
||||
content, err := os.ReadFile(m.common.cfg.Path)
|
||||
if err != nil {
|
||||
log.Error("unable to read file", "file", m.common.cfg.Path, "error", err)
|
||||
return func() tea.Msg { return errMsg{err} }
|
||||
}
|
||||
body := string(utils.RemoveFrontmatter(content))
|
||||
cmds = append(cmds, renderWithGlamour(m.pager, body))
|
||||
}
|
||||
|
||||
return tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
// If there's been an error, any key exits
|
||||
if m.fatalErr != nil {
|
||||
if _, ok := msg.(tea.KeyMsg); ok {
|
||||
|
|
@ -269,43 +216,45 @@ func (m model) Update(ctx tea.Context, msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
case "esc":
|
||||
if m.state == stateShowDocument {
|
||||
if m.state == stateShowDocument || m.stash.viewState == stashStateLoadingDocument {
|
||||
batch := m.unloadDocument()
|
||||
return m, tea.Batch(batch...)
|
||||
}
|
||||
case "r":
|
||||
var cmd tea.Cmd
|
||||
if m.state == stateShowStash {
|
||||
// pass through all keys if we're editing the filter
|
||||
if m.stash.filterState == filtering {
|
||||
m.stash, cmd = m.stash.update(msg)
|
||||
return m, cmd
|
||||
}
|
||||
m.stash.markdowns = nil
|
||||
return m, m.Init()
|
||||
}
|
||||
|
||||
case "q":
|
||||
var cmd tea.Cmd
|
||||
|
||||
switch m.state {
|
||||
switch m.state { //nolint:exhaustive
|
||||
case stateShowStash:
|
||||
// pass through all keys if we're editing the filter
|
||||
if m.stash.filterState == filtering || m.stash.selectionState == selectionSettingNote {
|
||||
m.stash, cmd = m.stash.update(ctx, msg)
|
||||
if m.stash.filterState == filtering {
|
||||
m.stash, cmd = m.stash.update(msg)
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
// Special cases for the pager
|
||||
case stateShowDocument:
|
||||
switch m.pager.state {
|
||||
// If setting a note send all keys straight through
|
||||
case pagerStateSetNote:
|
||||
var batch []tea.Cmd
|
||||
newPagerModel, cmd := m.pager.update(ctx, msg)
|
||||
m.pager = newPagerModel
|
||||
batch = append(batch, cmd)
|
||||
return m, tea.Batch(batch...)
|
||||
}
|
||||
}
|
||||
|
||||
return m, tea.Quit
|
||||
|
||||
case "left", "h", "delete":
|
||||
if m.state == stateShowDocument && m.pager.state != pagerStateSetNote {
|
||||
if m.state == stateShowDocument {
|
||||
cmds = append(cmds, m.unloadDocument()...)
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
case "ctrl+z":
|
||||
return m, tea.Suspend
|
||||
|
||||
// Ctrl+C always quits no matter where in the application you are.
|
||||
case "ctrl+c":
|
||||
return m, tea.Quit
|
||||
|
|
@ -323,70 +272,20 @@ func (m model) Update(ctx tea.Context, msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
m.common.cwd = msg.cwd
|
||||
cmds = append(cmds, findNextLocalFile(m))
|
||||
|
||||
case sshAuthErrMsg:
|
||||
if m.keygenState != keygenFinished { // if we haven't run the keygen yet, do that
|
||||
m.keygenState = keygenRunning
|
||||
cmds = append(cmds, generateSSHKeys)
|
||||
} else {
|
||||
// The keygen ran but things still didn't work and we can't auth
|
||||
m.common.authStatus = authFailed
|
||||
m.stash.err = errors.New("SSH authentication failed; we tried ssh-agent, loading keys from disk, and generating SSH keys")
|
||||
if debug {
|
||||
log.Println("entering offline mode;", m.stash.err)
|
||||
}
|
||||
|
||||
// Even though it failed, news/stash loading is finished
|
||||
m.stash.loaded.Add(StashedDoc, NewsDoc)
|
||||
}
|
||||
|
||||
case keygenFailedMsg:
|
||||
// Keygen failed. That sucks.
|
||||
m.common.authStatus = authFailed
|
||||
m.stash.err = errors.New("could not authenticate; could not generate SSH keys")
|
||||
if debug {
|
||||
log.Println("entering offline mode;", m.stash.err)
|
||||
}
|
||||
|
||||
m.keygenState = keygenFinished
|
||||
|
||||
// Even though it failed, news/stash loading is finished
|
||||
m.stash.loaded.Add(StashedDoc, NewsDoc)
|
||||
|
||||
case keygenSuccessMsg:
|
||||
// The keygen's done, so let's try initializing the charm client again
|
||||
m.keygenState = keygenFinished
|
||||
cmds = append(cmds, newCharmClient)
|
||||
|
||||
case newCharmClientMsg:
|
||||
m.common.cc = msg
|
||||
m.common.authStatus = authOK
|
||||
cmds = append(cmds, loadStash(m.stash), loadNews(m.stash))
|
||||
|
||||
case stashLoadErrMsg:
|
||||
m.common.authStatus = authFailed
|
||||
|
||||
case fetchedMarkdownMsg:
|
||||
// We've loaded a markdown file's contents for rendering
|
||||
m.pager.currentDocument = *msg
|
||||
msg.Body = string(utils.RemoveFrontmatter([]byte(msg.Body)))
|
||||
cmds = append(cmds, renderWithGlamour(m.pager, msg.Body))
|
||||
body := string(utils.RemoveFrontmatter([]byte(msg.Body)))
|
||||
cmds = append(cmds, renderWithGlamour(m.pager, body))
|
||||
|
||||
case contentRenderedMsg:
|
||||
m.state = stateShowDocument
|
||||
|
||||
case noteSavedMsg:
|
||||
// A note was saved to a document. This will have been done in the
|
||||
// pager, so we'll need to find the corresponding note in the stash.
|
||||
// So, pass the message to the stash for processing.
|
||||
stashModel, cmd := m.stash.update(ctx, msg)
|
||||
m.stash = stashModel
|
||||
return m, cmd
|
||||
|
||||
case localFileSearchFinished, gotStashMsg, gotNewsMsg:
|
||||
case localFileSearchFinished:
|
||||
// Always pass these messages to the stash so we can keep it updated
|
||||
// about network activity, even if the user isn't currently viewing
|
||||
// the stash.
|
||||
stashModel, cmd := m.stash.update(ctx, msg)
|
||||
stashModel, cmd := m.stash.update(msg)
|
||||
m.stash = stashModel
|
||||
return m, cmd
|
||||
|
||||
|
|
@ -401,38 +300,9 @@ func (m model) Update(ctx tea.Context, msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
}
|
||||
cmds = append(cmds, findNextLocalFile(m))
|
||||
|
||||
case stashSuccessMsg:
|
||||
// Common handling that should happen regardless of application state
|
||||
md := markdown(msg)
|
||||
m.stash.addMarkdowns(&md)
|
||||
m.common.filesStashed[msg.stashID] = struct{}{}
|
||||
delete(m.common.filesStashing, md.stashID)
|
||||
|
||||
if m.stash.filterApplied() {
|
||||
for _, v := range m.stash.filteredMarkdowns {
|
||||
if v.stashID == msg.stashID && v.docType == ConvertedDoc {
|
||||
// Add the server-side ID we got back so we can do things
|
||||
// like rename and stash it.
|
||||
v.ID = msg.ID
|
||||
|
||||
// Keep the unique ID in sync so we can do things like
|
||||
// delete. Note that the markdown received a new unique ID
|
||||
// when it was added to the file listing in
|
||||
// stash.addMarkdowns.
|
||||
v.uniqueID = md.uniqueID
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case stashFailMsg:
|
||||
// Common handling that should happen regardless of application state
|
||||
delete(m.common.filesStashed, msg.markdown.stashID)
|
||||
delete(m.common.filesStashing, msg.markdown.stashID)
|
||||
|
||||
case filteredMarkdownMsg:
|
||||
if m.state == stateShowDocument {
|
||||
newStashModel, cmd := m.stash.update(ctx, msg)
|
||||
newStashModel, cmd := m.stash.update(msg)
|
||||
m.stash = newStashModel
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
|
|
@ -441,12 +311,12 @@ func (m model) Update(ctx tea.Context, msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
// Process children
|
||||
switch m.state {
|
||||
case stateShowStash:
|
||||
newStashModel, cmd := m.stash.update(ctx, msg)
|
||||
newStashModel, cmd := m.stash.update(msg)
|
||||
m.stash = newStashModel
|
||||
cmds = append(cmds, cmd)
|
||||
|
||||
case stateShowDocument:
|
||||
newPagerModel, cmd := m.pager.update(ctx, msg)
|
||||
newPagerModel, cmd := m.pager.update(msg)
|
||||
m.pager = newPagerModel
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
|
|
@ -454,20 +324,20 @@ func (m model) Update(ctx tea.Context, msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
func (m model) View(ctx tea.Context) string {
|
||||
func (m model) View() string {
|
||||
if m.fatalErr != nil {
|
||||
return errorView(&m.common.styles, m.fatalErr, true)
|
||||
return errorView(m.fatalErr, true)
|
||||
}
|
||||
|
||||
switch m.state {
|
||||
switch m.state { //nolint:exhaustive
|
||||
case stateShowDocument:
|
||||
return m.pager.View(ctx)
|
||||
return m.pager.View()
|
||||
default:
|
||||
return m.stash.view(ctx)
|
||||
return m.stash.view()
|
||||
}
|
||||
}
|
||||
|
||||
func errorView(st *styles, err error, fatal bool) string {
|
||||
func errorView(err error, fatal bool) string {
|
||||
exitMsg := "press any key to "
|
||||
if fatal {
|
||||
exitMsg += "exit"
|
||||
|
|
@ -475,19 +345,20 @@ func errorView(st *styles, err error, fatal bool) string {
|
|||
exitMsg += "return"
|
||||
}
|
||||
s := fmt.Sprintf("%s\n\n%v\n\n%s",
|
||||
st.ErrorTitleStyle.Render("ERROR"),
|
||||
errorTitleStyle.Render("ERROR"),
|
||||
err,
|
||||
st.SubtleStyle.Render(exitMsg),
|
||||
subtleStyle.Render(exitMsg),
|
||||
)
|
||||
return "\n" + indent(s, 3)
|
||||
}
|
||||
|
||||
// COMMANDS
|
||||
|
||||
func findLocalFiles(m model) tea.Cmd {
|
||||
func findLocalFiles(m commonModel) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
log.Info("findLocalFiles")
|
||||
var (
|
||||
cwd = m.common.cfg.WorkingDirectory
|
||||
cwd = m.cfg.Path
|
||||
err error
|
||||
)
|
||||
|
||||
|
|
@ -503,26 +374,22 @@ func findLocalFiles(m model) tea.Cmd {
|
|||
|
||||
// Note that this is one error check for both cases above
|
||||
if err != nil {
|
||||
if debug {
|
||||
log.Println("error finding local files:", err)
|
||||
}
|
||||
log.Error("error finding local files", "error", err)
|
||||
return errMsg{err}
|
||||
}
|
||||
|
||||
if debug {
|
||||
log.Println("local directory is:", cwd)
|
||||
log.Debug("local directory is", "cwd", cwd)
|
||||
|
||||
// Switch between FindFiles and FindAllFiles to bypass .gitignore rules
|
||||
var ch chan gitcha.SearchResult
|
||||
if m.cfg.ShowAllFiles {
|
||||
ch, err = gitcha.FindAllFilesExcept(cwd, markdownExtensions, nil)
|
||||
} else {
|
||||
ch, err = gitcha.FindFilesExcept(cwd, markdownExtensions, ignorePatterns(m))
|
||||
}
|
||||
|
||||
var ignore []string
|
||||
if !m.common.cfg.ShowAllFiles {
|
||||
ignore = ignorePatterns(m)
|
||||
}
|
||||
|
||||
ch, err := gitcha.FindFilesExcept(cwd, markdownExtensions, ignore)
|
||||
if err != nil {
|
||||
if debug {
|
||||
log.Println("error finding local files:", err)
|
||||
}
|
||||
log.Error("error finding local files", "error", err)
|
||||
return errMsg{err}
|
||||
}
|
||||
|
||||
|
|
@ -539,186 +406,11 @@ func findNextLocalFile(m model) tea.Cmd {
|
|||
return foundLocalFileMsg(res)
|
||||
}
|
||||
// We're done
|
||||
if debug {
|
||||
log.Println("local file search finished")
|
||||
}
|
||||
log.Debug("local file search finished")
|
||||
return localFileSearchFinished{}
|
||||
}
|
||||
}
|
||||
|
||||
func newCharmClient() tea.Msg {
|
||||
cfg, err := charm.ConfigFromEnv()
|
||||
if err != nil {
|
||||
return errMsg{err}
|
||||
}
|
||||
|
||||
cc, err := charm.NewClient(cfg)
|
||||
if err == charm.ErrMissingSSHAuth {
|
||||
if debug {
|
||||
log.Println("missing SSH auth:", err)
|
||||
}
|
||||
return sshAuthErrMsg{}
|
||||
} else if err != nil {
|
||||
if debug {
|
||||
log.Println("error creating new charm client:", err)
|
||||
}
|
||||
return errMsg{err}
|
||||
}
|
||||
|
||||
return newCharmClientMsg(cc)
|
||||
}
|
||||
|
||||
func loadStash(m stashModel) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
if m.common.cc == nil {
|
||||
err := errors.New("no charm client")
|
||||
if debug {
|
||||
log.Println("error loading stash:", err)
|
||||
}
|
||||
return stashLoadErrMsg{err}
|
||||
}
|
||||
stash, err := m.common.cc.GetStash(m.serverPage)
|
||||
if err != nil {
|
||||
if debug {
|
||||
if _, ok := err.(charm.ErrAuthFailed); ok {
|
||||
log.Println("auth failure while loading stash:", err)
|
||||
} else {
|
||||
log.Println("error loading stash:", err)
|
||||
}
|
||||
}
|
||||
return stashLoadErrMsg{err}
|
||||
}
|
||||
if debug {
|
||||
log.Println("loaded stash page", m.serverPage)
|
||||
}
|
||||
return gotStashMsg(stash)
|
||||
}
|
||||
}
|
||||
|
||||
func loadNews(m stashModel) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
if m.common.cc == nil {
|
||||
err := errors.New("no charm client")
|
||||
if debug {
|
||||
log.Println("error loading news:", err)
|
||||
}
|
||||
return newsLoadErrMsg{err}
|
||||
}
|
||||
news, err := m.common.cc.GetNews(1) // just fetch the first page
|
||||
if err != nil {
|
||||
if debug {
|
||||
log.Println("error loading news:", err)
|
||||
}
|
||||
return newsLoadErrMsg{err}
|
||||
}
|
||||
if debug {
|
||||
log.Println("fetched news")
|
||||
}
|
||||
return gotNewsMsg(news)
|
||||
}
|
||||
}
|
||||
|
||||
func generateSSHKeys() tea.Msg {
|
||||
if debug {
|
||||
log.Println("running keygen...")
|
||||
}
|
||||
_, err := keygen.NewSSHKeyPair(nil)
|
||||
if err != nil {
|
||||
if debug {
|
||||
log.Println("keygen failed:", err)
|
||||
}
|
||||
return keygenFailedMsg{err}
|
||||
}
|
||||
if debug {
|
||||
log.Println("keys generated successfully")
|
||||
}
|
||||
return keygenSuccessMsg{}
|
||||
}
|
||||
|
||||
func saveDocumentNote(cc *charm.Client, id int, note string) tea.Cmd {
|
||||
if cc == nil {
|
||||
return func() tea.Msg {
|
||||
err := errors.New("can't set note; no charm client")
|
||||
if debug {
|
||||
log.Println("error saving note:", err)
|
||||
}
|
||||
return errMsg{err}
|
||||
}
|
||||
}
|
||||
return func() tea.Msg {
|
||||
if err := cc.SetMarkdownNote(id, note); err != nil {
|
||||
if debug {
|
||||
log.Println("error saving note:", err)
|
||||
}
|
||||
return errMsg{err}
|
||||
}
|
||||
return noteSavedMsg(&charm.Markdown{ID: id, Note: note})
|
||||
}
|
||||
}
|
||||
|
||||
func stashDocument(cc *charm.Client, md markdown) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
if cc == nil {
|
||||
err := errors.New("can't stash; no charm client")
|
||||
if debug {
|
||||
log.Println("error stashing document:", err)
|
||||
}
|
||||
return stashFailMsg{err, md}
|
||||
}
|
||||
|
||||
// Is the document missing a body? If so, it likely means it needs to
|
||||
// be loaded. But...if it turns out the document body really is empty
|
||||
// then we'll stash it anyway.
|
||||
if len(md.Body) == 0 {
|
||||
switch md.docType {
|
||||
case LocalDoc:
|
||||
data, err := os.ReadFile(md.localPath)
|
||||
if err != nil {
|
||||
if debug {
|
||||
log.Println("error loading document body for stashing:", err)
|
||||
}
|
||||
return stashFailMsg{err, md}
|
||||
}
|
||||
md.Body = string(data)
|
||||
|
||||
case NewsDoc:
|
||||
newMD, err := fetchMarkdown(cc, md.ID, md.docType)
|
||||
if err != nil {
|
||||
if debug {
|
||||
log.Println(err)
|
||||
}
|
||||
return stashFailMsg{err, md}
|
||||
}
|
||||
md.Body = newMD.Body
|
||||
|
||||
default:
|
||||
err := fmt.Errorf("user is attempting to stash an unsupported markdown type: %s", md.docType)
|
||||
if debug {
|
||||
log.Println(err)
|
||||
}
|
||||
return stashFailMsg{err, md}
|
||||
}
|
||||
}
|
||||
|
||||
newMd, err := cc.StashMarkdown(md.Note, md.Body)
|
||||
if err != nil {
|
||||
if debug {
|
||||
log.Println("error stashing document:", err)
|
||||
}
|
||||
return stashFailMsg{err, md}
|
||||
}
|
||||
|
||||
md.convertToStashed()
|
||||
|
||||
// The server sends the whole stashed document back, but we really just
|
||||
// need to know the ID so we can operate on this newly stashed
|
||||
// markdown.
|
||||
md.ID = newMd.ID
|
||||
|
||||
return stashSuccessMsg(md)
|
||||
}
|
||||
}
|
||||
|
||||
func waitForStatusMessageTimeout(appCtx applicationContext, t *time.Timer) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
<-t.C
|
||||
|
|
@ -732,20 +424,17 @@ func waitForStatusMessageTimeout(appCtx applicationContext, t *time.Timer) tea.C
|
|||
// document. Note that we could be doing things like checking if the file is
|
||||
// a directory, but we trust that gitcha has already done that.
|
||||
func localFileToMarkdown(cwd string, res gitcha.SearchResult) *markdown {
|
||||
md := &markdown{
|
||||
docType: LocalDoc,
|
||||
return &markdown{
|
||||
localPath: res.Path,
|
||||
Markdown: charm.Markdown{
|
||||
Note: stripAbsolutePath(res.Path, cwd),
|
||||
CreatedAt: res.Info.ModTime(),
|
||||
},
|
||||
Note: stripAbsolutePath(res.Path, cwd),
|
||||
Modtime: res.Info.ModTime(),
|
||||
}
|
||||
|
||||
return md
|
||||
}
|
||||
|
||||
func stripAbsolutePath(fullPath, cwd string) string {
|
||||
return strings.Replace(fullPath, cwd+string(os.PathSeparator), "", -1)
|
||||
fp, _ := filepath.EvalSymlinks(fullPath)
|
||||
cp, _ := filepath.EvalSymlinks(cwd)
|
||||
return strings.ReplaceAll(fp, cp+string(os.PathSeparator), "")
|
||||
}
|
||||
|
||||
// Lightweight version of reflow's indent function.
|
||||
|
|
@ -761,17 +450,3 @@ func indent(s string, n int) string {
|
|||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
|
|
|||
86
url.go
Normal file
86
url.go
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
protoGithub = "github://"
|
||||
protoGitlab = "gitlab://"
|
||||
protoHTTPS = "https://"
|
||||
)
|
||||
|
||||
var (
|
||||
githubURL *url.URL
|
||||
gitlabURL *url.URL
|
||||
urlsOnce sync.Once
|
||||
)
|
||||
|
||||
func init() {
|
||||
urlsOnce.Do(func() {
|
||||
githubURL, _ = url.Parse("https://github.com")
|
||||
gitlabURL, _ = url.Parse("https://gitlab.com")
|
||||
})
|
||||
}
|
||||
|
||||
func readmeURL(path string) (*source, error) {
|
||||
switch {
|
||||
case strings.HasPrefix(path, protoGithub):
|
||||
if u := githubReadmeURL(path); u != nil {
|
||||
return readmeURL(u.String())
|
||||
}
|
||||
return nil, nil
|
||||
case strings.HasPrefix(path, protoGitlab):
|
||||
if u := gitlabReadmeURL(path); u != nil {
|
||||
return readmeURL(u.String())
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(path, protoHTTPS) {
|
||||
path = protoHTTPS + path
|
||||
}
|
||||
u, err := url.Parse(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse url: %w", err)
|
||||
}
|
||||
|
||||
switch {
|
||||
case u.Hostname() == githubURL.Hostname():
|
||||
return findGitHubREADME(u)
|
||||
case u.Hostname() == gitlabURL.Hostname():
|
||||
return findGitLabREADME(u)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func githubReadmeURL(path string) *url.URL {
|
||||
path = strings.TrimPrefix(path, protoGithub)
|
||||
parts := strings.Split(path, "/")
|
||||
if len(parts) != 2 {
|
||||
// custom hostnames are not supported yet
|
||||
return nil
|
||||
}
|
||||
u, _ := url.Parse(githubURL.String())
|
||||
return u.JoinPath(path)
|
||||
}
|
||||
|
||||
func gitlabReadmeURL(path string) *url.URL {
|
||||
path = strings.TrimPrefix(path, protoGitlab)
|
||||
parts := strings.Split(path, "/")
|
||||
if len(parts) != 2 {
|
||||
// custom hostnames are not supported yet
|
||||
return nil
|
||||
}
|
||||
u, _ := url.Parse(gitlabURL.String())
|
||||
return u.JoinPath(path)
|
||||
}
|
||||
|
||||
func isURL(path string) bool {
|
||||
_, err := url.ParseRequestURI(path)
|
||||
return err == nil && strings.Contains(path, "://")
|
||||
}
|
||||
30
url_test.go
Normal file
30
url_test.go
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
package main
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestURLParser(t *testing.T) {
|
||||
for path, url := range map[string]string{
|
||||
"github.com/charmbracelet/glow": "https://raw.githubusercontent.com/charmbracelet/glow/master/README.md",
|
||||
"github://charmbracelet/glow": "https://raw.githubusercontent.com/charmbracelet/glow/master/README.md",
|
||||
"github://caarlos0/dotfiles.fish": "https://raw.githubusercontent.com/caarlos0/dotfiles.fish/main/README.md",
|
||||
"github://tj/git-extras": "https://raw.githubusercontent.com/tj/git-extras/main/Readme.md",
|
||||
"https://github.com/goreleaser/nfpm": "https://raw.githubusercontent.com/goreleaser/nfpm/main/README.md",
|
||||
"gitlab.com/caarlos0/test": "https://gitlab.com/caarlos0/test/-/raw/master/README.md",
|
||||
"gitlab://caarlos0/test": "https://gitlab.com/caarlos0/test/-/raw/master/README.md",
|
||||
"https://gitlab.com/terrakok/gitlab-client": "https://gitlab.com/terrakok/gitlab-client/-/raw/develop/Readme.md",
|
||||
} {
|
||||
t.Run(path, func(t *testing.T) {
|
||||
t.Skip("test uses network, sometimes fails for no reason")
|
||||
got, err := readmeURL(path)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if got == nil {
|
||||
t.Fatalf("should not be nil")
|
||||
}
|
||||
if url != got.URL {
|
||||
t.Errorf("expected url for %s to be %s, was %s", path, url, got.URL)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,16 @@
|
|||
// Package utils provides utility functions.
|
||||
package utils
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/glamour"
|
||||
"github.com/charmbracelet/glamour/ansi"
|
||||
"github.com/charmbracelet/glamour/styles"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
)
|
||||
|
||||
|
|
@ -24,7 +31,7 @@ func detectFrontmatter(c []byte) []int {
|
|||
return []int{-1, -1}
|
||||
}
|
||||
|
||||
// Expands tilde and all environment variables from the given path.
|
||||
// ExpandPath expands tilde and all environment variables from the given path.
|
||||
func ExpandPath(path string) string {
|
||||
s, err := homedir.Expand(path)
|
||||
if err == nil {
|
||||
|
|
@ -32,3 +39,75 @@ func ExpandPath(path string) string {
|
|||
}
|
||||
return os.ExpandEnv(path)
|
||||
}
|
||||
|
||||
// WrapCodeBlock wraps a string in a code block with the given language.
|
||||
func WrapCodeBlock(s, language string) string {
|
||||
return "```" + language + "\n" + s + "```"
|
||||
}
|
||||
|
||||
var markdownExtensions = []string{
|
||||
".md", ".mdown", ".mkdn", ".mkd", ".markdown",
|
||||
}
|
||||
|
||||
// IsMarkdownFile returns whether the filename has a markdown extension.
|
||||
func IsMarkdownFile(filename string) bool {
|
||||
ext := filepath.Ext(filename)
|
||||
|
||||
if ext == "" {
|
||||
// By default, assume it's a markdown file.
|
||||
return true
|
||||
}
|
||||
|
||||
for _, v := range markdownExtensions {
|
||||
if strings.EqualFold(ext, v) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Has an extension but not markdown
|
||||
// so assume this is a code file.
|
||||
return false
|
||||
}
|
||||
|
||||
// GlamourStyle returns a glamour.TermRendererOption based on the given style.
|
||||
func GlamourStyle(style string, isCode bool) glamour.TermRendererOption {
|
||||
if !isCode {
|
||||
if style == styles.AutoStyle {
|
||||
return glamour.WithAutoStyle()
|
||||
}
|
||||
return glamour.WithStylePath(style)
|
||||
}
|
||||
|
||||
// If we are rendering a pure code block, we need to modify the style to
|
||||
// remove the indentation.
|
||||
|
||||
var styleConfig ansi.StyleConfig
|
||||
|
||||
switch style {
|
||||
case styles.AutoStyle:
|
||||
if lipgloss.HasDarkBackground() {
|
||||
styleConfig = styles.DarkStyleConfig
|
||||
} else {
|
||||
styleConfig = styles.LightStyleConfig
|
||||
}
|
||||
case styles.DarkStyle:
|
||||
styleConfig = styles.DarkStyleConfig
|
||||
case styles.LightStyle:
|
||||
styleConfig = styles.LightStyleConfig
|
||||
case styles.PinkStyle:
|
||||
styleConfig = styles.PinkStyleConfig
|
||||
case styles.NoTTYStyle:
|
||||
styleConfig = styles.NoTTYStyleConfig
|
||||
case styles.DraculaStyle:
|
||||
styleConfig = styles.DraculaStyleConfig
|
||||
case styles.TokyoNightStyle:
|
||||
styleConfig = styles.DraculaStyleConfig
|
||||
default:
|
||||
return glamour.WithStylesFromJSONFile(style)
|
||||
}
|
||||
|
||||
var margin uint
|
||||
styleConfig.CodeBlock.Margin = &margin
|
||||
|
||||
return glamour.WithStyles(styleConfig)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue