mirror of
https://github.com/makew0rld/amfora.git
synced 2024-09-11 05:55:27 +03:00
Compare commits
122 Commits
8c6950635a
...
575476857a
Author | SHA1 | Date | |
---|---|---|---|
|
575476857a | ||
|
88caea0808 | ||
|
1a59487098 | ||
|
eaf4460134 | ||
|
84767a39fa | ||
|
6c8fde70aa | ||
|
b6da629623 | ||
|
c4fd4301f9 | ||
|
2534983d96 | ||
|
ef8a09997c | ||
|
8ee55e67bb | ||
|
926b77275f | ||
|
1a0d2b6d5a | ||
|
6a92dd336b | ||
|
598a9d05dd | ||
|
f3cf73dd29 | ||
|
e35ba06cf9 | ||
|
818604f582 | ||
|
1da658c8a6 | ||
|
4edbdd288d | ||
|
40f55d894b | ||
|
e432e28003 | ||
|
66858aab09 | ||
|
64548334e5 | ||
|
be88605753 | ||
|
e5b5a5e494 | ||
|
0b3f874ef1 | ||
|
82a1e08fe0 | ||
|
dba8cbf61b | ||
|
1d257f908a | ||
|
00541a435d | ||
|
32b2182267 | ||
|
bc81281c84 | ||
|
946b3f5bc0 | ||
|
99fbd021a6 | ||
|
3917402e2d | ||
|
bfb3893774 | ||
|
a71a38926b | ||
|
00d90cbd7a | ||
|
ebf5e521d6 | ||
|
ea9c7f214a | ||
|
a2c6ca42c7 | ||
|
50044ddce5 | ||
|
e6ac6d8ebf | ||
|
dc84332124 | ||
|
a373aecfb7 | ||
|
22d44e76b9 | ||
|
4a95df8036 | ||
|
e3e1fc27cd | ||
|
1f1774a18a | ||
|
cabc0660fd | ||
|
33bf9603b5 | ||
|
1aa13f2408 | ||
|
03c4d3e286 | ||
|
004851f651 | ||
|
7318283aef | ||
|
eab0a6a626 | ||
|
d312a801e3 | ||
|
034b4c019d | ||
|
3823a46152 | ||
|
40865f977e | ||
|
2ae6f6f3a1 | ||
|
baebc86c09 | ||
|
0deee6d573 | ||
|
7edb6c5381 | ||
|
77e3dbed87 | ||
|
e62da93e57 | ||
|
61d8645401 | ||
|
48d83d13d2 | ||
|
149b0d1ce2 | ||
|
e021d790ac | ||
|
9fa31f38d6 | ||
|
b7f916f1f9 | ||
|
18062d6a01 | ||
|
be2fe883cb | ||
|
b58a520ac1 | ||
|
bf62e9498d | ||
|
efb398fed8 | ||
|
8596df2c7a | ||
|
52bfae5f2b | ||
|
762e2d606a | ||
|
969f4f91d9 | ||
|
7645c46ccb | ||
|
0ccf5e2c03 | ||
|
76276f6d1f | ||
|
aafe0a85fe | ||
|
fe73359bcd | ||
|
e8342ce3fd | ||
|
9c217df9a8 | ||
|
eb314f2a4d | ||
|
1c4d13b055 | ||
|
790d7ace6c | ||
|
043242392c | ||
|
97ee1aa368 | ||
|
06b649d81a | ||
|
e0568f26c6 | ||
|
5ed3afcd52 | ||
|
b52263c487 | ||
|
f270fa2b23 | ||
|
6666ef2584 | ||
|
2e1049b8ab | ||
|
599f42744e | ||
|
0245267d87 | ||
|
5098f125a1 | ||
|
a033731cd8 | ||
|
92fd9ed0bc | ||
|
6e17b57837 | ||
|
9a2c2ec1f8 | ||
|
545144c1a3 | ||
|
e8122fc52d | ||
|
2f471f96ce | ||
|
8dc34f7131 | ||
|
5f57b13217 | ||
|
3929c9704e | ||
|
cb3729f94a | ||
|
94806f54bd | ||
|
cde2960e35 | ||
|
ac1303f342 | ||
|
6ec7ed1668 | ||
|
4ef4d247d1 | ||
|
ff0f6d3812 | ||
|
4480e2d540 |
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@ -1,2 +1,2 @@
|
|||||||
github: makeworld-the-better-one
|
github: makew0rld
|
||||||
ko_fi: makeworld
|
ko_fi: makeworld
|
||||||
|
9
.github/workflows/golangci-lint.yml
vendored
9
.github/workflows/golangci-lint.yml
vendored
@ -19,11 +19,14 @@ jobs:
|
|||||||
name: lint
|
name: lint
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: 1.22
|
||||||
|
- uses: actions/checkout@v3
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v2
|
uses: golangci/golangci-lint-action@v3
|
||||||
with:
|
with:
|
||||||
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
|
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
|
||||||
version: v1.35
|
version: v1.43
|
||||||
# Optional: show only new issues if it's a pull request. The default value is `false`.
|
# Optional: show only new issues if it's a pull request. The default value is `false`.
|
||||||
only-new-issues: true
|
only-new-issues: true
|
||||||
|
4
.github/workflows/goreleaser.yml
vendored
4
.github/workflows/goreleaser.yml
vendored
@ -16,11 +16,11 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: 1.16
|
go-version: 1.22
|
||||||
- name: Run GoReleaser
|
- name: Run GoReleaser
|
||||||
uses: goreleaser/goreleaser-action@v2
|
uses: goreleaser/goreleaser-action@v2
|
||||||
with:
|
with:
|
||||||
version: 0.x
|
version: 0.x
|
||||||
args: release --rm-dist
|
args: release --rm-dist
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GH_REPOS }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
16
.github/workflows/homebrew.yml
vendored
Normal file
16
.github/workflows/homebrew.yml
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
homebrew:
|
||||||
|
name: Bump Homebrew formula
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: mislav/bump-homebrew-formula-action@v3
|
||||||
|
with:
|
||||||
|
# A PR will be sent to github.com/Homebrew/homebrew-core to update this formula:
|
||||||
|
formula-name: amfora
|
||||||
|
env:
|
||||||
|
COMMITTER_TOKEN: ${{ secrets.HOMEBREW_TOKEN }}
|
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
@ -19,8 +19,8 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
go-version: ['1.14', '1.15', '1.16']
|
go-version: ['1.21', '1.22']
|
||||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
os: [ubuntu-latest]
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
|
@ -19,15 +19,13 @@ linters:
|
|||||||
- goerr113
|
- goerr113
|
||||||
- gofmt
|
- gofmt
|
||||||
- goimports
|
- goimports
|
||||||
- golint
|
- revive
|
||||||
- goprintffuncname
|
- goprintffuncname
|
||||||
- interfacer
|
|
||||||
- lll
|
- lll
|
||||||
- maligned
|
|
||||||
- misspell
|
- misspell
|
||||||
- nolintlint
|
- nolintlint
|
||||||
- prealloc
|
- prealloc
|
||||||
- scopelint
|
- exportloopref
|
||||||
- unconvert
|
- unconvert
|
||||||
- unparam
|
- unparam
|
||||||
|
|
||||||
|
@ -43,6 +43,8 @@ builds:
|
|||||||
goarch: arm
|
goarch: arm
|
||||||
- goos: openbsd
|
- goos: openbsd
|
||||||
goarch: arm64
|
goarch: arm64
|
||||||
|
- goos: windows
|
||||||
|
goarch: arm
|
||||||
|
|
||||||
archives:
|
archives:
|
||||||
- format: binary
|
- format: binary
|
||||||
|
71
CHANGELOG.md
71
CHANGELOG.md
@ -4,7 +4,50 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [Unreleased]
|
## [1.10.0] - 2024-03-17
|
||||||
|
### Added
|
||||||
|
- Syntax highlighting for preformatted text blocks with alt text (#252, #263, [wiki page](https://github.com/makeworld-the-better-one/amfora/wiki/Source-Code-Highlighting))
|
||||||
|
- [Client certificates](https://github.com/makeworld-the-better-one/amfora/wiki/Client-Certificates) can be restricted to certain paths of a host (#115)
|
||||||
|
- `header` config option in `[subscriptions]` to allow disabling the header text on the subscriptions page (#191)
|
||||||
|
- Selected link and scroll position stays for non-cached pages (#122)
|
||||||
|
- Keybinding to open URL with URL handler instead of configured proxy (#143)
|
||||||
|
- `include` theme key to import themes from an external file (#154, #290)
|
||||||
|
- Support SOCKS5 proxying by setting `AMFORA_SOCKS5` environment variable (#155)
|
||||||
|
- When bookmarking a page, the first level one heading is suggested as the name (#267, #293)
|
||||||
|
- Confirmation prompts for URL schemes in new `[url-prompts]` config section (#301, #302)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Center text automatically, removing `left_margin` from the config (#233)
|
||||||
|
- `max_width` defaults to 80 columns instead of 100 (#233)
|
||||||
|
- Tabs have the domain of the current page instead of numbers (#202)
|
||||||
|
- Closing Amfora with <kbd>q</kbd> was removed in favor of <kbd>Shift-q</kbd> (#243)
|
||||||
|
- Paging up or down scrolls by 50% instead of 75%, to match `less` (#303)
|
||||||
|
- Update deps, require Go 1.17 (#336)
|
||||||
|
- Show local directory index file if available (#319)
|
||||||
|
- Updated Project Gemini URLs (#342)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Modal can't be closed when opening non-gemini text URLs from the commandline (#283, #284)
|
||||||
|
- External programs started by Amfora remain as zombie processes (#219)
|
||||||
|
- Prevent link lines (and other types) from being wider than the `max_width` setting (#280)
|
||||||
|
- `new:7` on new tab page fails to open link (#306)
|
||||||
|
- Slashes aren't decoded in redirect URLs (#322, #324)
|
||||||
|
- Typing `localhost` in the bottom bar actually loads localhost instead of searching (#326, #327)
|
||||||
|
|
||||||
|
|
||||||
|
## [1.9.2] - 2021-12-10
|
||||||
|
### Fixed
|
||||||
|
- Preformatted text color showing even when `color = false` (bug since v1.8.0 at least) (#278)
|
||||||
|
- Link numbers and link text in color even when `color = false` (regression in v1.9.0) (#278)
|
||||||
|
|
||||||
|
|
||||||
|
## [1.9.1] - 2021-12-08
|
||||||
|
### Fixed
|
||||||
|
- Deadlock when loading an invalid `about:` URL (#277)
|
||||||
|
- Crash when rendering text from stdin
|
||||||
|
|
||||||
|
|
||||||
|
## [1.9.0] - 2021-12-07
|
||||||
### Added
|
### Added
|
||||||
- Support for version 1.1 JSON feeds
|
- Support for version 1.1 JSON feeds
|
||||||
- Copy current URL or selected URL to clipboard (#220, #225)
|
- Copy current URL or selected URL to clipboard (#220, #225)
|
||||||
@ -12,21 +55,41 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Configurable keybindings for scrolling on pages (#211, #222)
|
- Configurable keybindings for scrolling on pages (#211, #222)
|
||||||
- Ability to save `about:` pages (#210, #236)
|
- Ability to save `about:` pages (#210, #236)
|
||||||
- `bind_beginning` and `bind_end` keybindings
|
- `bind_beginning` and `bind_end` keybindings
|
||||||
|
- Display gemtext from stdin (#205, #242)
|
||||||
|
- Specifying `default` in the theme config uses the terminal's default background color, including transparency (#244, #245)
|
||||||
|
- Redirects occur automatically if it only adds a trailing slash (#271)
|
||||||
|
- Non-gemini links are underlined by default to help color blind users (#189)
|
||||||
|
- Text and element colors of default theme change to be black on terminals with light backgrounds (#181)
|
||||||
|
- Support paths with spaces in `[url-handlers]` config settings (#214)
|
||||||
|
- Display info modal when opening URL with custom application
|
||||||
|
- Files can be opened by relative path on the commandline (#231, #257)
|
||||||
|
- Support keybindings that use <kbd>Shift</kbd> (#269)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Favicon support removed (#199)
|
|
||||||
- Bookmarks are stored using XML in the XBEL format, old bookmarks are transferred (#68)
|
- Bookmarks are stored using XML in the XBEL format, old bookmarks are transferred (#68)
|
||||||
- Text no longer disappears under the left margin when scrolling (regression from v1.8.0) (#197)
|
- Text no longer disappears under the left margin when scrolling (regression in v1.8.0) (#197)
|
||||||
- Default search engine changed to geminispace.info from gus.guru
|
- Default search engine changed to geminispace.info from gus.guru
|
||||||
|
- The user's terminal theme colors are used by default (#181)
|
||||||
|
- By default, non-gemini URI schemes are opened in the default application. This requires a config change for previous users, see the [wiki](https://github.com/makeworld-the-better-one/amfora/wiki/Handling-Other-URL-Schemes) (#207)
|
||||||
|
- Windows uses paths set by `XDG` variables over `APPDATA` if they are set (#255)
|
||||||
|
- Treat status codes like 22 as equivalent to 20 as per the latest spec (#266)
|
||||||
|
- Show minimal loading page instead of `about:newtab` when loading a URL in a new tab (#272)
|
||||||
|
|
||||||
|
## Removed
|
||||||
|
- Favicon support (#199)
|
||||||
|
- The default Amfora theme, get it back [here](https://github.com/makeworld-the-better-one/amfora/blob/master/contrib/themes/amfora.toml) (#181)
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Help text is now the same color as `regular_text` in the theme config
|
- Help text is now the same color as `regular_text` in the theme config
|
||||||
- Non-ASCII (multibyte) characters can now be used as keybindings (#198, #200)
|
- Non-ASCII (multibyte) characters can now be used as keybindings (#198, #200)
|
||||||
- Possible subscription update race condition on startup
|
- Possible subscription update race condition on startup
|
||||||
- Plaintext documents are escaped properly (regression from v1.8.0)
|
- Plaintext documents are escaped properly (regression in v1.8.0)
|
||||||
- Help page scrollbar color matches what's in the theme config
|
- Help page scrollbar color matches what's in the theme config
|
||||||
- Regression where lists would not appear if `bullets = false` (#234, #235)
|
- Regression where lists would not appear if `bullets = false` (#234, #235)
|
||||||
- Support multiple bookmarks with the same name
|
- Support multiple bookmarks with the same name
|
||||||
|
- Cert change message grammar: "an security" -> "a security" (#274)
|
||||||
|
- Display an error modal for status codes that can't be handled
|
||||||
|
- Prevent user from getting trapped in the help menu when keybindings are pressed (#241, #261)
|
||||||
|
|
||||||
|
|
||||||
## [1.8.0] - 2021-02-17
|
## [1.8.0] - 2021-02-17
|
||||||
|
12
Makefile
12
Makefile
@ -21,15 +21,15 @@ clean:
|
|||||||
|
|
||||||
.PHONY: install
|
.PHONY: install
|
||||||
install: amfora amfora.desktop
|
install: amfora amfora.desktop
|
||||||
$(INSTALL) -d $(PREFIX)/bin/
|
$(INSTALL) -d $(DESTDIR)$(PREFIX)/bin/
|
||||||
$(INSTALL) -m 755 amfora $(PREFIX)/bin/amfora
|
$(INSTALL) -m 755 amfora $(DESTDIR)$(PREFIX)/bin/amfora
|
||||||
$(INSTALL) -d $(PREFIX)/share/applications/
|
$(INSTALL) -d $(DESTDIR)$(PREFIX)/share/applications/
|
||||||
$(INSTALL) -m 644 amfora.desktop $(PREFIX)/share/applications/amfora.desktop
|
$(INSTALL) -m 644 amfora.desktop $(DESTDIR)$(PREFIX)/share/applications/amfora.desktop
|
||||||
|
|
||||||
.PHONY: uninstall
|
.PHONY: uninstall
|
||||||
uninstall:
|
uninstall:
|
||||||
$(RM) -f $(PREFIX)/bin/amfora
|
$(RM) -f $(DESTDIR)$(PREFIX)/bin/amfora
|
||||||
$(RM) -f $(PREFIX)/share/applications/amfora.desktop
|
$(RM) -f $(DESTDIR)$(PREFIX)/share/applications/amfora.desktop
|
||||||
|
|
||||||
# Development helpers
|
# Development helpers
|
||||||
|
|
||||||
|
1
NOTES.md
1
NOTES.md
@ -6,7 +6,6 @@
|
|||||||
## Upstream Bugs
|
## Upstream Bugs
|
||||||
- Bookmark keys aren't deleted, just set to `""`
|
- Bookmark keys aren't deleted, just set to `""`
|
||||||
- Waiting on [this viper PR](https://github.com/spf13/viper/pull/519) to be merged
|
- Waiting on [this viper PR](https://github.com/spf13/viper/pull/519) to be merged
|
||||||
- [cview.Styles not being used](https://code.rocketnine.space/tslocum/cview/issues/47) - issue is circumvented in Amfora
|
|
||||||
- [ANSI conversion is messed up](https://code.rocketnine.space/tslocum/cview/issues/48)
|
- [ANSI conversion is messed up](https://code.rocketnine.space/tslocum/cview/issues/48)
|
||||||
- [WordWrap is broken in some cases](https://code.rocketnine.space/tslocum/cview/issues/27) - close #156 if this is fixed
|
- [WordWrap is broken in some cases](https://code.rocketnine.space/tslocum/cview/issues/27) - close #156 if this is fixed
|
||||||
- [Prevent panic when reformatting](https://code.rocketnine.space/tslocum/cview/issues/50) - can't reliably reproduce or debug
|
- [Prevent panic when reformatting](https://code.rocketnine.space/tslocum/cview/issues/50) - can't reliably reproduce or debug
|
||||||
|
31
README.md
31
README.md
@ -13,12 +13,16 @@
|
|||||||
|
|
||||||
###### Recording of v1.0.0
|
###### Recording of v1.0.0
|
||||||
|
|
||||||
Amfora aims to be the best looking [Gemini](https://gemini.circumlunar.space/) client with the most features... all in the terminal. It does not support Gopher or other non-Web protocols - check out [Bombadillo](http://bombadillo.colorfield.space/) for that.
|
Amfora aims to be the best looking [Gemini](https://geminiquickst.art/) client with the most features... all in the terminal. It does not support Gopher or other non-Web protocols - check out [Bombadillo](http://bombadillo.colorfield.space/) for that.
|
||||||
|
|
||||||
It also aims to be completely cross platform, with full Windows support. If you're on Windows, I would not recommend using the default terminal software. Use [Windows Terminal](https://www.microsoft.com/en-us/p/windows-terminal/9n0dx20hk701) instead, and make sure it [works with UTF-8](https://akr.am/blog/posts/using-utf-8-in-the-windows-terminal). Note that some of the application colors might not display correctly on Windows, but all functionality will still work.
|
It also aims to be completely cross platform, with full Windows support. If you're on Windows, I would not recommend using the default terminal software. Use [Windows Terminal](https://www.microsoft.com/en-us/p/windows-terminal/9n0dx20hk701) instead, and make sure it [works with UTF-8](https://akr.am/blog/posts/using-utf-8-in-the-windows-terminal). Note that some of the application colors might not display correctly on Windows, but all functionality will still work.
|
||||||
|
|
||||||
It fully passes Sean Conman's client torture test, as well as the Egsam one.
|
It fully passes Sean Conman's client torture test, as well as the Egsam one.
|
||||||
|
|
||||||
|
## Project Status
|
||||||
|
|
||||||
|
Amfora is in maintenance mode. When possible, I’ll make/merge bug fixes, and maybe slowly merge feature PRs by others. See my [blog post](https://www.makeworld.space/2023/08/bye_gemini.html) for details.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Binary
|
### Binary
|
||||||
@ -44,11 +48,10 @@ Make sure to click "Watch" in the top right, then "Custom" > "Releases" to get n
|
|||||||
|
|
||||||
Amfora is packaged in many Linux distros. It's also on [Scoop](https://scoop.sh/) for Windows users.
|
Amfora is packaged in many Linux distros. It's also on [Scoop](https://scoop.sh/) for Windows users.
|
||||||
|
|
||||||
### Homebrew
|
### macOS (Homebrew)
|
||||||
|
|
||||||
If you use [Homebrew](https://brew.sh/), you can install Amfora through the my personal tap.
|
If you use [Homebrew](https://brew.sh/), you can install Amfora with:
|
||||||
```
|
```
|
||||||
brew tap makeworld-the-better-one/tap
|
|
||||||
brew install amfora
|
brew install amfora
|
||||||
```
|
```
|
||||||
You can update it with:
|
You can update it with:
|
||||||
@ -56,6 +59,19 @@ You can update it with:
|
|||||||
brew upgrade amfora
|
brew upgrade amfora
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### macOS (MacPorts)
|
||||||
|
|
||||||
|
On macOS, Amfora can also be installed through [MacPorts](https://www.macports.org):
|
||||||
|
```
|
||||||
|
sudo port install amfora
|
||||||
|
```
|
||||||
|
You can update it with:
|
||||||
|
```
|
||||||
|
sudo port selfupdate
|
||||||
|
sudo port upgrade amfora
|
||||||
|
```
|
||||||
|
**NOTE:** this installation source is community-maintained. More information [here](https://ports.macports.org/port/amfora/).
|
||||||
|
|
||||||
### Termux
|
### Termux
|
||||||
|
|
||||||
If you're using [Termux](https://termux.com/) on Android you can't just run Amfora like normal. After installing Amfora, run `pkg install proot`. Then run `termux-chroot` before running the Amfora binary. You can exit out of the chroot after closing Amfora. See [here](https://stackoverflow.com/q/38959067/7361270) for why this is needed.
|
If you're using [Termux](https://termux.com/) on Android you can't just run Amfora like normal. After installing Amfora, run `pkg install proot`. Then run `termux-chroot` before running the Amfora binary. You can exit out of the chroot after closing Amfora. See [here](https://stackoverflow.com/q/38959067/7361270) for why this is needed.
|
||||||
@ -68,7 +84,7 @@ This section is for advanced users who want to install the latest (possibly unst
|
|||||||
<summary>Click to expand</summary>
|
<summary>Click to expand</summary>
|
||||||
|
|
||||||
**Requirements:**
|
**Requirements:**
|
||||||
- Go 1.14 or later
|
- Go 1.15 or later
|
||||||
- GNU Make
|
- GNU Make
|
||||||
|
|
||||||
Please note the Makefile does not intend to support Windows, and so there may be issues.
|
Please note the Makefile does not intend to support Windows, and so there may be issues.
|
||||||
@ -92,7 +108,6 @@ yay -S amfora-git
|
|||||||
MacOS users can also use [Homebrew](https://brew.sh/) to install the latest commit of Amfora:
|
MacOS users can also use [Homebrew](https://brew.sh/) to install the latest commit of Amfora:
|
||||||
|
|
||||||
```
|
```
|
||||||
brew tap makeworld-the-better-one/tap
|
|
||||||
brew install --HEAD amfora
|
brew install --HEAD amfora
|
||||||
```
|
```
|
||||||
You can update it with:
|
You can update it with:
|
||||||
@ -131,6 +146,7 @@ Features in *italics* are in the master branch, but not in the latest release.
|
|||||||
- So is subscribing to a page, to know when it changes
|
- So is subscribing to a page, to know when it changes
|
||||||
- [x] Open non-text files in another application
|
- [x] Open non-text files in another application
|
||||||
- [x] Ability to stream content instead of downloading it first
|
- [x] Ability to stream content instead of downloading it first
|
||||||
|
- [x] *Highlighting of preformatted code blocks that list a language in the alt text*
|
||||||
- [ ] Stream support
|
- [ ] Stream support
|
||||||
- [ ] Table of contents for pages
|
- [ ] Table of contents for pages
|
||||||
- [ ] Search in pages with <kbd>Ctrl-F</kbd>
|
- [ ] Search in pages with <kbd>Ctrl-F</kbd>
|
||||||
@ -151,6 +167,9 @@ Amfora ❤️ open source!
|
|||||||
- [progressbar](https://github.com/schollz/progressbar)
|
- [progressbar](https://github.com/schollz/progressbar)
|
||||||
- [go-humanize](https://github.com/dustin/go-humanize)
|
- [go-humanize](https://github.com/dustin/go-humanize)
|
||||||
- [gofeed](https://github.com/mmcdole/gofeed)
|
- [gofeed](https://github.com/mmcdole/gofeed)
|
||||||
|
- [chroma](https://github.com/alecthomas/chroma) for source code syntax highlighting
|
||||||
|
- [clipboard](https://github.com/atotto/clipboard)
|
||||||
|
- [termenv](https://github.com/muesli/termenv)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
This project is licensed under the GPL v3.0. See the [LICENSE](./LICENSE) file for details.
|
This project is licensed under the GPL v3.0. See the [LICENSE](./LICENSE) file for details.
|
||||||
|
@ -21,3 +21,12 @@ Thank you to the following contributors, who have helped make Amfora great. FOSS
|
|||||||
* Himanshu (@singalhimanshu)
|
* Himanshu (@singalhimanshu)
|
||||||
* @regr4
|
* @regr4
|
||||||
* Anas Mohamed (@amohamed11)
|
* Anas Mohamed (@amohamed11)
|
||||||
|
* David Jimenez (@dvejmz)
|
||||||
|
* Michael McDonagh (@m-mcdonagh)
|
||||||
|
* mooff (@awfulcooking)
|
||||||
|
* Josias (@justjosias)
|
||||||
|
* mntn (@mntn-xyz)
|
||||||
|
* Maxime Bouillot (@Arkaeriit)
|
||||||
|
* Emily (@emily-is-my-username)
|
||||||
|
* Autumn! (@autumnull)
|
||||||
|
* William Rehwinkel (@FiskFan1999)
|
||||||
|
70
amfora.go
70
amfora.go
@ -2,26 +2,35 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/makeworld-the-better-one/amfora/bookmarks"
|
"github.com/makeworld-the-better-one/amfora/bookmarks"
|
||||||
"github.com/makeworld-the-better-one/amfora/client"
|
"github.com/makeworld-the-better-one/amfora/client"
|
||||||
"github.com/makeworld-the-better-one/amfora/config"
|
"github.com/makeworld-the-better-one/amfora/config"
|
||||||
"github.com/makeworld-the-better-one/amfora/display"
|
"github.com/makeworld-the-better-one/amfora/display"
|
||||||
|
"github.com/makeworld-the-better-one/amfora/logger"
|
||||||
"github.com/makeworld-the-better-one/amfora/subscriptions"
|
"github.com/makeworld-the-better-one/amfora/subscriptions"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
version = "v1.8.0"
|
version = "v1.10.0"
|
||||||
commit = "unknown"
|
commit = "unknown"
|
||||||
builtBy = "unknown"
|
builtBy = "unknown"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// err := logger.Init()
|
log, err := logger.GetLogger()
|
||||||
// if err != nil {
|
if err != nil {
|
||||||
// panic(err)
|
panic(err)
|
||||||
// }
|
}
|
||||||
|
|
||||||
|
debugModeEnabled := os.Getenv("AMFORA_DEBUG") == "1"
|
||||||
|
if debugModeEnabled {
|
||||||
|
log.Println("Debug mode enabled")
|
||||||
|
}
|
||||||
|
|
||||||
if len(os.Args) > 1 {
|
if len(os.Args) > 1 {
|
||||||
if os.Args[1] == "--version" || os.Args[1] == "-v" {
|
if os.Args[1] == "--version" || os.Args[1] == "-v" {
|
||||||
@ -40,12 +49,17 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := config.Init()
|
err = config.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Config error: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Config error: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
client.Init()
|
|
||||||
|
err = client.Init()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Client error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
err = subscriptions.Init()
|
err = subscriptions.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -65,9 +79,30 @@ func main() {
|
|||||||
|
|
||||||
// Initialize Amfora's settings
|
// Initialize Amfora's settings
|
||||||
display.Init(version, commit, builtBy)
|
display.Init(version, commit, builtBy)
|
||||||
display.NewTab()
|
|
||||||
|
// Load a URL, file, or render from stdin
|
||||||
if len(os.Args[1:]) > 0 {
|
if len(os.Args[1:]) > 0 {
|
||||||
display.URL(os.Args[1])
|
url := os.Args[1]
|
||||||
|
if !strings.Contains(url, "://") || strings.HasPrefix(url, "../") || strings.HasPrefix(url, "./") {
|
||||||
|
fileName := url
|
||||||
|
if _, err := os.Stat(fileName); err == nil {
|
||||||
|
if !strings.HasPrefix(fileName, "/") {
|
||||||
|
cwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "error getting working directory path: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fileName = filepath.Join(cwd, fileName)
|
||||||
|
}
|
||||||
|
url = "file://" + fileName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
display.NewTabWithURL(url)
|
||||||
|
} else if !isStdinEmpty() {
|
||||||
|
display.NewTab()
|
||||||
|
renderFromStdin()
|
||||||
|
} else {
|
||||||
|
display.NewTab()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start
|
// Start
|
||||||
@ -75,3 +110,20 @@ func main() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isStdinEmpty() bool {
|
||||||
|
stat, _ := os.Stdin.Stat()
|
||||||
|
return (stat.Mode() & os.ModeCharDevice) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderFromStdin() {
|
||||||
|
stdinTextBuilder := new(strings.Builder)
|
||||||
|
_, err := io.Copy(stdinTextBuilder, os.Stdin)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "error reading from standard input: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
stdinText := stdinTextBuilder.String()
|
||||||
|
display.RenderFromString(stdinText)
|
||||||
|
}
|
||||||
|
@ -61,7 +61,6 @@ func Init() error {
|
|||||||
|
|
||||||
err = os.Remove(config.OldBkmkPath)
|
err = os.Remove(config.OldBkmkPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//nolint:goerr113
|
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"couldn't delete old bookmarks file (%s), you must delete it yourself to prevent duplicate bookmarks: %w",
|
"couldn't delete old bookmarks file (%s), you must delete it yourself to prevent duplicate bookmarks: %w",
|
||||||
config.OldBkmkPath,
|
config.OldBkmkPath,
|
||||||
|
111
client/client.go
111
client/client.go
@ -2,51 +2,128 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/makeworld-the-better-one/go-gemini"
|
"github.com/makeworld-the-better-one/go-gemini"
|
||||||
|
gemsocks5 "github.com/makeworld-the-better-one/go-gemini-socks5"
|
||||||
"github.com/mitchellh/go-homedir"
|
"github.com/mitchellh/go-homedir"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Simple key for certCache map and others, instead of a full URL
|
||||||
|
// Only uses the part of the URL relevant to matching certs to a URL
|
||||||
|
type certMapKey struct {
|
||||||
|
host string
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
certCache = make(map[string][][]byte)
|
// [auth] section of config put into maps
|
||||||
|
confCerts = make(map[certMapKey]string)
|
||||||
|
confKeys = make(map[certMapKey]string)
|
||||||
|
|
||||||
|
// Cache the cert and key assigned to different URLs
|
||||||
|
certCache = make(map[certMapKey][][]byte)
|
||||||
certCacheMu = &sync.RWMutex{}
|
certCacheMu = &sync.RWMutex{}
|
||||||
|
|
||||||
fetchClient *gemini.Client
|
fetchClient *gemini.Client
|
||||||
)
|
)
|
||||||
|
|
||||||
func Init() {
|
func Init() error {
|
||||||
fetchClient = &gemini.Client{
|
fetchClient = &gemini.Client{
|
||||||
ConnectTimeout: 10 * time.Second, // Default is 15
|
ConnectTimeout: 10 * time.Second, // Default is 15
|
||||||
ReadTimeout: time.Duration(viper.GetInt("a-general.page_max_time")) * time.Second,
|
ReadTimeout: time.Duration(viper.GetInt("a-general.page_max_time")) * time.Second,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if socksHost := os.Getenv("AMFORA_SOCKS5"); socksHost != "" {
|
||||||
|
fetchClient.Proxy = gemsocks5.ProxyFunc(socksHost, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate config maps
|
||||||
|
|
||||||
|
certsViper := viper.Sub("auth.certs")
|
||||||
|
for _, certURL := range certsViper.AllKeys() {
|
||||||
|
// Normalize URL so that it can be matched no matter how it was written
|
||||||
|
// in the config
|
||||||
|
pu, _ := normalizeURL(FixUserURL(certURL))
|
||||||
|
if pu == nil {
|
||||||
|
//nolint:goerr113
|
||||||
|
return errors.New("[auth.certs]: couldn't normalize URL: " + certURL)
|
||||||
|
}
|
||||||
|
confCerts[certMapKey{pu.Host, pu.Path}] = certsViper.GetString(certURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
keysViper := viper.Sub("auth.keys")
|
||||||
|
for _, keyURL := range keysViper.AllKeys() {
|
||||||
|
pu, _ := normalizeURL(FixUserURL(keyURL))
|
||||||
|
if pu == nil {
|
||||||
|
//nolint:goerr113
|
||||||
|
return errors.New("[auth.keys]: couldn't normalize URL: " + keyURL)
|
||||||
|
}
|
||||||
|
confKeys[certMapKey{pu.Host, pu.Path}] = keysViper.GetString(keyURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func clientCert(host string) ([]byte, []byte) {
|
// getCertPath returns the path of the cert from the config.
|
||||||
|
// It returns "" if no config value exists.
|
||||||
|
func getCertPath(host string, path string) string {
|
||||||
|
for k, v := range confCerts {
|
||||||
|
if k.host == host && (k.path == path || strings.HasPrefix(path, k.path)) {
|
||||||
|
// Either exact match to what's in config, or a subpath
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// No matches
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// getKeyPath returns the path of the key from the config.
|
||||||
|
// It returns "" if no config value exists.
|
||||||
|
func getKeyPath(host string, path string) string {
|
||||||
|
for k, v := range confKeys {
|
||||||
|
if k.host == host && (k.path == path || strings.HasPrefix(path, k.path)) {
|
||||||
|
// Either exact match to what's in config, or a subpath
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// No matches
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func clientCert(host string, path string) ([]byte, []byte) {
|
||||||
|
mkey := certMapKey{host, path}
|
||||||
|
|
||||||
certCacheMu.RLock()
|
certCacheMu.RLock()
|
||||||
pair, ok := certCache[host]
|
pair, ok := certCache[mkey]
|
||||||
certCacheMu.RUnlock()
|
certCacheMu.RUnlock()
|
||||||
if ok {
|
if ok {
|
||||||
return pair[0], pair[1]
|
return pair[0], pair[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ogCertPath := getCertPath(host, path)
|
||||||
// Expand paths starting with ~/
|
// Expand paths starting with ~/
|
||||||
certPath, err := homedir.Expand(viper.GetString("auth.certs." + host))
|
certPath, err := homedir.Expand(ogCertPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
certPath = viper.GetString("auth.certs." + host)
|
certPath = ogCertPath
|
||||||
}
|
}
|
||||||
keyPath, err := homedir.Expand(viper.GetString("auth.keys." + host))
|
ogKeyPath := getKeyPath(host, path)
|
||||||
|
keyPath, err := homedir.Expand(ogKeyPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
keyPath = viper.GetString("auth.keys." + host)
|
keyPath = ogKeyPath
|
||||||
}
|
}
|
||||||
|
|
||||||
if certPath == "" && keyPath == "" {
|
if certPath == "" && keyPath == "" {
|
||||||
certCacheMu.Lock()
|
certCacheMu.Lock()
|
||||||
certCache[host] = [][]byte{nil, nil}
|
certCache[mkey] = [][]byte{nil, nil}
|
||||||
certCacheMu.Unlock()
|
certCacheMu.Unlock()
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@ -54,33 +131,33 @@ func clientCert(host string) ([]byte, []byte) {
|
|||||||
cert, err := ioutil.ReadFile(certPath)
|
cert, err := ioutil.ReadFile(certPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
certCacheMu.Lock()
|
certCacheMu.Lock()
|
||||||
certCache[host] = [][]byte{nil, nil}
|
certCache[mkey] = [][]byte{nil, nil}
|
||||||
certCacheMu.Unlock()
|
certCacheMu.Unlock()
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
key, err := ioutil.ReadFile(keyPath)
|
key, err := ioutil.ReadFile(keyPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
certCacheMu.Lock()
|
certCacheMu.Lock()
|
||||||
certCache[host] = [][]byte{nil, nil}
|
certCache[mkey] = [][]byte{nil, nil}
|
||||||
certCacheMu.Unlock()
|
certCacheMu.Unlock()
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
certCacheMu.Lock()
|
certCacheMu.Lock()
|
||||||
certCache[host] = [][]byte{cert, key}
|
certCache[mkey] = [][]byte{cert, key}
|
||||||
certCacheMu.Unlock()
|
certCacheMu.Unlock()
|
||||||
return cert, key
|
return cert, key
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasClientCert returns whether or not a client certificate exists for a host.
|
// HasClientCert returns whether or not a client certificate exists for a host and path.
|
||||||
func HasClientCert(host string) bool {
|
func HasClientCert(host string, path string) bool {
|
||||||
cert, _ := clientCert(host)
|
cert, _ := clientCert(host, path)
|
||||||
return cert != nil
|
return cert != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetch(u string, c *gemini.Client) (*gemini.Response, error) {
|
func fetch(u string, c *gemini.Client) (*gemini.Response, error) {
|
||||||
parsed, _ := url.Parse(u)
|
parsed, _ := url.Parse(u)
|
||||||
cert, key := clientCert(parsed.Host)
|
cert, key := clientCert(parsed.Host, parsed.Path)
|
||||||
|
|
||||||
var res *gemini.Response
|
var res *gemini.Response
|
||||||
var err error
|
var err error
|
||||||
@ -109,7 +186,7 @@ func Fetch(u string) (*gemini.Response, error) {
|
|||||||
|
|
||||||
func fetchWithProxy(proxyHostname, proxyPort, u string, c *gemini.Client) (*gemini.Response, error) {
|
func fetchWithProxy(proxyHostname, proxyPort, u string, c *gemini.Client) (*gemini.Response, error) {
|
||||||
parsed, _ := url.Parse(u)
|
parsed, _ := url.Parse(u)
|
||||||
cert, key := clientCert(parsed.Host)
|
cert, key := clientCert(parsed.Host, parsed.Path)
|
||||||
|
|
||||||
var res *gemini.Response
|
var res *gemini.Response
|
||||||
var err error
|
var err error
|
||||||
|
@ -62,7 +62,6 @@ func loadTofuEntry(domain string, port string) (string, time.Time, error) {
|
|||||||
return id, expiry, nil
|
return id, expiry, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:errcheck
|
|
||||||
// certID returns a generic string representing a cert or domain.
|
// certID returns a generic string representing a cert or domain.
|
||||||
func certID(cert *x509.Certificate) string {
|
func certID(cert *x509.Certificate) string {
|
||||||
h := sha256.New()
|
h := sha256.New()
|
||||||
@ -73,7 +72,7 @@ func certID(cert *x509.Certificate) string {
|
|||||||
// origCertID uses cert.Raw, which was used in v1.0.0 of the app.
|
// origCertID uses cert.Raw, which was used in v1.0.0 of the app.
|
||||||
func origCertID(cert *x509.Certificate) string {
|
func origCertID(cert *x509.Certificate) string {
|
||||||
h := sha256.New()
|
h := sha256.New()
|
||||||
h.Write(cert.Raw) //nolint:errcheck
|
h.Write(cert.Raw)
|
||||||
return fmt.Sprintf("%X", h.Sum(nil))
|
return fmt.Sprintf("%X", h.Sum(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
97
client/url.go
Normal file
97
client/url.go
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
// Functions that transform and normalize URLs
|
||||||
|
// Originally used to be in display/util.go
|
||||||
|
// Moved here for #115, so URLs in the [auth] config section could be normalized
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/makeworld-the-better-one/go-gemini"
|
||||||
|
"golang.org/x/text/unicode/norm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// See doc for NormalizeURL
|
||||||
|
func normalizeURL(u string) (*url.URL, string) {
|
||||||
|
u = norm.NFC.String(u)
|
||||||
|
|
||||||
|
tmp, err := gemini.GetPunycodeURL(u)
|
||||||
|
if err != nil {
|
||||||
|
return nil, u
|
||||||
|
}
|
||||||
|
u = tmp
|
||||||
|
parsed, _ := url.Parse(u)
|
||||||
|
|
||||||
|
if parsed.Scheme == "" {
|
||||||
|
// Always add scheme
|
||||||
|
parsed.Scheme = "gemini"
|
||||||
|
} else if parsed.Scheme != "gemini" {
|
||||||
|
// Not a gemini URL, nothing to do
|
||||||
|
return nil, u
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed.User = nil // No passwords in Gemini
|
||||||
|
parsed.Fragment = "" // No fragments either
|
||||||
|
if parsed.Port() == "1965" {
|
||||||
|
// Always remove default port
|
||||||
|
hostname := parsed.Hostname()
|
||||||
|
if strings.Contains(hostname, ":") {
|
||||||
|
parsed.Host = "[" + parsed.Hostname() + "]"
|
||||||
|
} else {
|
||||||
|
parsed.Host = parsed.Hostname()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add slash to the end of a URL with just a domain
|
||||||
|
// gemini://example.com -> gemini://example.com/
|
||||||
|
if parsed.Path == "" {
|
||||||
|
parsed.Path = "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do the same to the query string
|
||||||
|
un, err := gemini.QueryUnescape(parsed.RawQuery)
|
||||||
|
if err == nil {
|
||||||
|
parsed.RawQuery = gemini.QueryEscape(un)
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// NormalizeURL attempts to make URLs that are different strings
|
||||||
|
// but point to the same place all look the same.
|
||||||
|
//
|
||||||
|
// Example: gemini://gus.guru:1965/ and //gus.guru/.
|
||||||
|
// This function will take both output the same URL each time.
|
||||||
|
//
|
||||||
|
// It will also percent-encode invalid characters, and decode chars
|
||||||
|
// that don't need to be encoded. It will also apply Unicode NFC
|
||||||
|
// normalization.
|
||||||
|
//
|
||||||
|
// The string passed must already be confirmed to be a URL.
|
||||||
|
// Detection of a search string vs. a URL must happen elsewhere.
|
||||||
|
//
|
||||||
|
// It only works with absolute URLs.
|
||||||
|
func NormalizeURL(u string) string {
|
||||||
|
pu, s := normalizeURL(u)
|
||||||
|
if pu != nil {
|
||||||
|
// Could be normalized, return it
|
||||||
|
return pu.String()
|
||||||
|
}
|
||||||
|
// Return the best URL available up to that point
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// FixUserURL will take a user-typed URL and add a gemini scheme to it if
|
||||||
|
// necessary. It is not the same as normalizeURL, and that func should still
|
||||||
|
// be used, afterward.
|
||||||
|
//
|
||||||
|
// For example "example.com" will become "gemini://example.com", but
|
||||||
|
// "//example.com" will be left untouched.
|
||||||
|
func FixUserURL(u string) string {
|
||||||
|
if !strings.HasPrefix(u, "//") && !strings.HasPrefix(u, "gemini://") && !strings.Contains(u, "://") {
|
||||||
|
// Assume it's a Gemini URL
|
||||||
|
u = "gemini://" + u
|
||||||
|
}
|
||||||
|
return u
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
//nolint: lll
|
//nolint: lll
|
||||||
package display
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
@ -23,9 +23,10 @@ var normalizeURLTests = []struct {
|
|||||||
{"magnet:?xt=urn:btih:test", "magnet:?xt=urn:btih:test"},
|
{"magnet:?xt=urn:btih:test", "magnet:?xt=urn:btih:test"},
|
||||||
{"https://example.com", "https://example.com"},
|
{"https://example.com", "https://example.com"},
|
||||||
// Fixing URL tests
|
// Fixing URL tests
|
||||||
{"gemini://gemini.circumlunar.space/%64%6f%63%73/%66%61%71%2e%67%6d%69", "gemini://gemini.circumlunar.space/docs/faq.gmi"},
|
// Some commented out due to #324
|
||||||
|
//{"gemini://geminiprotocol.net/%64%6f%63%73/%66%61%71%2e%67%6d%69", "gemini://geminiprotocol.net/docs/faq.gmi"},
|
||||||
{"gemini://example.com/蛸", "gemini://example.com/%E8%9B%B8"},
|
{"gemini://example.com/蛸", "gemini://example.com/%E8%9B%B8"},
|
||||||
{"gemini://gemini.circumlunar.space/%64%6f%63%73/;;.'%66%61%71蛸%2e%67%6d%69", "gemini://gemini.circumlunar.space/docs/%3B%3B.%27faq%E8%9B%B8.gmi"},
|
//{"gemini://geminiprotocol.net/%64%6f%63%73/;;.'%66%61%71蛸%2e%67%6d%69", "gemini://geminiprotocol.net/docs/%3B%3B.%27faq%E8%9B%B8.gmi"},
|
||||||
{"gemini://example.com/?%2Ch%64ello蛸", "gemini://example.com/?%2Chdello%E8%9B%B8"},
|
{"gemini://example.com/?%2Ch%64ello蛸", "gemini://example.com/?%2Chdello%E8%9B%B8"},
|
||||||
// IPv6 tests, see #195
|
// IPv6 tests, see #195
|
||||||
{"gemini://[::1]", "gemini://[::1]/"},
|
{"gemini://[::1]", "gemini://[::1]/"},
|
||||||
@ -36,7 +37,7 @@ var normalizeURLTests = []struct {
|
|||||||
|
|
||||||
func TestNormalizeURL(t *testing.T) {
|
func TestNormalizeURL(t *testing.T) {
|
||||||
for _, tt := range normalizeURLTests {
|
for _, tt := range normalizeURLTests {
|
||||||
actual := normalizeURL(tt.u)
|
actual := NormalizeURL(tt.u)
|
||||||
if actual != tt.expected {
|
if actual != tt.expected {
|
||||||
t.Errorf("normalizeURL(%s): expected %s, actual %s", tt.u, tt.expected, actual)
|
t.Errorf("normalizeURL(%s): expected %s, actual %s", tt.u, tt.expected, actual)
|
||||||
}
|
}
|
106
config/config.go
106
config/config.go
@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
"github.com/makeworld-the-better-one/amfora/cache"
|
"github.com/makeworld-the-better-one/amfora/cache"
|
||||||
homedir "github.com/mitchellh/go-homedir"
|
homedir "github.com/mitchellh/go-homedir"
|
||||||
|
"github.com/muesli/termenv"
|
||||||
"github.com/rkoesters/xdg/basedir"
|
"github.com/rkoesters/xdg/basedir"
|
||||||
"github.com/rkoesters/xdg/userdirs"
|
"github.com/rkoesters/xdg/userdirs"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
@ -59,9 +60,16 @@ var MediaHandlers = make(map[string]MediaHandler)
|
|||||||
// Defaults to ScrollBarAuto on an invalid value
|
// Defaults to ScrollBarAuto on an invalid value
|
||||||
var ScrollBar cview.ScrollBarVisibility
|
var ScrollBar cview.ScrollBarVisibility
|
||||||
|
|
||||||
|
// Whether the user's terminal is dark or light
|
||||||
|
// Defaults to dark, but is determined in Init()
|
||||||
|
// Used to prevent white text on a white background with the default theme
|
||||||
|
var hasDarkTerminalBackground bool
|
||||||
|
|
||||||
func Init() error {
|
func Init() error {
|
||||||
|
|
||||||
// *** Set paths ***
|
// *** Set paths ***
|
||||||
|
// Windows uses paths under APPDATA, Unix systems use XDG paths
|
||||||
|
// Windows systems use XDG paths if variables are defined, see #255
|
||||||
|
|
||||||
home, err := homedir.Dir()
|
home, err := homedir.Dir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -78,10 +86,10 @@ func Init() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Store config directory and file paths
|
// Store config directory and file paths
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" && os.Getenv("XDG_CONFIG_HOME") == "" {
|
||||||
configDir = amforaAppData
|
configDir = amforaAppData
|
||||||
} else {
|
} else {
|
||||||
// Unix / POSIX system
|
// Unix / POSIX system, or Windows with XDG_CONFIG_HOME defined
|
||||||
configDir = filepath.Join(basedir.ConfigHome, "amfora")
|
configDir = filepath.Join(basedir.ConfigHome, "amfora")
|
||||||
}
|
}
|
||||||
configPath = filepath.Join(configDir, "config.toml")
|
configPath = filepath.Join(configDir, "config.toml")
|
||||||
@ -94,7 +102,7 @@ func Init() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Store TOFU db directory and file paths
|
// Store TOFU db directory and file paths
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" && os.Getenv("XDG_CACHE_HOME") == "" {
|
||||||
// Windows just stores it in APPDATA along with other stuff
|
// Windows just stores it in APPDATA along with other stuff
|
||||||
tofuDBDir = amforaAppData
|
tofuDBDir = amforaAppData
|
||||||
} else {
|
} else {
|
||||||
@ -104,7 +112,7 @@ func Init() error {
|
|||||||
tofuDBPath = filepath.Join(tofuDBDir, "tofu.toml")
|
tofuDBPath = filepath.Join(tofuDBDir, "tofu.toml")
|
||||||
|
|
||||||
// Store bookmarks dir and path
|
// Store bookmarks dir and path
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" && os.Getenv("XDG_DATA_HOME") == "" {
|
||||||
// Windows just keeps it in APPDATA along with other Amfora files
|
// Windows just keeps it in APPDATA along with other Amfora files
|
||||||
bkmkDir = amforaAppData
|
bkmkDir = amforaAppData
|
||||||
} else {
|
} else {
|
||||||
@ -115,18 +123,12 @@ func Init() error {
|
|||||||
BkmkPath = filepath.Join(bkmkDir, "bookmarks.xml")
|
BkmkPath = filepath.Join(bkmkDir, "bookmarks.xml")
|
||||||
|
|
||||||
// Feeds dir and path
|
// Feeds dir and path
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" && os.Getenv("XDG_DATA_HOME") == "" {
|
||||||
// In APPDATA beside other Amfora files
|
// In APPDATA beside other Amfora files
|
||||||
subscriptionDir = amforaAppData
|
subscriptionDir = amforaAppData
|
||||||
} else {
|
} else {
|
||||||
// XDG data dir on POSIX systems
|
// XDG data dir on POSIX systems
|
||||||
xdg_data, ok := os.LookupEnv("XDG_DATA_HOME")
|
subscriptionDir = filepath.Join(basedir.DataHome, "amfora")
|
||||||
if ok && strings.TrimSpace(xdg_data) != "" {
|
|
||||||
subscriptionDir = filepath.Join(xdg_data, "amfora")
|
|
||||||
} else {
|
|
||||||
// Default to ~/.local/share/amfora
|
|
||||||
subscriptionDir = filepath.Join(home, ".local", "share", "amfora")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
SubscriptionPath = filepath.Join(subscriptionDir, "subscriptions.json")
|
SubscriptionPath = filepath.Join(subscriptionDir, "subscriptions.json")
|
||||||
|
|
||||||
@ -188,21 +190,23 @@ func Init() error {
|
|||||||
|
|
||||||
// Setup main config
|
// Setup main config
|
||||||
|
|
||||||
viper.SetDefault("a-general.home", "gemini://gemini.circumlunar.space")
|
viper.SetDefault("a-general.home", "gemini://geminiprotocol.net")
|
||||||
viper.SetDefault("a-general.auto_redirect", false)
|
viper.SetDefault("a-general.auto_redirect", false)
|
||||||
viper.SetDefault("a-general.http", "default")
|
viper.SetDefault("a-general.http", "default")
|
||||||
viper.SetDefault("a-general.search", "gemini://geminispace.info/search")
|
viper.SetDefault("a-general.search", "gemini://geminispace.info/search")
|
||||||
viper.SetDefault("a-general.color", true)
|
viper.SetDefault("a-general.color", true)
|
||||||
viper.SetDefault("a-general.ansi", true)
|
viper.SetDefault("a-general.ansi", true)
|
||||||
|
viper.SetDefault("a-general.highlight_code", true)
|
||||||
|
viper.SetDefault("a-general.highlight_style", "monokai")
|
||||||
viper.SetDefault("a-general.bullets", true)
|
viper.SetDefault("a-general.bullets", true)
|
||||||
viper.SetDefault("a-general.show_link", false)
|
viper.SetDefault("a-general.show_link", false)
|
||||||
viper.SetDefault("a-general.left_margin", 0.15)
|
viper.SetDefault("a-general.max_width", 80)
|
||||||
viper.SetDefault("a-general.max_width", 100)
|
|
||||||
viper.SetDefault("a-general.downloads", "")
|
viper.SetDefault("a-general.downloads", "")
|
||||||
viper.SetDefault("a-general.temp_downloads", "")
|
viper.SetDefault("a-general.temp_downloads", "")
|
||||||
viper.SetDefault("a-general.page_max_size", 2097152)
|
viper.SetDefault("a-general.page_max_size", 2097152)
|
||||||
viper.SetDefault("a-general.page_max_time", 10)
|
viper.SetDefault("a-general.page_max_time", 10)
|
||||||
viper.SetDefault("a-general.scrollbar", "auto")
|
viper.SetDefault("a-general.scrollbar", "auto")
|
||||||
|
viper.SetDefault("a-general.underline", true)
|
||||||
viper.SetDefault("keybindings.bind_reload", []string{"R", "Ctrl-R"})
|
viper.SetDefault("keybindings.bind_reload", []string{"R", "Ctrl-R"})
|
||||||
viper.SetDefault("keybindings.bind_home", "Backspace")
|
viper.SetDefault("keybindings.bind_home", "Backspace")
|
||||||
viper.SetDefault("keybindings.bind_bookmarks", "Ctrl-B")
|
viper.SetDefault("keybindings.bind_bookmarks", "Ctrl-B")
|
||||||
@ -224,7 +228,7 @@ func Init() error {
|
|||||||
viper.SetDefault("keybindings.bind_close_tab", "Ctrl-W")
|
viper.SetDefault("keybindings.bind_close_tab", "Ctrl-W")
|
||||||
viper.SetDefault("keybindings.bind_next_tab", "F2")
|
viper.SetDefault("keybindings.bind_next_tab", "F2")
|
||||||
viper.SetDefault("keybindings.bind_prev_tab", "F1")
|
viper.SetDefault("keybindings.bind_prev_tab", "F1")
|
||||||
viper.SetDefault("keybindings.bind_quit", []string{"Ctrl-C", "Ctrl-Q", "q"})
|
viper.SetDefault("keybindings.bind_quit", []string{"Ctrl-C", "Ctrl-Q", "Q"})
|
||||||
viper.SetDefault("keybindings.bind_help", "?")
|
viper.SetDefault("keybindings.bind_help", "?")
|
||||||
viper.SetDefault("keybindings.bind_link1", "1")
|
viper.SetDefault("keybindings.bind_link1", "1")
|
||||||
viper.SetDefault("keybindings.bind_link2", "2")
|
viper.SetDefault("keybindings.bind_link2", "2")
|
||||||
@ -252,9 +256,11 @@ func Init() error {
|
|||||||
viper.SetDefault("keybindings.bind_end", []string{"End", "G"})
|
viper.SetDefault("keybindings.bind_end", []string{"End", "G"})
|
||||||
viper.SetDefault("keybindings.bind_search", "/")
|
viper.SetDefault("keybindings.bind_search", "/")
|
||||||
viper.SetDefault("keybindings.bind_next_match", "n")
|
viper.SetDefault("keybindings.bind_next_match", "n")
|
||||||
viper.SetDefault("keybindings.bind_prev_match", "p")
|
viper.SetDefault("keybindings.bind_prev_match", "N")
|
||||||
viper.SetDefault("keybindings.shift_numbers", "")
|
viper.SetDefault("keybindings.shift_numbers", "")
|
||||||
viper.SetDefault("url-handlers.other", "off")
|
viper.SetDefault("keybindings.bind_url_handler_open", "Ctrl-U")
|
||||||
|
viper.SetDefault("url-handlers.other", "default")
|
||||||
|
viper.SetDefault("url-prompts.other", false)
|
||||||
viper.SetDefault("cache.max_size", 0)
|
viper.SetDefault("cache.max_size", 0)
|
||||||
viper.SetDefault("cache.max_pages", 20)
|
viper.SetDefault("cache.max_pages", 20)
|
||||||
viper.SetDefault("cache.timeout", 1800)
|
viper.SetDefault("cache.timeout", 1800)
|
||||||
@ -262,6 +268,7 @@ func Init() error {
|
|||||||
viper.SetDefault("subscriptions.update_interval", 1800)
|
viper.SetDefault("subscriptions.update_interval", 1800)
|
||||||
viper.SetDefault("subscriptions.workers", 3)
|
viper.SetDefault("subscriptions.workers", 3)
|
||||||
viper.SetDefault("subscriptions.entries_per_page", 20)
|
viper.SetDefault("subscriptions.entries_per_page", 20)
|
||||||
|
viper.SetDefault("subscriptions.header", true)
|
||||||
|
|
||||||
viper.SetConfigFile(configPath)
|
viper.SetConfigFile(configPath)
|
||||||
viper.SetConfigType("toml")
|
viper.SetConfigType("toml")
|
||||||
@ -344,24 +351,77 @@ func Init() error {
|
|||||||
cache.SetMaxPages(viper.GetInt("cache.max_pages"))
|
cache.SetMaxPages(viper.GetInt("cache.max_pages"))
|
||||||
cache.SetTimeout(viper.GetInt("cache.timeout"))
|
cache.SetTimeout(viper.GetInt("cache.timeout"))
|
||||||
|
|
||||||
|
setColor := func(k string, colorStr string) error {
|
||||||
|
if k == "include" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
colorStr = strings.ToLower(colorStr)
|
||||||
|
var color tcell.Color
|
||||||
|
if colorStr == "default" {
|
||||||
|
if strings.HasSuffix(k, "bg") {
|
||||||
|
color = tcell.ColorDefault
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf(`"default" is only valid for a background color (color ending in "bg"), not "%s"`, k)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
color = tcell.GetColor(colorStr)
|
||||||
|
if color == tcell.ColorDefault {
|
||||||
|
return fmt.Errorf(`invalid color format for "%s": %s`, k, colorStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SetColor(k, color)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Setup theme
|
// Setup theme
|
||||||
configTheme := viper.Sub("theme")
|
configTheme := viper.Sub("theme")
|
||||||
if configTheme != nil {
|
if configTheme != nil {
|
||||||
|
// Include key comes first
|
||||||
|
if incPath := configTheme.GetString("include"); incPath != "" {
|
||||||
|
incViper := viper.New()
|
||||||
|
newIncPath, err := homedir.Expand(incPath)
|
||||||
|
if err == nil {
|
||||||
|
incViper.SetConfigFile(newIncPath)
|
||||||
|
} else {
|
||||||
|
incViper.SetConfigFile(incPath)
|
||||||
|
}
|
||||||
|
incViper.SetConfigType("toml")
|
||||||
|
err = incViper.ReadInConfig()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for k2, v2 := range incViper.AllSettings() {
|
||||||
|
colorStr, ok := v2.(string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf(`include: value for "%s" is not a string: %v`, k2, v2)
|
||||||
|
}
|
||||||
|
if err := setColor(k2, colorStr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
for k, v := range configTheme.AllSettings() {
|
for k, v := range configTheme.AllSettings() {
|
||||||
colorStr, ok := v.(string)
|
colorStr, ok := v.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf(`value for "%s" is not a string: %v`, k, v)
|
return fmt.Errorf(`value for "%s" is not a string: %v`, k, v)
|
||||||
}
|
}
|
||||||
color := tcell.GetColor(strings.ToLower(colorStr))
|
if err := setColor(k, colorStr); err != nil {
|
||||||
if color == tcell.ColorDefault {
|
return err
|
||||||
return fmt.Errorf(`invalid color format for "%s": %s`, k, colorStr)
|
|
||||||
}
|
}
|
||||||
SetColor(k, color)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if viper.GetBool("a-general.color") {
|
if viper.GetBool("a-general.color") {
|
||||||
cview.Styles.PrimitiveBackgroundColor = GetColor("bg")
|
cview.Styles.PrimitiveBackgroundColor = GetColor("bg")
|
||||||
} // Otherwise it's black by default
|
} else {
|
||||||
|
// No colors allowed, set background to black instead of default
|
||||||
|
themeMu.Lock()
|
||||||
|
theme["bg"] = tcell.ColorBlack
|
||||||
|
cview.Styles.PrimitiveBackgroundColor = tcell.ColorBlack
|
||||||
|
themeMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
hasDarkTerminalBackground = termenv.HasDarkBackground()
|
||||||
|
|
||||||
// Parse HTTP command
|
// Parse HTTP command
|
||||||
HTTPCommand = viper.GetStringSlice("a-general.http")
|
HTTPCommand = viper.GetStringSlice("a-general.http")
|
||||||
|
@ -3,6 +3,17 @@ package config
|
|||||||
//go:generate ./default.sh
|
//go:generate ./default.sh
|
||||||
var defaultConf = []byte(`# This is the default config file.
|
var defaultConf = []byte(`# This is the default config file.
|
||||||
# It also shows all the default values, if you don't create the file.
|
# It also shows all the default values, if you don't create the file.
|
||||||
|
# You can edit this file to set your own configuration for Amfora.
|
||||||
|
|
||||||
|
# When Amfora updates, defaults may change, but this file on your drive will not.
|
||||||
|
# You can always get the latest defaults on GitHub.
|
||||||
|
# https://github.com/makeworld-the-better-one/amfora/blob/master/default-config.toml
|
||||||
|
|
||||||
|
# Please also check out the Amfora Wiki for more help
|
||||||
|
# https://github.com/makeworld-the-better-one/amfora/wiki
|
||||||
|
# gemini://makeworld.space/amfora-wiki/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# All URL values may omit the scheme and/or port, as well as the beginning double slash
|
# All URL values may omit the scheme and/or port, as well as the beginning double slash
|
||||||
# Valid URL examples:
|
# Valid URL examples:
|
||||||
@ -14,7 +25,7 @@ var defaultConf = []byte(`# This is the default config file.
|
|||||||
|
|
||||||
[a-general]
|
[a-general]
|
||||||
# Press Ctrl-H to access it
|
# Press Ctrl-H to access it
|
||||||
home = "gemini://gemini.circumlunar.space"
|
home = "gemini://geminiprotocol.net"
|
||||||
|
|
||||||
# Follow up to 5 Gemini redirects without prompting.
|
# Follow up to 5 Gemini redirects without prompting.
|
||||||
# A prompt is always shown after the 5th redirect and for redirects to protocols other than Gemini.
|
# A prompt is always shown after the 5th redirect and for redirects to protocols other than Gemini.
|
||||||
@ -26,7 +37,7 @@ auto_redirect = false
|
|||||||
# If a command is set, than the URL will be added (in quotes) to the end of the command.
|
# If a command is set, than the URL will be added (in quotes) to the end of the command.
|
||||||
# A space will be prepended to the URL.
|
# A space will be prepended to the URL.
|
||||||
#
|
#
|
||||||
# The best to define a command is using a string array.
|
# The best way to define a command is using a string array.
|
||||||
# Examples:
|
# Examples:
|
||||||
# http = ['firefox']
|
# http = ['firefox']
|
||||||
# http = ['custom-browser', '--flag', '--option=2']
|
# http = ['custom-browser', '--flag', '--option=2']
|
||||||
@ -47,17 +58,20 @@ color = true
|
|||||||
# Whether ANSI color codes from the page content should be rendered
|
# Whether ANSI color codes from the page content should be rendered
|
||||||
ansi = true
|
ansi = true
|
||||||
|
|
||||||
|
# Whether or not to support source code highlighting in preformatted blocks based on alt text
|
||||||
|
highlight_code = true
|
||||||
|
|
||||||
|
# Which highlighting style to use (see https://xyproto.github.io/splash/docs/)
|
||||||
|
highlight_style = "monokai"
|
||||||
|
|
||||||
# Whether to replace list asterisks with unicode bullets
|
# Whether to replace list asterisks with unicode bullets
|
||||||
bullets = true
|
bullets = true
|
||||||
|
|
||||||
# Whether to show link after link text
|
# Whether to show link after link text
|
||||||
show_link = false
|
show_link = false
|
||||||
|
|
||||||
# A number from 0 to 1, indicating what percentage of the terminal width the left margin should take up.
|
|
||||||
left_margin = 0.15
|
|
||||||
|
|
||||||
# The max number of columns to wrap a page's text to. Preformatted blocks are not wrapped.
|
# The max number of columns to wrap a page's text to. Preformatted blocks are not wrapped.
|
||||||
max_width = 100
|
max_width = 80
|
||||||
|
|
||||||
# 'downloads' is the path to a downloads folder.
|
# 'downloads' is the path to a downloads folder.
|
||||||
# An empty value means the code will find the default downloads folder for your system.
|
# An empty value means the code will find the default downloads folder for your system.
|
||||||
@ -74,6 +88,10 @@ page_max_time = 10
|
|||||||
# "auto" means the scrollbar only appears when the page is longer than the window.
|
# "auto" means the scrollbar only appears when the page is longer than the window.
|
||||||
scrollbar = "auto"
|
scrollbar = "auto"
|
||||||
|
|
||||||
|
# Underline non-gemini URLs
|
||||||
|
# This is done to help color blind users
|
||||||
|
underline = true
|
||||||
|
|
||||||
|
|
||||||
[auth]
|
[auth]
|
||||||
# Authentication settings
|
# Authentication settings
|
||||||
@ -81,13 +99,17 @@ scrollbar = "auto"
|
|||||||
|
|
||||||
[auth.certs]
|
[auth.certs]
|
||||||
# Client certificates
|
# Client certificates
|
||||||
# Set domain name equal to path to client cert
|
# Set URL equal to path to client cert file
|
||||||
# "example.com" = 'mycert.crt'
|
#
|
||||||
|
# "example.com" = 'mycert.crt' # Cert is used for all paths on this domain
|
||||||
|
# "example.com/dir/"= 'mycert.crt' # Cert is used for /dir/ and everything below only
|
||||||
|
#
|
||||||
|
# See the comment at the beginning of this file for examples of all valid types of
|
||||||
|
# URLs, ports and schemes can be used too
|
||||||
|
|
||||||
[auth.keys]
|
[auth.keys]
|
||||||
# Client certificate keys
|
# Client certificate keys
|
||||||
# Set domain name equal to path to key for the client cert above
|
# Same as [auth.certs] but the path is to the client key file.
|
||||||
# "example.com" = 'mycert.key'
|
|
||||||
|
|
||||||
|
|
||||||
[keybindings]
|
[keybindings]
|
||||||
@ -166,24 +188,53 @@ scrollbar = "auto"
|
|||||||
# bind_copy_target_url
|
# bind_copy_target_url
|
||||||
# bind_beginning: moving to beginning of page (top left)
|
# bind_beginning: moving to beginning of page (top left)
|
||||||
# bind_end: same but the for the end (bottom left)
|
# bind_end: same but the for the end (bottom left)
|
||||||
|
# bind_url_handler_open: Open highlighted URL with URL handler (#143)
|
||||||
|
|
||||||
|
# Search
|
||||||
|
# bind_search = "/"
|
||||||
|
# bind_next_match = "n"
|
||||||
|
# bind_prev_match = "N"
|
||||||
|
|
||||||
[url-handlers]
|
[url-handlers]
|
||||||
# Allows setting the commands to run for various URL schemes.
|
# Allows setting the commands to run for various URL schemes.
|
||||||
# E.g. to open FTP URLs with FileZilla set the following key:
|
# E.g. to open FTP URLs with FileZilla set the following key:
|
||||||
# ftp = 'filezilla'
|
# ftp = ['filezilla']
|
||||||
# You can set any scheme to "off" or "" to disable handling it, or
|
# You can set any scheme to 'off' or '' to disable handling it, or
|
||||||
# just leave the key unset.
|
# just leave the key unset.
|
||||||
#
|
#
|
||||||
# DO NOT use this for setting the HTTP command.
|
# DO NOT use this for setting the HTTP command.
|
||||||
# Use the http setting in the "a-general" section above.
|
# Use the http setting in the "a-general" section above.
|
||||||
#
|
#
|
||||||
# NOTE: These settings are overrided by the ones in the proxies section.
|
# NOTE: These settings are overrided by the ones in the proxies section.
|
||||||
|
#
|
||||||
|
# The best way to define a command is using a string array.
|
||||||
|
# Examples:
|
||||||
|
# magnet = ['transmission']
|
||||||
|
# foo = ['custom-browser', '--flag', '--option=2']
|
||||||
|
# tel = ['/path/with spaces/in it/telephone']
|
||||||
|
#
|
||||||
# Note the use of single quotes, so that backslashes will not be escaped.
|
# Note the use of single quotes, so that backslashes will not be escaped.
|
||||||
|
# Using just a string will also work, but it is deprecated, and will degrade if
|
||||||
|
# you use paths with spaces.
|
||||||
|
|
||||||
# This is a special key that defines the handler for all URL schemes for which
|
# This is a special key that defines the handler for all URL schemes for which
|
||||||
# no handler is defined.
|
# no handler is defined.
|
||||||
other = 'off'
|
# It uses the special value 'default', which will try and use the default
|
||||||
|
# application on your computer for opening this kind of URI.
|
||||||
|
other = 'default'
|
||||||
|
|
||||||
|
[url-prompts]
|
||||||
|
# Specify whether a confirmation prompt should be shown before following URL schemes.
|
||||||
|
# The special key 'other' matches all schemes that don't match any other key.
|
||||||
|
#
|
||||||
|
# Example: prompt on every non-gemini URL
|
||||||
|
# other = true
|
||||||
|
# gemini = false
|
||||||
|
#
|
||||||
|
# Example: only prompt on HTTP(S)
|
||||||
|
# other = false
|
||||||
|
# http = true
|
||||||
|
# https = true
|
||||||
|
|
||||||
# [[mediatype-handlers]] section
|
# [[mediatype-handlers]] section
|
||||||
# ---------------------------------
|
# ---------------------------------
|
||||||
@ -300,11 +351,18 @@ workers = 3
|
|||||||
# The number of subscription updates displayed per page.
|
# The number of subscription updates displayed per page.
|
||||||
entries_per_page = 20
|
entries_per_page = 20
|
||||||
|
|
||||||
|
# Set to false to remove the explanatory text from the top of the subscription page
|
||||||
|
header = true
|
||||||
|
|
||||||
|
|
||||||
[theme]
|
[theme]
|
||||||
# This section is for changing the COLORS used in Amfora.
|
# This section is for changing the COLORS used in Amfora.
|
||||||
# These colors only apply if 'color' is enabled above.
|
# These colors only apply if 'color' is enabled above.
|
||||||
# Colors can be set using a W3C color name, or a hex value such as "#ffffff".
|
# Colors can be set using a W3C color name, or a hex value such as "#ffffff".
|
||||||
|
# Setting a background to "default" keeps the terminal default
|
||||||
|
# If your terminal has transparency, set any background to "default" to keep it transparent
|
||||||
|
# The key "bg" is already set to "default", but this can be used on other backgrounds,
|
||||||
|
# like for modals.
|
||||||
|
|
||||||
# Note that not all colors will work on terminals that do not have truecolor support.
|
# Note that not all colors will work on terminals that do not have truecolor support.
|
||||||
# If you want to stick to the standard 16 or 256 colors, you can get
|
# If you want to stick to the standard 16 or 256 colors, you can get
|
||||||
@ -323,6 +381,7 @@ entries_per_page = 20
|
|||||||
# EXAMPLES:
|
# EXAMPLES:
|
||||||
# hdg_1 = "green"
|
# hdg_1 = "green"
|
||||||
# hdg_2 = "#5f0000"
|
# hdg_2 = "#5f0000"
|
||||||
|
# bg = "default"
|
||||||
|
|
||||||
# Available keys to set:
|
# Available keys to set:
|
||||||
|
|
||||||
@ -334,6 +393,15 @@ entries_per_page = 20
|
|||||||
# bottombar_bg
|
# bottombar_bg
|
||||||
# scrollbar: The scrollbar that appears on the right for long pages
|
# scrollbar: The scrollbar that appears on the right for long pages
|
||||||
|
|
||||||
|
# You can also set an 'include' key to process another TOML file that contains theme keys.
|
||||||
|
# Example:
|
||||||
|
# include = "my/path/to/special-theme.toml"
|
||||||
|
#
|
||||||
|
# Any other theme keys will override this external file.
|
||||||
|
# You can use this special key to switch between themes easily.
|
||||||
|
# Download other themes here: https://github.com/makeworld-the-better-one/amfora/tree/master/contrib/themes
|
||||||
|
|
||||||
|
|
||||||
# hdg_1
|
# hdg_1
|
||||||
# hdg_2
|
# hdg_2
|
||||||
# hdg_3
|
# hdg_3
|
||||||
|
@ -61,6 +61,7 @@ const (
|
|||||||
CmdCopyTargetURL
|
CmdCopyTargetURL
|
||||||
CmdBeginning
|
CmdBeginning
|
||||||
CmdEnd
|
CmdEnd
|
||||||
|
CmdURLHandlerOpen // See #143
|
||||||
CmdSearch
|
CmdSearch
|
||||||
CmdNextMatch
|
CmdNextMatch
|
||||||
CmdPrevMatch
|
CmdPrevMatch
|
||||||
@ -83,7 +84,7 @@ var tcellKeys map[string]tcell.Key
|
|||||||
// a string in the format used by the configuration file. Support
|
// a string in the format used by the configuration file. Support
|
||||||
// function for GetKeyBinding(), used to make the help panel helpful.
|
// function for GetKeyBinding(), used to make the help panel helpful.
|
||||||
func keyBindingToString(kb keyBinding) (string, bool) {
|
func keyBindingToString(kb keyBinding) (string, bool) {
|
||||||
var prefix string = ""
|
var prefix string
|
||||||
|
|
||||||
if kb.mod&tcell.ModAlt == tcell.ModAlt {
|
if kb.mod&tcell.ModAlt == tcell.ModAlt {
|
||||||
prefix = "Alt-"
|
prefix = "Alt-"
|
||||||
@ -106,7 +107,7 @@ func keyBindingToString(kb keyBinding) (string, bool) {
|
|||||||
// Used by the help panel so bindable keys display with their
|
// Used by the help panel so bindable keys display with their
|
||||||
// bound values rather than hardcoded defaults.
|
// bound values rather than hardcoded defaults.
|
||||||
func GetKeyBinding(cmd Command) string {
|
func GetKeyBinding(cmd Command) string {
|
||||||
var s string = ""
|
var s string
|
||||||
for kb, c := range bindings {
|
for kb, c := range bindings {
|
||||||
if c == cmd {
|
if c == cmd {
|
||||||
t, ok := keyBindingToString(kb)
|
t, ok := keyBindingToString(kb)
|
||||||
@ -125,14 +126,19 @@ func GetKeyBinding(cmd Command) string {
|
|||||||
// Parse a single keybinding string and add it to the binding map
|
// Parse a single keybinding string and add it to the binding map
|
||||||
func parseBinding(cmd Command, binding string) {
|
func parseBinding(cmd Command, binding string) {
|
||||||
var k tcell.Key
|
var k tcell.Key
|
||||||
var m tcell.ModMask = 0
|
var m tcell.ModMask
|
||||||
var r rune = 0
|
var r rune
|
||||||
|
|
||||||
if strings.HasPrefix(binding, "Alt-") {
|
if strings.HasPrefix(binding, "Alt-") {
|
||||||
m = tcell.ModAlt
|
m = tcell.ModAlt
|
||||||
binding = binding[4:]
|
binding = binding[4:]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(binding, "Shift-") {
|
||||||
|
m += tcell.ModShift
|
||||||
|
binding = binding[6:]
|
||||||
|
}
|
||||||
|
|
||||||
if len([]rune(binding)) == 1 {
|
if len([]rune(binding)) == 1 {
|
||||||
k = tcell.KeyRune
|
k = tcell.KeyRune
|
||||||
r = []rune(binding)[0]
|
r = []rune(binding)[0]
|
||||||
@ -159,46 +165,47 @@ func parseBinding(cmd Command, binding string) {
|
|||||||
// Called by config.Init()
|
// Called by config.Init()
|
||||||
func KeyInit() {
|
func KeyInit() {
|
||||||
configBindings := map[Command]string{
|
configBindings := map[Command]string{
|
||||||
CmdLink1: "keybindings.bind_link1",
|
CmdLink1: "keybindings.bind_link1",
|
||||||
CmdLink2: "keybindings.bind_link2",
|
CmdLink2: "keybindings.bind_link2",
|
||||||
CmdLink3: "keybindings.bind_link3",
|
CmdLink3: "keybindings.bind_link3",
|
||||||
CmdLink4: "keybindings.bind_link4",
|
CmdLink4: "keybindings.bind_link4",
|
||||||
CmdLink5: "keybindings.bind_link5",
|
CmdLink5: "keybindings.bind_link5",
|
||||||
CmdLink6: "keybindings.bind_link6",
|
CmdLink6: "keybindings.bind_link6",
|
||||||
CmdLink7: "keybindings.bind_link7",
|
CmdLink7: "keybindings.bind_link7",
|
||||||
CmdLink8: "keybindings.bind_link8",
|
CmdLink8: "keybindings.bind_link8",
|
||||||
CmdLink9: "keybindings.bind_link9",
|
CmdLink9: "keybindings.bind_link9",
|
||||||
CmdLink0: "keybindings.bind_link0",
|
CmdLink0: "keybindings.bind_link0",
|
||||||
CmdBottom: "keybindings.bind_bottom",
|
CmdBottom: "keybindings.bind_bottom",
|
||||||
CmdEdit: "keybindings.bind_edit",
|
CmdEdit: "keybindings.bind_edit",
|
||||||
CmdHome: "keybindings.bind_home",
|
CmdHome: "keybindings.bind_home",
|
||||||
CmdBookmarks: "keybindings.bind_bookmarks",
|
CmdBookmarks: "keybindings.bind_bookmarks",
|
||||||
CmdAddBookmark: "keybindings.bind_add_bookmark",
|
CmdAddBookmark: "keybindings.bind_add_bookmark",
|
||||||
CmdSave: "keybindings.bind_save",
|
CmdSave: "keybindings.bind_save",
|
||||||
CmdReload: "keybindings.bind_reload",
|
CmdReload: "keybindings.bind_reload",
|
||||||
CmdBack: "keybindings.bind_back",
|
CmdBack: "keybindings.bind_back",
|
||||||
CmdForward: "keybindings.bind_forward",
|
CmdForward: "keybindings.bind_forward",
|
||||||
CmdMoveUp: "keybindings.bind_moveup",
|
CmdMoveUp: "keybindings.bind_moveup",
|
||||||
CmdMoveDown: "keybindings.bind_movedown",
|
CmdMoveDown: "keybindings.bind_movedown",
|
||||||
CmdMoveLeft: "keybindings.bind_moveleft",
|
CmdMoveLeft: "keybindings.bind_moveleft",
|
||||||
CmdMoveRight: "keybindings.bind_moveright",
|
CmdMoveRight: "keybindings.bind_moveright",
|
||||||
CmdPgup: "keybindings.bind_pgup",
|
CmdPgup: "keybindings.bind_pgup",
|
||||||
CmdPgdn: "keybindings.bind_pgdn",
|
CmdPgdn: "keybindings.bind_pgdn",
|
||||||
CmdNewTab: "keybindings.bind_new_tab",
|
CmdNewTab: "keybindings.bind_new_tab",
|
||||||
CmdCloseTab: "keybindings.bind_close_tab",
|
CmdCloseTab: "keybindings.bind_close_tab",
|
||||||
CmdNextTab: "keybindings.bind_next_tab",
|
CmdNextTab: "keybindings.bind_next_tab",
|
||||||
CmdPrevTab: "keybindings.bind_prev_tab",
|
CmdPrevTab: "keybindings.bind_prev_tab",
|
||||||
CmdQuit: "keybindings.bind_quit",
|
CmdQuit: "keybindings.bind_quit",
|
||||||
CmdHelp: "keybindings.bind_help",
|
CmdHelp: "keybindings.bind_help",
|
||||||
CmdSub: "keybindings.bind_sub",
|
CmdSub: "keybindings.bind_sub",
|
||||||
CmdAddSub: "keybindings.bind_add_sub",
|
CmdAddSub: "keybindings.bind_add_sub",
|
||||||
CmdCopyPageURL: "keybindings.bind_copy_page_url",
|
CmdCopyPageURL: "keybindings.bind_copy_page_url",
|
||||||
CmdCopyTargetURL: "keybindings.bind_copy_target_url",
|
CmdCopyTargetURL: "keybindings.bind_copy_target_url",
|
||||||
CmdBeginning: "keybindings.bind_beginning",
|
CmdBeginning: "keybindings.bind_beginning",
|
||||||
CmdEnd: "keybindings.bind_end",
|
CmdEnd: "keybindings.bind_end",
|
||||||
CmdSearch: "keybindings.bind_search",
|
CmdURLHandlerOpen: "keybindings.bind_url_handler_open",
|
||||||
CmdNextMatch: "keybindings.bind_next_match",
|
CmdSearch: "keybindings.bind_search",
|
||||||
CmdPrevMatch: "keybindings.bind_prev_match",
|
CmdNextMatch: "keybindings.bind_next_match",
|
||||||
|
CmdPrevMatch: "keybindings.bind_prev_match",
|
||||||
}
|
}
|
||||||
// This is split off to allow shift_numbers to override bind_tab[1-90]
|
// This is split off to allow shift_numbers to override bind_tab[1-90]
|
||||||
// (This is needed for older configs so that the default bind_tab values
|
// (This is needed for older configs so that the default bind_tab values
|
||||||
|
313
config/theme.go
313
config/theme.go
@ -8,81 +8,336 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Functions to allow themeing configuration.
|
// Functions to allow themeing configuration.
|
||||||
// UI element colors are mapped to a string key, such as "error" or "tab_bg"
|
// UI element tcell.Colors are mapped to a string key, such as "error" or "tab_bg"
|
||||||
// These are the same keys used in the config file.
|
// These are the same keys used in the config file.
|
||||||
|
|
||||||
|
// Special color with no real color value
|
||||||
|
// Used for a default foreground color
|
||||||
|
// White is the terminal background is black, black if the terminal background is white
|
||||||
|
// Converted to a real color in this file before being sent out to other modules
|
||||||
|
const ColorFg = tcell.ColorSpecial | 2
|
||||||
|
|
||||||
|
// The same as ColorFg, but inverted
|
||||||
|
const ColorBg = tcell.ColorSpecial | 3
|
||||||
|
|
||||||
var themeMu = sync.RWMutex{}
|
var themeMu = sync.RWMutex{}
|
||||||
var theme = map[string]tcell.Color{
|
var theme = map[string]tcell.Color{
|
||||||
// Default values below
|
// Map these for special uses in code
|
||||||
|
"ColorBg": ColorBg,
|
||||||
|
"ColorFg": ColorFg,
|
||||||
|
|
||||||
"bg": tcell.ColorBlack, // Used for cview.Styles.PrimitiveBackgroundColor
|
// Default values below
|
||||||
"tab_num": tcell.Color30, // xterm:Turquoise4, #008787
|
// Only the 16 Xterm system tcell.Colors are used, because those are the tcell.Colors overrided
|
||||||
"tab_divider": tcell.ColorWhite,
|
// by the user's default terminal theme
|
||||||
"bottombar_label": tcell.Color30,
|
|
||||||
"bottombar_text": tcell.ColorBlack,
|
// Used for cview.Styles.PrimitiveBackgroundColor
|
||||||
"bottombar_bg": tcell.ColorWhite,
|
// Set to tcell.ColorDefault because that allows transparent terminals to work
|
||||||
"scrollbar": tcell.ColorWhite,
|
// The rest of this theme assumes that the background is equivalent to black, but
|
||||||
|
// white colors switched to black later if the background is determined to be white.
|
||||||
|
//
|
||||||
|
// Also, this is set to tcell.ColorBlack in config.go if colors are disabled in the config.
|
||||||
|
"bg": tcell.ColorDefault,
|
||||||
|
|
||||||
|
"tab_num": tcell.ColorTeal,
|
||||||
|
"tab_divider": ColorFg,
|
||||||
|
"bottombar_label": tcell.ColorTeal,
|
||||||
|
"bottombar_text": ColorBg,
|
||||||
|
"bottombar_bg": ColorFg,
|
||||||
|
"scrollbar": ColorFg,
|
||||||
|
|
||||||
// Modals
|
// Modals
|
||||||
"btn_bg": tcell.ColorNavy, // All modal buttons
|
"btn_bg": tcell.ColorTeal, // All modal buttons
|
||||||
"btn_text": tcell.ColorWhite,
|
"btn_text": tcell.ColorWhite, // White instead of ColorFg because background is known to be Teal
|
||||||
|
|
||||||
"dl_choice_modal_bg": tcell.ColorPurple,
|
"dl_choice_modal_bg": tcell.ColorOlive,
|
||||||
"dl_choice_modal_text": tcell.ColorWhite,
|
"dl_choice_modal_text": tcell.ColorWhite,
|
||||||
"dl_modal_bg": tcell.Color130, // xterm:DarkOrange3, #af5f00
|
"dl_modal_bg": tcell.ColorOlive,
|
||||||
"dl_modal_text": tcell.ColorWhite,
|
"dl_modal_text": tcell.ColorWhite,
|
||||||
"info_modal_bg": tcell.ColorGray,
|
"info_modal_bg": tcell.ColorGray,
|
||||||
"info_modal_text": tcell.ColorWhite,
|
"info_modal_text": tcell.ColorWhite,
|
||||||
"error_modal_bg": tcell.ColorMaroon,
|
"error_modal_bg": tcell.ColorMaroon,
|
||||||
"error_modal_text": tcell.ColorWhite,
|
"error_modal_text": tcell.ColorWhite,
|
||||||
"yesno_modal_bg": tcell.ColorPurple,
|
"yesno_modal_bg": tcell.ColorTeal,
|
||||||
"yesno_modal_text": tcell.ColorWhite,
|
"yesno_modal_text": tcell.ColorWhite,
|
||||||
"tofu_modal_bg": tcell.ColorMaroon,
|
"tofu_modal_bg": tcell.ColorMaroon,
|
||||||
"tofu_modal_text": tcell.ColorWhite,
|
"tofu_modal_text": tcell.ColorWhite,
|
||||||
"subscription_modal_bg": tcell.Color61, // xterm:SlateBlue3, #5f5faf
|
"subscription_modal_bg": tcell.ColorTeal,
|
||||||
"subscription_modal_text": tcell.ColorWhite,
|
"subscription_modal_text": tcell.ColorWhite,
|
||||||
|
|
||||||
"input_modal_bg": tcell.ColorGreen,
|
"input_modal_bg": tcell.ColorGreen,
|
||||||
"input_modal_text": tcell.ColorWhite,
|
"input_modal_text": tcell.ColorWhite,
|
||||||
"input_modal_field_bg": tcell.ColorBlue,
|
"input_modal_field_bg": tcell.ColorNavy,
|
||||||
"input_modal_field_text": tcell.ColorWhite,
|
"input_modal_field_text": tcell.ColorWhite,
|
||||||
|
|
||||||
"bkmk_modal_bg": tcell.ColorTeal,
|
"bkmk_modal_bg": tcell.ColorTeal,
|
||||||
"bkmk_modal_text": tcell.ColorWhite,
|
"bkmk_modal_text": tcell.ColorWhite,
|
||||||
"bkmk_modal_label": tcell.ColorYellow,
|
"bkmk_modal_label": tcell.ColorYellow,
|
||||||
"bkmk_modal_field_bg": tcell.ColorBlue,
|
"bkmk_modal_field_bg": tcell.ColorNavy,
|
||||||
"bkmk_modal_field_text": tcell.ColorWhite,
|
"bkmk_modal_field_text": tcell.ColorWhite,
|
||||||
|
|
||||||
"hdg_1": tcell.ColorRed,
|
"hdg_1": tcell.ColorRed,
|
||||||
"hdg_2": tcell.ColorLime,
|
"hdg_2": tcell.ColorLime,
|
||||||
"hdg_3": tcell.ColorFuchsia,
|
"hdg_3": tcell.ColorFuchsia,
|
||||||
"amfora_link": tcell.Color33, // xterm:DodgerBlue1, #0087ff
|
"amfora_link": tcell.ColorBlue,
|
||||||
"foreign_link": tcell.Color92, // xterm:DarkViolet, #8700d7
|
"foreign_link": tcell.ColorPurple,
|
||||||
"link_number": tcell.ColorSilver,
|
"link_number": tcell.ColorSilver,
|
||||||
"regular_text": tcell.ColorWhite,
|
"regular_text": ColorFg,
|
||||||
"quote_text": tcell.ColorWhite,
|
"quote_text": ColorFg,
|
||||||
"preformatted_text": tcell.Color229, // xterm:Wheat1, #ffffaf
|
"preformatted_text": ColorFg,
|
||||||
"list_text": tcell.ColorWhite,
|
"list_text": ColorFg,
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetColor(key string, color tcell.Color) {
|
func SetColor(key string, color tcell.Color) {
|
||||||
themeMu.Lock()
|
themeMu.Lock()
|
||||||
theme[key] = color
|
// Use truecolor because this is only called with user-set tcell.Colors
|
||||||
|
// Which should be represented exactly
|
||||||
|
theme[key] = color.TrueColor()
|
||||||
themeMu.Unlock()
|
themeMu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetColor will return tcell.ColorBlack if there is no color for the provided key.
|
// GetColor will return tcell.ColorBlack if there is no tcell.Color for the provided key.
|
||||||
func GetColor(key string) tcell.Color {
|
func GetColor(key string) tcell.Color {
|
||||||
themeMu.RLock()
|
themeMu.RLock()
|
||||||
defer themeMu.RUnlock()
|
defer themeMu.RUnlock()
|
||||||
return theme[key].TrueColor()
|
|
||||||
|
color := theme[key]
|
||||||
|
|
||||||
|
if color == ColorFg {
|
||||||
|
if hasDarkTerminalBackground {
|
||||||
|
return tcell.ColorWhite
|
||||||
|
}
|
||||||
|
return tcell.ColorBlack
|
||||||
|
}
|
||||||
|
if color == ColorBg {
|
||||||
|
if hasDarkTerminalBackground {
|
||||||
|
return tcell.ColorBlack
|
||||||
|
}
|
||||||
|
return tcell.ColorWhite
|
||||||
|
}
|
||||||
|
|
||||||
|
return color
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetColorString returns a string that can be used in a cview color tag,
|
// colorToString converts a color to a string for use in a cview tag
|
||||||
|
func colorToString(color tcell.Color) string {
|
||||||
|
if color == tcell.ColorDefault {
|
||||||
|
return "-"
|
||||||
|
}
|
||||||
|
|
||||||
|
if color == ColorFg {
|
||||||
|
if hasDarkTerminalBackground {
|
||||||
|
return "white"
|
||||||
|
}
|
||||||
|
return "black"
|
||||||
|
}
|
||||||
|
if color == ColorBg {
|
||||||
|
if hasDarkTerminalBackground {
|
||||||
|
return "black"
|
||||||
|
}
|
||||||
|
return "white"
|
||||||
|
}
|
||||||
|
|
||||||
|
if color&tcell.ColorIsRGB == 0 {
|
||||||
|
// tcell.Color is not RGB/TrueColor, it's a tcell.Color from the default terminal
|
||||||
|
// theme as set above
|
||||||
|
// Return a tcell.Color name instead of a hex code, so that cview doesn't use TrueColor
|
||||||
|
return ColorToColorName[color]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Color set by user, must be respected exactly so hex code is used
|
||||||
|
return fmt.Sprintf("#%06x", color.Hex())
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetColorString returns a string that can be used in a cview tcell.Color tag,
|
||||||
// for the given theme key.
|
// for the given theme key.
|
||||||
// It will return "#000000" if there is no color for the provided key.
|
// It will return "#000000" if there is no tcell.Color for the provided key.
|
||||||
func GetColorString(key string) string {
|
func GetColorString(key string) string {
|
||||||
themeMu.RLock()
|
themeMu.RLock()
|
||||||
defer themeMu.RUnlock()
|
defer themeMu.RUnlock()
|
||||||
return fmt.Sprintf("#%06x", theme[key].TrueColor().Hex())
|
|
||||||
|
return colorToString(theme[key])
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetContrastingColor returns tcell.ColorBlack if tcell.Color is brighter than gray
|
||||||
|
// otherwise returns tcell.ColorWhite if tcell.Color is dimmer than gray
|
||||||
|
// if tcell.Color is tcell.ColorDefault (undefined luminance) this returns tcell.ColorDefault
|
||||||
|
func GetContrastingColor(color tcell.Color) tcell.Color {
|
||||||
|
if color == tcell.ColorDefault {
|
||||||
|
// tcell.Color should never be tcell.ColorDefault
|
||||||
|
// only config keys which end in bg are allowed to be set to default
|
||||||
|
// and the only way the argument of this function is set to tcell.ColorDefault
|
||||||
|
// is if both the text and bg of an element in the UI are set to default
|
||||||
|
return tcell.ColorDefault
|
||||||
|
}
|
||||||
|
r, g, b := color.RGB()
|
||||||
|
luminance := (77*r + 150*g + 29*b + 1<<7) >> 8
|
||||||
|
const gray = 119 // The middle gray
|
||||||
|
if luminance > gray {
|
||||||
|
return tcell.ColorBlack
|
||||||
|
}
|
||||||
|
return tcell.ColorWhite
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTextColor is the Same as GetColor, unless the key is "default".
|
||||||
|
// This happens on focus of a UI element which has a bg of default, in which case
|
||||||
|
// It return tcell.ColorBlack or tcell.ColorWhite, depending on which is more readable
|
||||||
|
func GetTextColor(key, bg string) tcell.Color {
|
||||||
|
themeMu.RLock()
|
||||||
|
defer themeMu.RUnlock()
|
||||||
|
color := theme[key].TrueColor()
|
||||||
|
if color != tcell.ColorDefault {
|
||||||
|
return color
|
||||||
|
}
|
||||||
|
return GetContrastingColor(theme[bg].TrueColor())
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTextColorString is the Same as GetColorString, unless the key is "default".
|
||||||
|
// This happens on focus of a UI element which has a bg of default, in which case
|
||||||
|
// It return tcell.ColorBlack or tcell.ColorWhite, depending on which is more readable
|
||||||
|
func GetTextColorString(key, bg string) string {
|
||||||
|
return colorToString(GetTextColor(key, bg))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inverted version of a tcell map
|
||||||
|
// https://github.com/gdamore/tcell/blob/v2.3.3/color.go#L845
|
||||||
|
var ColorToColorName = map[tcell.Color]string{
|
||||||
|
tcell.ColorBlack: "black",
|
||||||
|
tcell.ColorMaroon: "maroon",
|
||||||
|
tcell.ColorGreen: "green",
|
||||||
|
tcell.ColorOlive: "olive",
|
||||||
|
tcell.ColorNavy: "navy",
|
||||||
|
tcell.ColorPurple: "purple",
|
||||||
|
tcell.ColorTeal: "teal",
|
||||||
|
tcell.ColorSilver: "silver",
|
||||||
|
tcell.ColorGray: "gray",
|
||||||
|
tcell.ColorRed: "red",
|
||||||
|
tcell.ColorLime: "lime",
|
||||||
|
tcell.ColorYellow: "yellow",
|
||||||
|
tcell.ColorBlue: "blue",
|
||||||
|
tcell.ColorFuchsia: "fuchsia",
|
||||||
|
tcell.ColorAqua: "aqua",
|
||||||
|
tcell.ColorWhite: "white",
|
||||||
|
tcell.ColorAliceBlue: "aliceblue",
|
||||||
|
tcell.ColorAntiqueWhite: "antiquewhite",
|
||||||
|
tcell.ColorAquaMarine: "aquamarine",
|
||||||
|
tcell.ColorAzure: "azure",
|
||||||
|
tcell.ColorBeige: "beige",
|
||||||
|
tcell.ColorBisque: "bisque",
|
||||||
|
tcell.ColorBlanchedAlmond: "blanchedalmond",
|
||||||
|
tcell.ColorBlueViolet: "blueviolet",
|
||||||
|
tcell.ColorBrown: "brown",
|
||||||
|
tcell.ColorBurlyWood: "burlywood",
|
||||||
|
tcell.ColorCadetBlue: "cadetblue",
|
||||||
|
tcell.ColorChartreuse: "chartreuse",
|
||||||
|
tcell.ColorChocolate: "chocolate",
|
||||||
|
tcell.ColorCoral: "coral",
|
||||||
|
tcell.ColorCornflowerBlue: "cornflowerblue",
|
||||||
|
tcell.ColorCornsilk: "cornsilk",
|
||||||
|
tcell.ColorCrimson: "crimson",
|
||||||
|
tcell.ColorDarkBlue: "darkblue",
|
||||||
|
tcell.ColorDarkCyan: "darkcyan",
|
||||||
|
tcell.ColorDarkGoldenrod: "darkgoldenrod",
|
||||||
|
tcell.ColorDarkGray: "darkgray",
|
||||||
|
tcell.ColorDarkGreen: "darkgreen",
|
||||||
|
tcell.ColorDarkKhaki: "darkkhaki",
|
||||||
|
tcell.ColorDarkMagenta: "darkmagenta",
|
||||||
|
tcell.ColorDarkOliveGreen: "darkolivegreen",
|
||||||
|
tcell.ColorDarkOrange: "darkorange",
|
||||||
|
tcell.ColorDarkOrchid: "darkorchid",
|
||||||
|
tcell.ColorDarkRed: "darkred",
|
||||||
|
tcell.ColorDarkSalmon: "darksalmon",
|
||||||
|
tcell.ColorDarkSeaGreen: "darkseagreen",
|
||||||
|
tcell.ColorDarkSlateBlue: "darkslateblue",
|
||||||
|
tcell.ColorDarkSlateGray: "darkslategray",
|
||||||
|
tcell.ColorDarkTurquoise: "darkturquoise",
|
||||||
|
tcell.ColorDarkViolet: "darkviolet",
|
||||||
|
tcell.ColorDeepPink: "deeppink",
|
||||||
|
tcell.ColorDeepSkyBlue: "deepskyblue",
|
||||||
|
tcell.ColorDimGray: "dimgray",
|
||||||
|
tcell.ColorDodgerBlue: "dodgerblue",
|
||||||
|
tcell.ColorFireBrick: "firebrick",
|
||||||
|
tcell.ColorFloralWhite: "floralwhite",
|
||||||
|
tcell.ColorForestGreen: "forestgreen",
|
||||||
|
tcell.ColorGainsboro: "gainsboro",
|
||||||
|
tcell.ColorGhostWhite: "ghostwhite",
|
||||||
|
tcell.ColorGold: "gold",
|
||||||
|
tcell.ColorGoldenrod: "goldenrod",
|
||||||
|
tcell.ColorGreenYellow: "greenyellow",
|
||||||
|
tcell.ColorHoneydew: "honeydew",
|
||||||
|
tcell.ColorHotPink: "hotpink",
|
||||||
|
tcell.ColorIndianRed: "indianred",
|
||||||
|
tcell.ColorIndigo: "indigo",
|
||||||
|
tcell.ColorIvory: "ivory",
|
||||||
|
tcell.ColorKhaki: "khaki",
|
||||||
|
tcell.ColorLavender: "lavender",
|
||||||
|
tcell.ColorLavenderBlush: "lavenderblush",
|
||||||
|
tcell.ColorLawnGreen: "lawngreen",
|
||||||
|
tcell.ColorLemonChiffon: "lemonchiffon",
|
||||||
|
tcell.ColorLightBlue: "lightblue",
|
||||||
|
tcell.ColorLightCoral: "lightcoral",
|
||||||
|
tcell.ColorLightCyan: "lightcyan",
|
||||||
|
tcell.ColorLightGoldenrodYellow: "lightgoldenrodyellow",
|
||||||
|
tcell.ColorLightGray: "lightgray",
|
||||||
|
tcell.ColorLightGreen: "lightgreen",
|
||||||
|
tcell.ColorLightPink: "lightpink",
|
||||||
|
tcell.ColorLightSalmon: "lightsalmon",
|
||||||
|
tcell.ColorLightSeaGreen: "lightseagreen",
|
||||||
|
tcell.ColorLightSkyBlue: "lightskyblue",
|
||||||
|
tcell.ColorLightSlateGray: "lightslategray",
|
||||||
|
tcell.ColorLightSteelBlue: "lightsteelblue",
|
||||||
|
tcell.ColorLightYellow: "lightyellow",
|
||||||
|
tcell.ColorLimeGreen: "limegreen",
|
||||||
|
tcell.ColorLinen: "linen",
|
||||||
|
tcell.ColorMediumAquamarine: "mediumaquamarine",
|
||||||
|
tcell.ColorMediumBlue: "mediumblue",
|
||||||
|
tcell.ColorMediumOrchid: "mediumorchid",
|
||||||
|
tcell.ColorMediumPurple: "mediumpurple",
|
||||||
|
tcell.ColorMediumSeaGreen: "mediumseagreen",
|
||||||
|
tcell.ColorMediumSlateBlue: "mediumslateblue",
|
||||||
|
tcell.ColorMediumSpringGreen: "mediumspringgreen",
|
||||||
|
tcell.ColorMediumTurquoise: "mediumturquoise",
|
||||||
|
tcell.ColorMediumVioletRed: "mediumvioletred",
|
||||||
|
tcell.ColorMidnightBlue: "midnightblue",
|
||||||
|
tcell.ColorMintCream: "mintcream",
|
||||||
|
tcell.ColorMistyRose: "mistyrose",
|
||||||
|
tcell.ColorMoccasin: "moccasin",
|
||||||
|
tcell.ColorNavajoWhite: "navajowhite",
|
||||||
|
tcell.ColorOldLace: "oldlace",
|
||||||
|
tcell.ColorOliveDrab: "olivedrab",
|
||||||
|
tcell.ColorOrange: "orange",
|
||||||
|
tcell.ColorOrangeRed: "orangered",
|
||||||
|
tcell.ColorOrchid: "orchid",
|
||||||
|
tcell.ColorPaleGoldenrod: "palegoldenrod",
|
||||||
|
tcell.ColorPaleGreen: "palegreen",
|
||||||
|
tcell.ColorPaleTurquoise: "paleturquoise",
|
||||||
|
tcell.ColorPaleVioletRed: "palevioletred",
|
||||||
|
tcell.ColorPapayaWhip: "papayawhip",
|
||||||
|
tcell.ColorPeachPuff: "peachpuff",
|
||||||
|
tcell.ColorPeru: "peru",
|
||||||
|
tcell.ColorPink: "pink",
|
||||||
|
tcell.ColorPlum: "plum",
|
||||||
|
tcell.ColorPowderBlue: "powderblue",
|
||||||
|
tcell.ColorRebeccaPurple: "rebeccapurple",
|
||||||
|
tcell.ColorRosyBrown: "rosybrown",
|
||||||
|
tcell.ColorRoyalBlue: "royalblue",
|
||||||
|
tcell.ColorSaddleBrown: "saddlebrown",
|
||||||
|
tcell.ColorSalmon: "salmon",
|
||||||
|
tcell.ColorSandyBrown: "sandybrown",
|
||||||
|
tcell.ColorSeaGreen: "seagreen",
|
||||||
|
tcell.ColorSeashell: "seashell",
|
||||||
|
tcell.ColorSienna: "sienna",
|
||||||
|
tcell.ColorSkyblue: "skyblue",
|
||||||
|
tcell.ColorSlateBlue: "slateblue",
|
||||||
|
tcell.ColorSlateGray: "slategray",
|
||||||
|
tcell.ColorSnow: "snow",
|
||||||
|
tcell.ColorSpringGreen: "springgreen",
|
||||||
|
tcell.ColorSteelBlue: "steelblue",
|
||||||
|
tcell.ColorTan: "tan",
|
||||||
|
tcell.ColorThistle: "thistle",
|
||||||
|
tcell.ColorTomato: "tomato",
|
||||||
|
tcell.ColorTurquoise: "turquoise",
|
||||||
|
tcell.ColorViolet: "violet",
|
||||||
|
tcell.ColorWheat: "wheat",
|
||||||
|
tcell.ColorWhiteSmoke: "whitesmoke",
|
||||||
|
tcell.ColorYellowGreen: "yellowgreen",
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,14 @@
|
|||||||
|
|
||||||
You can use these themes by replacing the `[theme]` section of your [config](https://github.com/makeworld-the-better-one/amfora/wiki/Configuration) with their contents. Some themes won't display properly on terminals that do not have truecolor support.
|
You can use these themes by replacing the `[theme]` section of your [config](https://github.com/makeworld-the-better-one/amfora/wiki/Configuration) with their contents. Some themes won't display properly on terminals that do not have truecolor support.
|
||||||
|
|
||||||
|
## Amfora
|
||||||
|
|
||||||
|
This is the original Amfora theme we all know and love. From v1.9.0 and onwards, the user's terminal theme is used by default. Use this theme to restore the original Amfora look.
|
||||||
|
|
||||||
|
<a href="https://raw.githubusercontent.com/makeworld-the-better-one/amfora/master/demo-large.gif">
|
||||||
|
<img src="../../demo-large.gif" alt="Demo GIF" width="80%">
|
||||||
|
</a>
|
||||||
|
|
||||||
## Nord
|
## Nord
|
||||||
|
|
||||||
Contributed by **[@lokesh-krishna](https://github.com/lokesh-krishna)**.
|
Contributed by **[@lokesh-krishna](https://github.com/lokesh-krishna)**.
|
||||||
@ -21,6 +29,22 @@ Contributed by **[@crdpa](https://github.com/crdpa)**.
|
|||||||
![screenshot of dracula theme](https://user-images.githubusercontent.com/61637474/99983210-53d2e900-2d8a-11eb-9ab7-12dc10c2933a.png)
|
![screenshot of dracula theme](https://user-images.githubusercontent.com/61637474/99983210-53d2e900-2d8a-11eb-9ab7-12dc10c2933a.png)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
## Dracula variant
|
||||||
|
|
||||||
|
Contributed by **[@marcransome](https://github.com/marcransome)**.
|
||||||
|
|
||||||
|
![screenshot of dracula variant theme](https://user-images.githubusercontent.com/679401/132952433-563501ef-4d98-4d43-988e-f15bab7cb155.png)
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>More screenshots</summary>
|
||||||
|
|
||||||
|
![screenshot of dracula variant theme](https://user-images.githubusercontent.com/679401/132952340-96840ad8-fb78-499d-bf6b-3fcdf659edc7.png)
|
||||||
|
![screenshot of dracula variant theme](https://user-images.githubusercontent.com/679401/132952347-6b93d985-afc8-47b4-9569-1775ce4f37e7.png)
|
||||||
|
![screenshot of dracula variant theme](https://user-images.githubusercontent.com/679401/132952348-ffcbcc7a-f9ad-41c6-a7d2-5c870754c4c9.png)
|
||||||
|
![screenshot of dracula variant theme](https://user-images.githubusercontent.com/679401/132952352-50ca16f3-d255-4a1d-a25b-ccf53116957d.png)
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
## Greyscale Light
|
## Greyscale Light
|
||||||
|
|
||||||
Contributed by **[@leifmetcalf](https://github.com/leifmetcalf)**.
|
Contributed by **[@leifmetcalf](https://github.com/leifmetcalf)**.
|
||||||
@ -82,6 +106,19 @@ Contributed by **[@sergetymo](https://github.com/sergetymo)**.
|
|||||||
![screenshot of error modal](https://user-images.githubusercontent.com/65758149/101183206-da73aa00-3657-11eb-8733-5040c8aefb99.png)
|
![screenshot of error modal](https://user-images.githubusercontent.com/65758149/101183206-da73aa00-3657-11eb-8733-5040c8aefb99.png)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
## Ayu Light
|
||||||
|
Contributed by **[@sergetymo](https://github.com/sergetymo)**.
|
||||||
|
|
||||||
|
![screenshot of Ayu Light theme](https://user-images.githubusercontent.com/65758149/181745417-48a92840-10d2-4659-950d-fbc9b3588d5c.png)
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>More screenshots</summary>
|
||||||
|
|
||||||
|
![screenshot of bookmark modal](https://user-images.githubusercontent.com/65758149/181745413-b5a15120-2ff6-4879-8539-0f02f0eece21.png)
|
||||||
|
![screenshot of error modal](https://user-images.githubusercontent.com/65758149/181745400-c3e9ba95-aee4-4956-91a8-3dddcbad48cc.png)
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
## Atelier Forest
|
## Atelier Forest
|
||||||
|
|
||||||
Contributed by **[@joyalicegu](https://github.com/joyalicegu)**.
|
Contributed by **[@joyalicegu](https://github.com/joyalicegu)**.
|
||||||
@ -127,6 +164,28 @@ Contributed by **[@knix3](https://github.com/knix3)**
|
|||||||
![screenshot of error](https://user-images.githubusercontent.com/69134168/118543250-096f6b00-b722-11eb-9dca-d2b1bd6a8885.png)
|
![screenshot of error](https://user-images.githubusercontent.com/69134168/118543250-096f6b00-b722-11eb-9dca-d2b1bd6a8885.png)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
## Tokyo Night
|
||||||
|
|
||||||
|
Contributed by **[@luetage](https://github.com/luetage)**
|
||||||
|
|
||||||
|
![screenshot of Tokyo Night theme](https://user-images.githubusercontent.com/13988217/130348393-69986b51-ddd7-4310-90ae-382461502535.png)
|
||||||
|
|
||||||
|
## Rosé Pine
|
||||||
|
|
||||||
|
Contributed by **[@mvllow](https://github.com/mvllow)**.
|
||||||
|
|
||||||
|
### Rosé Pine
|
||||||
|
|
||||||
|
<img width="702" alt="screenshot of Rosé Pine theme" src="https://user-images.githubusercontent.com/47515065/157946507-c672e775-e1f1-429a-87a3-0ce947365977.png">
|
||||||
|
|
||||||
|
### Rosé Pine Moon
|
||||||
|
|
||||||
|
<img width="702" alt="screenshot of Rosé Pine Moon theme" src="https://user-images.githubusercontent.com/47515065/157946274-f4527098-4101-4825-9cc5-563cf541c7ba.png">
|
||||||
|
|
||||||
|
### Rosé Pine Dawn
|
||||||
|
|
||||||
|
<img width="702" alt="screenshot of Rosé Pine Dawn theme" src="https://user-images.githubusercontent.com/47515065/157946746-7de92443-1a1b-4758-aa6e-717d7b26ebdf.png">
|
||||||
|
|
||||||
## Yours?
|
## Yours?
|
||||||
|
|
||||||
Contribute your own theme by opening a PR.
|
Contribute your own theme by opening a PR.
|
||||||
|
50
contrib/themes/amfora.toml
Normal file
50
contrib/themes/amfora.toml
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
#[theme]
|
||||||
|
|
||||||
|
# Only the 256 xterm colors are used, so truecolor support is not needed
|
||||||
|
|
||||||
|
bg = "black"
|
||||||
|
tab_num = "#008787"
|
||||||
|
tab_divider = "white"
|
||||||
|
bottombar_label = "#008787"
|
||||||
|
bottombar_text = "black"
|
||||||
|
bottombar_bg = "white"
|
||||||
|
scrollbar = "white"
|
||||||
|
|
||||||
|
btn_bg = "#000080"
|
||||||
|
btn_text = "white"
|
||||||
|
|
||||||
|
dl_choice_modal_bg = "#800080"
|
||||||
|
dl_choice_modal_text = "white"
|
||||||
|
dl_modal_bg = "#af5f00"
|
||||||
|
dl_modal_text = "white"
|
||||||
|
info_modal_bg = "#808080"
|
||||||
|
info_modal_text = "white"
|
||||||
|
error_modal_bg = "#800000"
|
||||||
|
error_modal_text = "white"
|
||||||
|
yesno_modal_bg = "#800080"
|
||||||
|
yesno_modal_text = "white"
|
||||||
|
tofu_modal_bg = "#800000"
|
||||||
|
tofu_modal_text = "white"
|
||||||
|
subscription_modal_bg = "#5f5faf"
|
||||||
|
subscription_modal_text = "white"
|
||||||
|
input_modal_bg = "#008000"
|
||||||
|
input_modal_text = "white"
|
||||||
|
input_modal_field_bg = "#0000ff"
|
||||||
|
input_modal_field_text = "white"
|
||||||
|
|
||||||
|
bkmk_modal_bg = "#008080"
|
||||||
|
bkmk_modal_text = "white"
|
||||||
|
bkmk_modal_label = "#ffff00"
|
||||||
|
bkmk_modal_field_bg = "#0000ff"
|
||||||
|
bkmk_modal_field_text = "white"
|
||||||
|
|
||||||
|
hdg_1 = "#ff0000"
|
||||||
|
hdg_2 = "#00ff00"
|
||||||
|
hdg_3 = "#ff00ff"
|
||||||
|
amfora_link = "#0087ff"
|
||||||
|
foreign_link = "#8700d7"
|
||||||
|
link_number = "#c0c0c0"
|
||||||
|
regular_text = "white"
|
||||||
|
quote_text = "white"
|
||||||
|
preformatted_text = "#ffffaf"
|
||||||
|
list_text = "white"
|
@ -1,4 +1,4 @@
|
|||||||
[theme]
|
#[theme]
|
||||||
|
|
||||||
# atelier forest light
|
# atelier forest light
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
[theme]
|
#[theme]
|
||||||
|
|
||||||
# atelier forest
|
# atelier forest
|
||||||
|
|
||||||
|
56
contrib/themes/ayu_light.toml
Normal file
56
contrib/themes/ayu_light.toml
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# Ayu Light theme ported to Amfora
|
||||||
|
# by Serge Tymoshenko <serge@tymo.name>
|
||||||
|
|
||||||
|
bg = "#fcfcfc"
|
||||||
|
fg = "#5c6166"
|
||||||
|
tab_num = "#5c6166"
|
||||||
|
tab_divider = "#5c6166"
|
||||||
|
bottombar_bg = "#fcfcfc"
|
||||||
|
bottombar_text = "#5c6166"
|
||||||
|
bottombar_label = "#5c6166"
|
||||||
|
|
||||||
|
hdg_1 = "#fa8d3e"
|
||||||
|
hdg_2 = "#f2ae49"
|
||||||
|
hdg_3 = "#f2ae49"
|
||||||
|
amfora_link = "#399ee6"
|
||||||
|
foreign_link = "#a37acc"
|
||||||
|
link_number = "#5c6166"
|
||||||
|
regular_text = "#5c6166"
|
||||||
|
quote_text = "#4cbf99"
|
||||||
|
preformatted_text = "#86b300"
|
||||||
|
list_text = "#5c6166"
|
||||||
|
|
||||||
|
btn_bg = "#55b4d4"
|
||||||
|
btn_text = "#fcfcfc"
|
||||||
|
|
||||||
|
dl_choice_modal_bg = "#f2ae49"
|
||||||
|
dl_choice_modal_text = "#fcfcfc"
|
||||||
|
|
||||||
|
dl_modal_bg = "#f2ae49"
|
||||||
|
dl_modal_text = "#fcfcfc"
|
||||||
|
|
||||||
|
info_modal_bg = "#f2ae49"
|
||||||
|
info_modal_text = "#fcfcfc"
|
||||||
|
|
||||||
|
error_modal_bg = "#f07171"
|
||||||
|
error_modal_text = "#fcfcfc"
|
||||||
|
|
||||||
|
yesno_modal_bg = "#f2ae49"
|
||||||
|
yesno_modal_text = "#fcfcfc"
|
||||||
|
|
||||||
|
tofu_modal_bg = "#ed9366"
|
||||||
|
tofu_modal_text = "#282c34"
|
||||||
|
|
||||||
|
input_modal_bg = "#f2ae49"
|
||||||
|
input_modal_text = "#fcfcfc"
|
||||||
|
input_modal_field_bg = "#e6ba7e"
|
||||||
|
input_modal_field_text = "#5c6166"
|
||||||
|
|
||||||
|
bkmk_modal_bg = "#f2ae49"
|
||||||
|
bkmk_modal_text = "#fcfcfc"
|
||||||
|
bkmk_modal_label = "#fcfcfc"
|
||||||
|
bkmk_modal_field_bg = "#e6ba7e"
|
||||||
|
bkmk_modal_field_text = "#5c6166"
|
||||||
|
|
||||||
|
subscription_modal_bg = "#f2ae49"
|
||||||
|
subscription_modal_text = "#5c6166"
|
48
contrib/themes/dracula-variant.toml
Normal file
48
contrib/themes/dracula-variant.toml
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
#[theme]
|
||||||
|
bg = "#282a36"
|
||||||
|
tab_num = "#bd93f9"
|
||||||
|
tab_divider = "#f8f8f2"
|
||||||
|
bottombar_label = "#bd93f9"
|
||||||
|
bottombar_text = "#8be9fd"
|
||||||
|
bottombar_bg = "#44475a"
|
||||||
|
scrollbar = "#44475a"
|
||||||
|
|
||||||
|
hdg_1 = "#bd93f9"
|
||||||
|
hdg_2 = "#bd93f9"
|
||||||
|
hdg_3 = "#bd93f9"
|
||||||
|
amfora_link = "#ff79c6"
|
||||||
|
foreign_link = "#ffb86c"
|
||||||
|
link_number = "#8be9fd"
|
||||||
|
regular_text = "#f8f8f2"
|
||||||
|
quote_text = "#f1fa8c"
|
||||||
|
preformatted_text = "#ffb86c"
|
||||||
|
list_text = "#f8f8f2"
|
||||||
|
|
||||||
|
btn_bg = "#44475a"
|
||||||
|
btn_text = "#f8f8f2"
|
||||||
|
|
||||||
|
dl_choice_modal_bg = "#6272a4"
|
||||||
|
dl_choice_modal_text = "#f8f8f2"
|
||||||
|
dl_modal_bg = "#6272a4"
|
||||||
|
dl_modal_text = "#f8f8f2"
|
||||||
|
info_modal_bg = "#6272a4"
|
||||||
|
info_modal_text = "#f8f8f2"
|
||||||
|
error_modal_bg = "#ff5555"
|
||||||
|
error_modal_text = "#f8f8f2"
|
||||||
|
yesno_modal_bg = "#6272a4"
|
||||||
|
yesno_modal_text = "#f8f8f2"
|
||||||
|
tofu_modal_bg = "#6272a4"
|
||||||
|
tofu_modal_text = "#f8f8f2"
|
||||||
|
subscription_modal_bg = "#6272a4"
|
||||||
|
subscription_modal_text = "#f8f8f2"
|
||||||
|
|
||||||
|
input_modal_bg = "#6272a4"
|
||||||
|
input_modal_text = "#f8f8f2"
|
||||||
|
input_modal_field_bg = "#44475a"
|
||||||
|
input_modal_field_text = "#f8f8f2"
|
||||||
|
|
||||||
|
bkmk_modal_bg = "#6272a4"
|
||||||
|
bkmk_modal_text = "#f8f8f2"
|
||||||
|
bkmk_modal_label = "#f8f8f2"
|
||||||
|
bkmk_modal_field_bg = "#44475a"
|
||||||
|
bkmk_modal_field_text = "#f8f8f2"
|
@ -1,4 +1,4 @@
|
|||||||
[theme]
|
#[theme]
|
||||||
# This section is for changing the COLORS used in Amfora.
|
# This section is for changing the COLORS used in Amfora.
|
||||||
# These colors only apply if 'color' is enabled above.
|
# These colors only apply if 'color' is enabled above.
|
||||||
# Colors can be set using a W3C color name, or a hex value such as "#ffffff".
|
# Colors can be set using a W3C color name, or a hex value such as "#ffffff".
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
[theme]
|
#[theme]
|
||||||
bg = "#ffffff"
|
bg = "#ffffff"
|
||||||
tab_num = "#000000"
|
tab_num = "#000000"
|
||||||
tab_divider = "#000000"
|
tab_divider = "#000000"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
[theme]
|
#[theme]
|
||||||
# This section is for changing the COLORS used in Amfora.
|
# This section is for changing the COLORS used in Amfora.
|
||||||
# These colors only apply if 'color' is enabled above.
|
# These colors only apply if 'color' is enabled above.
|
||||||
# Colors can be set using a W3C color name, or a hex value such as "#ffffff".
|
# Colors can be set using a W3C color name, or a hex value such as "#ffffff".
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
[theme]
|
#[theme]
|
||||||
|
|
||||||
# Gruvbox Dark theme
|
# Gruvbox Dark theme
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
[theme]
|
#[theme]
|
||||||
# This section is for changing the COLORS used in Amfora.
|
# This section is for changing the COLORS used in Amfora.
|
||||||
# These colors only apply if 'color' is enabled above.
|
# These colors only apply if 'color' is enabled above.
|
||||||
# Colors can be set using a W3C color name, or a hex value such as "#ffffff".
|
# Colors can be set using a W3C color name, or a hex value such as "#ffffff".
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
[theme]
|
#[theme]
|
||||||
# This section is for changing the COLORS used in Amfora.
|
# This section is for changing the COLORS used in Amfora.
|
||||||
# These colors only apply if 'color' is enabled above.
|
# These colors only apply if 'color' is enabled above.
|
||||||
# Colors can be set using a W3C color name, or a hex value such as "#ffffff".
|
# Colors can be set using a W3C color name, or a hex value such as "#ffffff".
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# Atom One Dark theme ported to Amfora
|
# Atom One Dark theme ported to Amfora
|
||||||
# by Serge Tymoshenko <serge@tymo.name>
|
# by Serge Tymoshenko <serge@tymo.name>
|
||||||
|
|
||||||
[theme]
|
#[theme]
|
||||||
# This section is for changing the COLORS used in Amfora.
|
# This section is for changing the COLORS used in Amfora.
|
||||||
# These colors only apply if 'color' is enabled above.
|
# These colors only apply if 'color' is enabled above.
|
||||||
# Colors can be set using a W3C color name, or a hex value such as "#ffffff".
|
# Colors can be set using a W3C color name, or a hex value such as "#ffffff".
|
||||||
|
46
contrib/themes/rose-pine-dawn.toml
Normal file
46
contrib/themes/rose-pine-dawn.toml
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
## name: Rosé Pine
|
||||||
|
## upstream: https://github.com/rose-pine/amfora/blob/main/themes/rose-pine-dawn.toml
|
||||||
|
## description: All natural pine, faux fur and a bit of soho vibes for the classy minimalist
|
||||||
|
|
||||||
|
bg = "#faf4ed"
|
||||||
|
tab_num = "#907aa9"
|
||||||
|
tab_divider = "#dfdad9"
|
||||||
|
bottombar_label = "#907aa9"
|
||||||
|
bottombar_text = "#575279"
|
||||||
|
bottombar_bg = "#fffaf3"
|
||||||
|
scrollbar = "#f4ede8"
|
||||||
|
hdg_1 = "#907aa9"
|
||||||
|
hdg_2 = "#56949f"
|
||||||
|
hdg_3 = "#d7827e"
|
||||||
|
amfora_link = "#ea9d34"
|
||||||
|
foreign_link = "#797593"
|
||||||
|
link_number = "#9893a5"
|
||||||
|
regular_text = "#575279"
|
||||||
|
quote_text = "#575279"
|
||||||
|
preformatted_text = "#575279"
|
||||||
|
list_text = "#575279"
|
||||||
|
btn_bg = "#286983"
|
||||||
|
btn_text = "#575279"
|
||||||
|
dl_choice_modal_bg = "#fffaf3"
|
||||||
|
dl_choice_modal_text = "#575279"
|
||||||
|
dl_modal_bg = "#fffaf3"
|
||||||
|
dl_modal_text = "#575279"
|
||||||
|
info_modal_bg = "#fffaf3"
|
||||||
|
info_modal_text = "#575279"
|
||||||
|
error_modal_bg = "#fffaf3"
|
||||||
|
error_modal_text = "#b4637a"
|
||||||
|
yesno_modal_bg = "#fffaf3"
|
||||||
|
yesno_modal_text = "#575279"
|
||||||
|
tofu_modal_bg = "#fffaf3"
|
||||||
|
tofu_modal_text = "#575279"
|
||||||
|
subscription_modal_bg = "#fffaf3"
|
||||||
|
subscription_modal_text = "#575279"
|
||||||
|
input_modal_bg = "#fffaf3"
|
||||||
|
input_modal_text = "#575279"
|
||||||
|
input_modal_field_bg = "#f2e9e1"
|
||||||
|
input_modal_field_text = "#575279"
|
||||||
|
bkmk_modal_bg = "#fffaf3"
|
||||||
|
bkmk_modal_text = "#575279"
|
||||||
|
bkmk_modal_label = "#907aa9"
|
||||||
|
bkmk_modal_field_bg = "#f2e9e1"
|
||||||
|
bkmk_modal_field_text = "#575279"
|
46
contrib/themes/rose-pine-moon.toml
Normal file
46
contrib/themes/rose-pine-moon.toml
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
## name: Rosé Pine
|
||||||
|
## upstream: https://github.com/rose-pine/amfora/blob/main/themes/rose-pine-moon.toml
|
||||||
|
## description: All natural pine, faux fur and a bit of soho vibes for the classy minimalist
|
||||||
|
|
||||||
|
bg = "#232136"
|
||||||
|
tab_num = "#c4a7e7"
|
||||||
|
tab_divider = "#44415a"
|
||||||
|
bottombar_label = "#c4a7e7"
|
||||||
|
bottombar_text = "#e0def4"
|
||||||
|
bottombar_bg = "#2a273f"
|
||||||
|
scrollbar = "#2a283e"
|
||||||
|
hdg_1 = "#c4a7e7"
|
||||||
|
hdg_2 = "#9ccfd8"
|
||||||
|
hdg_3 = "#ea9a97"
|
||||||
|
amfora_link = "#f6c177"
|
||||||
|
foreign_link = "#908caa"
|
||||||
|
link_number = "#6e6a86"
|
||||||
|
regular_text = "#e0def4"
|
||||||
|
quote_text = "#e0def4"
|
||||||
|
preformatted_text = "#e0def4"
|
||||||
|
list_text = "#e0def4"
|
||||||
|
btn_bg = "#3e8fb0"
|
||||||
|
btn_text = "#e0def4"
|
||||||
|
dl_choice_modal_bg = "#2a273f"
|
||||||
|
dl_choice_modal_text = "#e0def4"
|
||||||
|
dl_modal_bg = "#2a273f"
|
||||||
|
dl_modal_text = "#e0def4"
|
||||||
|
info_modal_bg = "#2a273f"
|
||||||
|
info_modal_text = "#e0def4"
|
||||||
|
error_modal_bg = "#2a273f"
|
||||||
|
error_modal_text = "#eb6f92"
|
||||||
|
yesno_modal_bg = "#2a273f"
|
||||||
|
yesno_modal_text = "#e0def4"
|
||||||
|
tofu_modal_bg = "#2a273f"
|
||||||
|
tofu_modal_text = "#e0def4"
|
||||||
|
subscription_modal_bg = "#2a273f"
|
||||||
|
subscription_modal_text = "#e0def4"
|
||||||
|
input_modal_bg = "#2a273f"
|
||||||
|
input_modal_text = "#e0def4"
|
||||||
|
input_modal_field_bg = "#393552"
|
||||||
|
input_modal_field_text = "#e0def4"
|
||||||
|
bkmk_modal_bg = "#2a273f"
|
||||||
|
bkmk_modal_text = "#e0def4"
|
||||||
|
bkmk_modal_label = "#c4a7e7"
|
||||||
|
bkmk_modal_field_bg = "#393552"
|
||||||
|
bkmk_modal_field_text = "#e0def4"
|
46
contrib/themes/rose-pine.toml
Normal file
46
contrib/themes/rose-pine.toml
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
## name: Rosé Pine
|
||||||
|
## upstream: https://github.com/rose-pine/amfora/blob/main/themes/rose-pine.toml
|
||||||
|
## description: All natural pine, faux fur and a bit of soho vibes for the classy minimalist
|
||||||
|
|
||||||
|
bg = "#191724"
|
||||||
|
tab_num = "#c4a7e7"
|
||||||
|
tab_divider = "#403d52"
|
||||||
|
bottombar_label = "#c4a7e7"
|
||||||
|
bottombar_text = "#e0def4"
|
||||||
|
bottombar_bg = "#1f1d2e"
|
||||||
|
scrollbar = "#21202e"
|
||||||
|
hdg_1 = "#c4a7e7"
|
||||||
|
hdg_2 = "#9ccfd8"
|
||||||
|
hdg_3 = "#ebbcba"
|
||||||
|
amfora_link = "#f6c177"
|
||||||
|
foreign_link = "#908caa"
|
||||||
|
link_number = "#6e6a86"
|
||||||
|
regular_text = "#e0def4"
|
||||||
|
quote_text = "#e0def4"
|
||||||
|
preformatted_text = "#e0def4"
|
||||||
|
list_text = "#e0def4"
|
||||||
|
btn_bg = "#31748f"
|
||||||
|
btn_text = "#e0def4"
|
||||||
|
dl_choice_modal_bg = "#1f1d2e"
|
||||||
|
dl_choice_modal_text = "#e0def4"
|
||||||
|
dl_modal_bg = "#1f1d2e"
|
||||||
|
dl_modal_text = "#e0def4"
|
||||||
|
info_modal_bg = "#1f1d2e"
|
||||||
|
info_modal_text = "#e0def4"
|
||||||
|
error_modal_bg = "#1f1d2e"
|
||||||
|
error_modal_text = "#eb6f92"
|
||||||
|
yesno_modal_bg = "#1f1d2e"
|
||||||
|
yesno_modal_text = "#e0def4"
|
||||||
|
tofu_modal_bg = "#1f1d2e"
|
||||||
|
tofu_modal_text = "#e0def4"
|
||||||
|
subscription_modal_bg = "#1f1d2e"
|
||||||
|
subscription_modal_text = "#e0def4"
|
||||||
|
input_modal_bg = "#1f1d2e"
|
||||||
|
input_modal_text = "#e0def4"
|
||||||
|
input_modal_field_bg = "#26233a"
|
||||||
|
input_modal_field_text = "#e0def4"
|
||||||
|
bkmk_modal_bg = "#1f1d2e"
|
||||||
|
bkmk_modal_text = "#e0def4"
|
||||||
|
bkmk_modal_label = "#c4a7e7"
|
||||||
|
bkmk_modal_field_bg = "#26233a"
|
||||||
|
bkmk_modal_field_text = "#e0def4"
|
@ -1,4 +1,4 @@
|
|||||||
[theme]
|
#[theme]
|
||||||
# This section is for changing the COLORS used in Amfora.
|
# This section is for changing the COLORS used in Amfora.
|
||||||
# These colors only apply if 'color' is enabled above.
|
# These colors only apply if 'color' is enabled above.
|
||||||
# Colors can be set using a W3C color name, or a hex value such as "#ffffff".
|
# Colors can be set using a W3C color name, or a hex value such as "#ffffff".
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
[theme]
|
#[theme]
|
||||||
# This section is for changing the COLORS used in Amfora.
|
# This section is for changing the COLORS used in Amfora.
|
||||||
# These colors only apply if 'color' is enabled above.
|
# These colors only apply if 'color' is enabled above.
|
||||||
# Colors can be set using a W3C color name, or a hex value such as "#ffffff".
|
# Colors can be set using a W3C color name, or a hex value such as "#ffffff".
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
[theme]
|
#[theme]
|
||||||
# This section is for changing the COLORS used in Amfora.
|
# This section is for changing the COLORS used in Amfora.
|
||||||
# These colors only apply if 'color' is enabled above.
|
# These colors only apply if 'color' is enabled above.
|
||||||
# Colors can be set using a W3C color name, or a hex value such as "#ffffff".
|
# Colors can be set using a W3C color name, or a hex value such as "#ffffff".
|
||||||
|
52
contrib/themes/tokyo-night.toml
Normal file
52
contrib/themes/tokyo-night.toml
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
#[theme]
|
||||||
|
|
||||||
|
# Tokyo Night
|
||||||
|
|
||||||
|
bg = "#1a1b26"
|
||||||
|
fg = "#a9b1d6"
|
||||||
|
tab_num = "#565f89"
|
||||||
|
tab_divider = "#3b4261"
|
||||||
|
bottombar_label = "#7aa2f7"
|
||||||
|
bottombar_text = "#7aa2f7"
|
||||||
|
bottombar_bg = "#1f2335"
|
||||||
|
scrollbar = "#565f89"
|
||||||
|
|
||||||
|
hdg_1 = "#f7768e"
|
||||||
|
hdg_2 = "#7dcfff"
|
||||||
|
hdg_3 = "#bb9af7"
|
||||||
|
amfora_link = "#73daca"
|
||||||
|
foreign_link = "#b4f9f8"
|
||||||
|
link_number = "#ff9e64"
|
||||||
|
regular_text = "#a9b1d6"
|
||||||
|
quote_text = "#e0af68"
|
||||||
|
preformatted_text = "#2ac3de"
|
||||||
|
list_text = "#a9b1d6"
|
||||||
|
|
||||||
|
btn_bg = "#414868"
|
||||||
|
btn_text = "#7aa2f7"
|
||||||
|
|
||||||
|
dl_choice_modal_bg = "#414868"
|
||||||
|
dl_choice_modal_text = "#c0caf5"
|
||||||
|
dl_modal_bg = "#414868"
|
||||||
|
dl_modal_text = "#c0caf5"
|
||||||
|
info_modal_bg = "#414868"
|
||||||
|
info_modal_text = "#c0caf5"
|
||||||
|
error_modal_bg = "#414868"
|
||||||
|
error_modal_text = "#f7768e"
|
||||||
|
yesno_modal_bg = "#414868"
|
||||||
|
yesno_modal_text = "#e0af68"
|
||||||
|
tofu_modal_bg = "#414868"
|
||||||
|
tofu_modal_text = "#2ac3de"
|
||||||
|
subscription_modal_bg = "#414868"
|
||||||
|
subscription_modal_text = "#bb9af7"
|
||||||
|
|
||||||
|
input_modal_bg = "#414868"
|
||||||
|
input_modal_text = "#c0caf5"
|
||||||
|
input_modal_field_bg = "#33467c"
|
||||||
|
input_modal_field_text = "#a9b1d6"
|
||||||
|
|
||||||
|
bkmk_modal_bg = "#414868"
|
||||||
|
bkmk_modal_text = "#c0caf5"
|
||||||
|
bkmk_modal_label = "#c0caf5"
|
||||||
|
bkmk_modal_field_bg = "#33467c"
|
||||||
|
bkmk_modal_field_text = "#a9b1d6"
|
@ -1,5 +1,16 @@
|
|||||||
# This is the default config file.
|
# This is the default config file.
|
||||||
# It also shows all the default values, if you don't create the file.
|
# It also shows all the default values, if you don't create the file.
|
||||||
|
# You can edit this file to set your own configuration for Amfora.
|
||||||
|
|
||||||
|
# When Amfora updates, defaults may change, but this file on your drive will not.
|
||||||
|
# You can always get the latest defaults on GitHub.
|
||||||
|
# https://github.com/makeworld-the-better-one/amfora/blob/master/default-config.toml
|
||||||
|
|
||||||
|
# Please also check out the Amfora Wiki for more help
|
||||||
|
# https://github.com/makeworld-the-better-one/amfora/wiki
|
||||||
|
# gemini://makeworld.space/amfora-wiki/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# All URL values may omit the scheme and/or port, as well as the beginning double slash
|
# All URL values may omit the scheme and/or port, as well as the beginning double slash
|
||||||
# Valid URL examples:
|
# Valid URL examples:
|
||||||
@ -11,7 +22,7 @@
|
|||||||
|
|
||||||
[a-general]
|
[a-general]
|
||||||
# Press Ctrl-H to access it
|
# Press Ctrl-H to access it
|
||||||
home = "gemini://gemini.circumlunar.space"
|
home = "gemini://geminiprotocol.net"
|
||||||
|
|
||||||
# Follow up to 5 Gemini redirects without prompting.
|
# Follow up to 5 Gemini redirects without prompting.
|
||||||
# A prompt is always shown after the 5th redirect and for redirects to protocols other than Gemini.
|
# A prompt is always shown after the 5th redirect and for redirects to protocols other than Gemini.
|
||||||
@ -23,7 +34,7 @@ auto_redirect = false
|
|||||||
# If a command is set, than the URL will be added (in quotes) to the end of the command.
|
# If a command is set, than the URL will be added (in quotes) to the end of the command.
|
||||||
# A space will be prepended to the URL.
|
# A space will be prepended to the URL.
|
||||||
#
|
#
|
||||||
# The best to define a command is using a string array.
|
# The best way to define a command is using a string array.
|
||||||
# Examples:
|
# Examples:
|
||||||
# http = ['firefox']
|
# http = ['firefox']
|
||||||
# http = ['custom-browser', '--flag', '--option=2']
|
# http = ['custom-browser', '--flag', '--option=2']
|
||||||
@ -44,17 +55,20 @@ color = true
|
|||||||
# Whether ANSI color codes from the page content should be rendered
|
# Whether ANSI color codes from the page content should be rendered
|
||||||
ansi = true
|
ansi = true
|
||||||
|
|
||||||
|
# Whether or not to support source code highlighting in preformatted blocks based on alt text
|
||||||
|
highlight_code = true
|
||||||
|
|
||||||
|
# Which highlighting style to use (see https://xyproto.github.io/splash/docs/)
|
||||||
|
highlight_style = "monokai"
|
||||||
|
|
||||||
# Whether to replace list asterisks with unicode bullets
|
# Whether to replace list asterisks with unicode bullets
|
||||||
bullets = true
|
bullets = true
|
||||||
|
|
||||||
# Whether to show link after link text
|
# Whether to show link after link text
|
||||||
show_link = false
|
show_link = false
|
||||||
|
|
||||||
# A number from 0 to 1, indicating what percentage of the terminal width the left margin should take up.
|
|
||||||
left_margin = 0.15
|
|
||||||
|
|
||||||
# The max number of columns to wrap a page's text to. Preformatted blocks are not wrapped.
|
# The max number of columns to wrap a page's text to. Preformatted blocks are not wrapped.
|
||||||
max_width = 100
|
max_width = 80
|
||||||
|
|
||||||
# 'downloads' is the path to a downloads folder.
|
# 'downloads' is the path to a downloads folder.
|
||||||
# An empty value means the code will find the default downloads folder for your system.
|
# An empty value means the code will find the default downloads folder for your system.
|
||||||
@ -71,6 +85,10 @@ page_max_time = 10
|
|||||||
# "auto" means the scrollbar only appears when the page is longer than the window.
|
# "auto" means the scrollbar only appears when the page is longer than the window.
|
||||||
scrollbar = "auto"
|
scrollbar = "auto"
|
||||||
|
|
||||||
|
# Underline non-gemini URLs
|
||||||
|
# This is done to help color blind users
|
||||||
|
underline = true
|
||||||
|
|
||||||
|
|
||||||
[auth]
|
[auth]
|
||||||
# Authentication settings
|
# Authentication settings
|
||||||
@ -78,13 +96,17 @@ scrollbar = "auto"
|
|||||||
|
|
||||||
[auth.certs]
|
[auth.certs]
|
||||||
# Client certificates
|
# Client certificates
|
||||||
# Set domain name equal to path to client cert
|
# Set URL equal to path to client cert file
|
||||||
# "example.com" = 'mycert.crt'
|
#
|
||||||
|
# "example.com" = 'mycert.crt' # Cert is used for all paths on this domain
|
||||||
|
# "example.com/dir/"= 'mycert.crt' # Cert is used for /dir/ and everything below only
|
||||||
|
#
|
||||||
|
# See the comment at the beginning of this file for examples of all valid types of
|
||||||
|
# URLs, ports and schemes can be used too
|
||||||
|
|
||||||
[auth.keys]
|
[auth.keys]
|
||||||
# Client certificate keys
|
# Client certificate keys
|
||||||
# Set domain name equal to path to key for the client cert above
|
# Same as [auth.certs] but the path is to the client key file.
|
||||||
# "example.com" = 'mycert.key'
|
|
||||||
|
|
||||||
|
|
||||||
[keybindings]
|
[keybindings]
|
||||||
@ -163,24 +185,53 @@ scrollbar = "auto"
|
|||||||
# bind_copy_target_url
|
# bind_copy_target_url
|
||||||
# bind_beginning: moving to beginning of page (top left)
|
# bind_beginning: moving to beginning of page (top left)
|
||||||
# bind_end: same but the for the end (bottom left)
|
# bind_end: same but the for the end (bottom left)
|
||||||
|
# bind_url_handler_open: Open highlighted URL with URL handler (#143)
|
||||||
|
|
||||||
|
# Search
|
||||||
|
# bind_search = "/"
|
||||||
|
# bind_next_match = "n"
|
||||||
|
# bind_prev_match = "N"
|
||||||
|
|
||||||
[url-handlers]
|
[url-handlers]
|
||||||
# Allows setting the commands to run for various URL schemes.
|
# Allows setting the commands to run for various URL schemes.
|
||||||
# E.g. to open FTP URLs with FileZilla set the following key:
|
# E.g. to open FTP URLs with FileZilla set the following key:
|
||||||
# ftp = 'filezilla'
|
# ftp = ['filezilla']
|
||||||
# You can set any scheme to "off" or "" to disable handling it, or
|
# You can set any scheme to 'off' or '' to disable handling it, or
|
||||||
# just leave the key unset.
|
# just leave the key unset.
|
||||||
#
|
#
|
||||||
# DO NOT use this for setting the HTTP command.
|
# DO NOT use this for setting the HTTP command.
|
||||||
# Use the http setting in the "a-general" section above.
|
# Use the http setting in the "a-general" section above.
|
||||||
#
|
#
|
||||||
# NOTE: These settings are overrided by the ones in the proxies section.
|
# NOTE: These settings are overrided by the ones in the proxies section.
|
||||||
|
#
|
||||||
|
# The best way to define a command is using a string array.
|
||||||
|
# Examples:
|
||||||
|
# magnet = ['transmission']
|
||||||
|
# foo = ['custom-browser', '--flag', '--option=2']
|
||||||
|
# tel = ['/path/with spaces/in it/telephone']
|
||||||
|
#
|
||||||
# Note the use of single quotes, so that backslashes will not be escaped.
|
# Note the use of single quotes, so that backslashes will not be escaped.
|
||||||
|
# Using just a string will also work, but it is deprecated, and will degrade if
|
||||||
|
# you use paths with spaces.
|
||||||
|
|
||||||
# This is a special key that defines the handler for all URL schemes for which
|
# This is a special key that defines the handler for all URL schemes for which
|
||||||
# no handler is defined.
|
# no handler is defined.
|
||||||
other = 'off'
|
# It uses the special value 'default', which will try and use the default
|
||||||
|
# application on your computer for opening this kind of URI.
|
||||||
|
other = 'default'
|
||||||
|
|
||||||
|
[url-prompts]
|
||||||
|
# Specify whether a confirmation prompt should be shown before following URL schemes.
|
||||||
|
# The special key 'other' matches all schemes that don't match any other key.
|
||||||
|
#
|
||||||
|
# Example: prompt on every non-gemini URL
|
||||||
|
# other = true
|
||||||
|
# gemini = false
|
||||||
|
#
|
||||||
|
# Example: only prompt on HTTP(S)
|
||||||
|
# other = false
|
||||||
|
# http = true
|
||||||
|
# https = true
|
||||||
|
|
||||||
# [[mediatype-handlers]] section
|
# [[mediatype-handlers]] section
|
||||||
# ---------------------------------
|
# ---------------------------------
|
||||||
@ -297,11 +348,18 @@ workers = 3
|
|||||||
# The number of subscription updates displayed per page.
|
# The number of subscription updates displayed per page.
|
||||||
entries_per_page = 20
|
entries_per_page = 20
|
||||||
|
|
||||||
|
# Set to false to remove the explanatory text from the top of the subscription page
|
||||||
|
header = true
|
||||||
|
|
||||||
|
|
||||||
[theme]
|
[theme]
|
||||||
# This section is for changing the COLORS used in Amfora.
|
# This section is for changing the COLORS used in Amfora.
|
||||||
# These colors only apply if 'color' is enabled above.
|
# These colors only apply if 'color' is enabled above.
|
||||||
# Colors can be set using a W3C color name, or a hex value such as "#ffffff".
|
# Colors can be set using a W3C color name, or a hex value such as "#ffffff".
|
||||||
|
# Setting a background to "default" keeps the terminal default
|
||||||
|
# If your terminal has transparency, set any background to "default" to keep it transparent
|
||||||
|
# The key "bg" is already set to "default", but this can be used on other backgrounds,
|
||||||
|
# like for modals.
|
||||||
|
|
||||||
# Note that not all colors will work on terminals that do not have truecolor support.
|
# Note that not all colors will work on terminals that do not have truecolor support.
|
||||||
# If you want to stick to the standard 16 or 256 colors, you can get
|
# If you want to stick to the standard 16 or 256 colors, you can get
|
||||||
@ -320,6 +378,7 @@ entries_per_page = 20
|
|||||||
# EXAMPLES:
|
# EXAMPLES:
|
||||||
# hdg_1 = "green"
|
# hdg_1 = "green"
|
||||||
# hdg_2 = "#5f0000"
|
# hdg_2 = "#5f0000"
|
||||||
|
# bg = "default"
|
||||||
|
|
||||||
# Available keys to set:
|
# Available keys to set:
|
||||||
|
|
||||||
@ -331,6 +390,15 @@ entries_per_page = 20
|
|||||||
# bottombar_bg
|
# bottombar_bg
|
||||||
# scrollbar: The scrollbar that appears on the right for long pages
|
# scrollbar: The scrollbar that appears on the right for long pages
|
||||||
|
|
||||||
|
# You can also set an 'include' key to process another TOML file that contains theme keys.
|
||||||
|
# Example:
|
||||||
|
# include = "my/path/to/special-theme.toml"
|
||||||
|
#
|
||||||
|
# Any other theme keys will override this external file.
|
||||||
|
# You can use this special key to switch between themes easily.
|
||||||
|
# Download other themes here: https://github.com/makeworld-the-better-one/amfora/tree/master/contrib/themes
|
||||||
|
|
||||||
|
|
||||||
# hdg_1
|
# hdg_1
|
||||||
# hdg_2
|
# hdg_2
|
||||||
# hdg_3
|
# hdg_3
|
||||||
|
@ -2,6 +2,8 @@ package display
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"code.rocketnine.space/tslocum/cview"
|
"code.rocketnine.space/tslocum/cview"
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
@ -28,8 +30,11 @@ const (
|
|||||||
var bkmkCh = make(chan bkmkAction)
|
var bkmkCh = make(chan bkmkAction)
|
||||||
var bkmkModalText string // The current text of the input field in the modal
|
var bkmkModalText string // The current text of the input field in the modal
|
||||||
|
|
||||||
|
// Regex for extracting top level 1 heading. The title will extracted from the 1st submatch.
|
||||||
|
var topHeadingRegex = regexp.MustCompile(`(?m)^#[^#][\t ]*[^\s].*$`)
|
||||||
|
|
||||||
func bkmkInit() {
|
func bkmkInit() {
|
||||||
panels.AddPanel("bkmk", bkmkModal, false, false)
|
panels.AddPanel(PanelBookmarks, bkmkModal, false, false)
|
||||||
|
|
||||||
m := bkmkModal
|
m := bkmkModal
|
||||||
if viper.GetBool("a-general.color") {
|
if viper.GetBool("a-general.color") {
|
||||||
@ -41,8 +46,10 @@ func bkmkInit() {
|
|||||||
form.SetLabelColor(config.GetColor("bkmk_modal_label"))
|
form.SetLabelColor(config.GetColor("bkmk_modal_label"))
|
||||||
form.SetFieldBackgroundColor(config.GetColor("bkmk_modal_field_bg"))
|
form.SetFieldBackgroundColor(config.GetColor("bkmk_modal_field_bg"))
|
||||||
form.SetFieldTextColor(config.GetColor("bkmk_modal_field_text"))
|
form.SetFieldTextColor(config.GetColor("bkmk_modal_field_text"))
|
||||||
|
form.SetFieldBackgroundColorFocused(config.GetColor("bkmk_modal_field_text"))
|
||||||
|
form.SetFieldTextColorFocused(config.GetTextColor("bkmk_modal_field_bg", "bkmk_modal_field_text"))
|
||||||
form.SetButtonBackgroundColorFocused(config.GetColor("btn_text"))
|
form.SetButtonBackgroundColorFocused(config.GetColor("btn_text"))
|
||||||
form.SetButtonTextColorFocused(config.GetColor("btn_bg"))
|
form.SetButtonTextColorFocused(config.GetTextColor("btn_bg", "btn_text"))
|
||||||
frame := m.GetFrame()
|
frame := m.GetFrame()
|
||||||
frame.SetBorderColor(config.GetColor("bkmk_modal_text"))
|
frame.SetBorderColor(config.GetColor("bkmk_modal_text"))
|
||||||
frame.SetTitleColor(config.GetColor("bkmk_modal_text"))
|
frame.SetTitleColor(config.GetColor("bkmk_modal_text"))
|
||||||
@ -109,13 +116,13 @@ func openBkmkModal(name string, exists bool) (string, bkmkAction) {
|
|||||||
bkmkModalText = text
|
bkmkModalText = text
|
||||||
})
|
})
|
||||||
|
|
||||||
panels.ShowPanel("bkmk")
|
panels.ShowPanel(PanelBookmarks)
|
||||||
panels.SendToFront("bkmk")
|
panels.SendToFront(PanelBookmarks)
|
||||||
App.SetFocus(bkmkModal)
|
App.SetFocus(bkmkModal)
|
||||||
App.Draw()
|
App.Draw()
|
||||||
|
|
||||||
action := <-bkmkCh
|
action := <-bkmkCh
|
||||||
panels.HidePanel("bkmk")
|
panels.HidePanel(PanelBookmarks)
|
||||||
App.SetFocus(tabs[curTab].view)
|
App.SetFocus(tabs[curTab].view)
|
||||||
App.Draw()
|
App.Draw()
|
||||||
|
|
||||||
@ -157,7 +164,17 @@ func addBookmark() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
name, exists := bookmarks.Get(p.URL)
|
name, exists := bookmarks.Get(p.URL)
|
||||||
|
|
||||||
|
// Retrieve & use top level 1 heading for name if bookmark does not already exist.
|
||||||
|
if !exists {
|
||||||
|
match := topHeadingRegex.FindString(p.Raw)
|
||||||
|
if match != "" {
|
||||||
|
name = strings.TrimSpace(match[1:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Open a bookmark modal with the current name of the bookmark, if it exists
|
// Open a bookmark modal with the current name of the bookmark, if it exists
|
||||||
|
// otherwise use the top level 1 heading as a suggested name
|
||||||
newName, action := openBkmkModal(name, exists)
|
newName, action := openBkmkModal(name, exists)
|
||||||
|
|
||||||
//nolint:exhaustive
|
//nolint:exhaustive
|
||||||
|
@ -6,15 +6,16 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"code.rocketnine.space/tslocum/cview"
|
"code.rocketnine.space/tslocum/cview"
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
"github.com/makeworld-the-better-one/amfora/cache"
|
"github.com/makeworld-the-better-one/amfora/cache"
|
||||||
|
"github.com/makeworld-the-better-one/amfora/client"
|
||||||
"github.com/makeworld-the-better-one/amfora/config"
|
"github.com/makeworld-the-better-one/amfora/config"
|
||||||
"github.com/makeworld-the-better-one/amfora/renderer"
|
"github.com/makeworld-the-better-one/amfora/renderer"
|
||||||
"github.com/makeworld-the-better-one/amfora/structs"
|
"github.com/makeworld-the-better-one/amfora/structs"
|
||||||
"github.com/makeworld-the-better-one/go-gemini"
|
"github.com/makeworld-the-better-one/go-gemini"
|
||||||
|
"github.com/muesli/termenv"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -29,8 +30,10 @@ var termH int
|
|||||||
var bottomBar = cview.NewInputField()
|
var bottomBar = cview.NewInputField()
|
||||||
|
|
||||||
var originalText []byte
|
var originalText []byte
|
||||||
var searchBar = cview.NewInputField()
|
var tagsRegex = regexp.MustCompile(`\[[a-zA-Z0-9_,;: \-\."#]+[^\[]*\]`)
|
||||||
|
var searchMode = false
|
||||||
var searchString = ""
|
var searchString = ""
|
||||||
|
var bottomBarText = ""
|
||||||
var matches = 0
|
var matches = 0
|
||||||
var curMatch = 0
|
var curMatch = 0
|
||||||
|
|
||||||
@ -58,14 +61,23 @@ var layout = cview.NewFlex()
|
|||||||
|
|
||||||
var newTabPage structs.Page
|
var newTabPage structs.Page
|
||||||
|
|
||||||
// Global mutex for changing the size of the left margin on all tabs.
|
|
||||||
var reformatMu = sync.Mutex{}
|
|
||||||
|
|
||||||
var App = cview.NewApplication()
|
var App = cview.NewApplication()
|
||||||
|
|
||||||
func Init(version, commit, builtBy string) {
|
func Init(version, commit, builtBy string) {
|
||||||
aboutInit(version, commit, builtBy)
|
aboutInit(version, commit, builtBy)
|
||||||
|
|
||||||
|
// Detect terminal colors for syntax highlighting
|
||||||
|
switch termenv.ColorProfile() {
|
||||||
|
case termenv.TrueColor:
|
||||||
|
renderer.TermColor = "terminal16m"
|
||||||
|
case termenv.ANSI256:
|
||||||
|
renderer.TermColor = "terminal256"
|
||||||
|
case termenv.ANSI:
|
||||||
|
renderer.TermColor = "terminal16"
|
||||||
|
case termenv.Ascii:
|
||||||
|
renderer.TermColor = ""
|
||||||
|
}
|
||||||
|
|
||||||
App.EnableMouse(false)
|
App.EnableMouse(false)
|
||||||
App.SetRoot(layout, true)
|
App.SetRoot(layout, true)
|
||||||
App.SetAfterResizeFunc(func(width int, height int) {
|
App.SetAfterResizeFunc(func(width int, height int) {
|
||||||
@ -74,26 +86,21 @@ func Init(version, commit, builtBy string) {
|
|||||||
termH = height
|
termH = height
|
||||||
|
|
||||||
// Make sure the current tab content is reformatted when the terminal size changes
|
// Make sure the current tab content is reformatted when the terminal size changes
|
||||||
go func(t *tab) {
|
for i := range tabs {
|
||||||
reformatMu.Lock() // Only allow one reformat job at a time
|
// Overwrite all tabs with a new, differently sized, left margin
|
||||||
for i := range tabs {
|
browser.AddTab(
|
||||||
// Overwrite all tabs with a new, differently sized, left margin
|
strconv.Itoa(i),
|
||||||
browser.AddTab(
|
tabs[i].label(),
|
||||||
strconv.Itoa(i),
|
makeContentLayout(tabs[i].view, leftMargin()),
|
||||||
makeTabLabel(strconv.Itoa(i+1)),
|
)
|
||||||
makeContentLayout(tabs[i].view, leftMargin()),
|
if tabs[i] == tabs[curTab] {
|
||||||
)
|
// Reformat page ASAP, in the middle of loop
|
||||||
if tabs[i] == t {
|
reformatPageAndSetView(tabs[curTab], tabs[curTab].page)
|
||||||
// Reformat page ASAP, in the middle of loop
|
|
||||||
reformatPageAndSetView(t, t.page)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
App.Draw()
|
}
|
||||||
reformatMu.Unlock()
|
|
||||||
}(tabs[curTab])
|
|
||||||
})
|
})
|
||||||
|
|
||||||
panels.AddPanel("browser", browser, true, true)
|
panels.AddPanel(PanelBrowser, browser, true, true)
|
||||||
|
|
||||||
helpInit()
|
helpInit()
|
||||||
|
|
||||||
@ -102,8 +109,6 @@ func Init(version, commit, builtBy string) {
|
|||||||
layout.AddItem(bottomBar, 1, 1, false)
|
layout.AddItem(bottomBar, 1, 1, false)
|
||||||
|
|
||||||
if viper.GetBool("a-general.color") {
|
if viper.GetBool("a-general.color") {
|
||||||
layout.SetBackgroundColor(config.GetColor("bg"))
|
|
||||||
|
|
||||||
bottomBar.SetBackgroundColor(config.GetColor("bottombar_bg"))
|
bottomBar.SetBackgroundColor(config.GetColor("bottombar_bg"))
|
||||||
bottomBar.SetLabelColor(config.GetColor("bottombar_label"))
|
bottomBar.SetLabelColor(config.GetColor("bottombar_label"))
|
||||||
bottomBar.SetFieldBackgroundColor(config.GetColor("bottombar_bg"))
|
bottomBar.SetFieldBackgroundColor(config.GetColor("bottombar_bg"))
|
||||||
@ -112,7 +117,7 @@ func Init(version, commit, builtBy string) {
|
|||||||
browser.SetTabBackgroundColor(config.GetColor("bg"))
|
browser.SetTabBackgroundColor(config.GetColor("bg"))
|
||||||
browser.SetTabBackgroundColorFocused(config.GetColor("tab_num"))
|
browser.SetTabBackgroundColorFocused(config.GetColor("tab_num"))
|
||||||
browser.SetTabTextColor(config.GetColor("tab_num"))
|
browser.SetTabTextColor(config.GetColor("tab_num"))
|
||||||
browser.SetTabTextColorFocused(config.GetColor("bg"))
|
browser.SetTabTextColorFocused(config.GetColor("ColorBg"))
|
||||||
browser.SetTabSwitcherDivider(
|
browser.SetTabSwitcherDivider(
|
||||||
"",
|
"",
|
||||||
fmt.Sprintf("[%s:%s]|[-]", config.GetColorString("tab_divider"), config.GetColorString("bg")),
|
fmt.Sprintf("[%s:%s]|[-]", config.GetColorString("tab_divider"), config.GetColorString("bg")),
|
||||||
@ -142,6 +147,9 @@ func Init(version, commit, builtBy string) {
|
|||||||
// Reset func to set the bottomBar back to what it was before
|
// Reset func to set the bottomBar back to what it was before
|
||||||
// Use for errors.
|
// Use for errors.
|
||||||
reset := func() {
|
reset := func() {
|
||||||
|
if searchMode {
|
||||||
|
resetSearch()
|
||||||
|
}
|
||||||
bottomBar.SetLabel("")
|
bottomBar.SetLabel("")
|
||||||
tabs[tab].applyAll()
|
tabs[tab].applyAll()
|
||||||
App.SetFocus(tabs[tab].view)
|
App.SetFocus(tabs[tab].view)
|
||||||
@ -150,6 +158,63 @@ func Init(version, commit, builtBy string) {
|
|||||||
//nolint:exhaustive
|
//nolint:exhaustive
|
||||||
switch key {
|
switch key {
|
||||||
case tcell.KeyEnter:
|
case tcell.KeyEnter:
|
||||||
|
|
||||||
|
if searchMode {
|
||||||
|
// Escape the search string to not find regexp symbols
|
||||||
|
searchString = regexp.QuoteMeta(bottomBar.GetText())
|
||||||
|
|
||||||
|
if strings.TrimSpace(searchString) == "" {
|
||||||
|
// Ignore
|
||||||
|
reset()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if tabs[tab].mode != tabModeSearch {
|
||||||
|
originalText = tabs[curTab].view.GetBytes(false)
|
||||||
|
}
|
||||||
|
tabs[tab].mode = tabModeSearch
|
||||||
|
|
||||||
|
// find all positions of the search string
|
||||||
|
searchRegex := regexp.MustCompile(searchString)
|
||||||
|
searchIdx := searchRegex.FindAllIndex(originalText, -1)
|
||||||
|
|
||||||
|
// find all positions of tags
|
||||||
|
tagsIdx := tagsRegex.FindAllIndex(originalText, -1)
|
||||||
|
|
||||||
|
text := make([]byte, 0)
|
||||||
|
matches = 0
|
||||||
|
lastMatch := 0
|
||||||
|
|
||||||
|
// loops through all occurrences and check if they
|
||||||
|
// discard if they lie within tags.
|
||||||
|
// []byte text is build from the original text buffer
|
||||||
|
// with the actual search strings replaced by tagged regions
|
||||||
|
// to highlight.
|
||||||
|
for i, match := range searchIdx {
|
||||||
|
for _, tag := range tagsIdx {
|
||||||
|
if match[0] >= tag[0] && match[1] <= tag[1] {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
matches++
|
||||||
|
text = append(text, originalText[lastMatch:match[0]]...)
|
||||||
|
replacement := []byte(fmt.Sprint(`["search-`, i, `"]`, searchString, `[""]`))
|
||||||
|
text = append(text, replacement...)
|
||||||
|
lastMatch = match[0] + len(searchString)
|
||||||
|
}
|
||||||
|
text = append(text, originalText[lastMatch:]...)
|
||||||
|
|
||||||
|
tabs[curTab].view.SetBytes(text)
|
||||||
|
|
||||||
|
curMatch = 0
|
||||||
|
tabs[curTab].view.Highlight(fmt.Sprint("search-", "0"))
|
||||||
|
tabs[curTab].view.ScrollToHighlight()
|
||||||
|
App.SetFocus(tabs[tab].view)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Figure out whether it's a URL, link number, or search
|
// Figure out whether it's a URL, link number, or search
|
||||||
// And send out a request
|
// And send out a request
|
||||||
|
|
||||||
@ -195,16 +260,19 @@ func Init(version, commit, builtBy string) {
|
|||||||
if i <= len(tabs[tab].page.Links) && i > 0 {
|
if i <= len(tabs[tab].page.Links) && i > 0 {
|
||||||
// Open new tab and load link
|
// Open new tab and load link
|
||||||
oldTab := tab
|
oldTab := tab
|
||||||
NewTab()
|
|
||||||
// Resolve and follow link manually
|
// Resolve and follow link manually
|
||||||
prevParsed, _ := url.Parse(tabs[oldTab].page.URL)
|
|
||||||
nextParsed, err := url.Parse(tabs[oldTab].page.Links[i-1])
|
nextParsed, err := url.Parse(tabs[oldTab].page.Links[i-1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Error("URL Error", "link URL could not be parsed")
|
Error("URL Error", "link URL could not be parsed")
|
||||||
reset()
|
reset()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
URL(prevParsed.ResolveReference(nextParsed).String())
|
if tabs[oldTab].hasContent() && !tabs[oldTab].isAnAboutPage() {
|
||||||
|
prevParsed, _ := url.Parse(tabs[oldTab].page.URL)
|
||||||
|
NewTabWithURL(prevParsed.ResolveReference(nextParsed).String())
|
||||||
|
} else {
|
||||||
|
NewTabWithURL(nextParsed.String())
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -215,21 +283,22 @@ func Init(version, commit, builtBy string) {
|
|||||||
// We don't want to convert legitimate
|
// We don't want to convert legitimate
|
||||||
// :// links to search terms.
|
// :// links to search terms.
|
||||||
query := strings.TrimSpace(query)
|
query := strings.TrimSpace(query)
|
||||||
if (strings.Contains(query, " ") && !hasSpaceisURL.MatchString(query)) ||
|
if ((strings.Contains(query, " ") && !hasSpaceisURL.MatchString(query)) ||
|
||||||
(!strings.HasPrefix(query, "//") && !strings.Contains(query, "://") &&
|
(!strings.HasPrefix(query, "//") && !strings.Contains(query, "://") &&
|
||||||
!strings.Contains(query, ".")) && !strings.HasPrefix(query, "about:") {
|
!strings.Contains(query, ".")) && !strings.HasPrefix(query, "about:")) &&
|
||||||
|
!(query == "localhost" || strings.HasPrefix(query, "localhost/") || strings.HasPrefix(query, "localhost:")) {
|
||||||
// Has a space and follows regex, OR
|
// Has a space and follows regex, OR
|
||||||
// doesn't start with "//", contain "://", and doesn't have a dot either.
|
// doesn't start with "//", contain "://", and doesn't have a dot either.
|
||||||
// Then it's a search
|
// Then it's a search
|
||||||
|
|
||||||
u := viper.GetString("a-general.search") + "?" + gemini.QueryEscape(query)
|
u := viper.GetString("a-general.search") + "?" + gemini.QueryEscape(query)
|
||||||
// Don't use the cached version of the search
|
// Don't use the cached version of the search
|
||||||
cache.RemovePage(normalizeURL(u))
|
cache.RemovePage(client.NormalizeURL(u))
|
||||||
URL(u)
|
URL(u)
|
||||||
} else {
|
} else {
|
||||||
// Full URL
|
// Full URL
|
||||||
// Don't use cached version for manually entered URL
|
// Don't use cached version for manually entered URL
|
||||||
cache.RemovePage(normalizeURL(fixUserURL(query)))
|
cache.RemovePage(client.NormalizeURL(client.FixUserURL(query)))
|
||||||
URL(query)
|
URL(query)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@ -237,7 +306,7 @@ func Init(version, commit, builtBy string) {
|
|||||||
}
|
}
|
||||||
if i <= len(tabs[tab].page.Links) && i > 0 {
|
if i <= len(tabs[tab].page.Links) && i > 0 {
|
||||||
// It's a valid link number
|
// It's a valid link number
|
||||||
followLink(tabs[tab], tabs[tab].page.URL, tabs[tab].page.Links[i-1])
|
go followLink(tabs[tab], tabs[tab].page.URL, tabs[tab].page.Links[i-1])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Invalid link number, don't do anything
|
// Invalid link number, don't do anything
|
||||||
@ -252,88 +321,6 @@ func Init(version, commit, builtBy string) {
|
|||||||
// Other potential keys are Tab and Backtab, they are ignored
|
// Other potential keys are Tab and Backtab, they are ignored
|
||||||
})
|
})
|
||||||
|
|
||||||
searchBar.SetDoneFunc(func(key tcell.Key) {
|
|
||||||
tab := curTab
|
|
||||||
|
|
||||||
reset := func() {
|
|
||||||
searchBar.SetLabel("")
|
|
||||||
tabs[tab].applyAll()
|
|
||||||
App.SetFocus(tabs[tab].view)
|
|
||||||
layout.RemoveItem(searchBar)
|
|
||||||
tabs[tab].mode = tabModeDone
|
|
||||||
}
|
|
||||||
|
|
||||||
//nolint:exhaustive
|
|
||||||
switch key {
|
|
||||||
case tcell.KeyEnter:
|
|
||||||
// Figure out whether it's a URL, link number, or search
|
|
||||||
// And send out a request
|
|
||||||
|
|
||||||
// Escape the search string to not find regexp symbols
|
|
||||||
searchString = regexp.QuoteMeta(searchBar.GetText())
|
|
||||||
|
|
||||||
if strings.TrimSpace(searchString) == "" {
|
|
||||||
// Ignore
|
|
||||||
reset()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if tabs[tab].mode != tabModeSearch {
|
|
||||||
originalText = tabs[curTab].view.GetBytes(false)
|
|
||||||
}
|
|
||||||
tabs[tab].mode = tabModeSearch
|
|
||||||
|
|
||||||
// find all positions of the search string
|
|
||||||
searchRegex := regexp.MustCompile(searchString)
|
|
||||||
searchIdx := searchRegex.FindAllIndex(originalText, -1)
|
|
||||||
|
|
||||||
// find all positions of tags
|
|
||||||
tagsRegex := regexp.MustCompile(`\[.*?[^\[]\]`)
|
|
||||||
tagsIdx := tagsRegex.FindAllIndex(originalText, -1)
|
|
||||||
|
|
||||||
text := []byte("")
|
|
||||||
matches = 0
|
|
||||||
lastMatch := 0
|
|
||||||
var isMatch bool
|
|
||||||
|
|
||||||
// loops through all occurrences and check if they
|
|
||||||
// discard if they lie within tags.
|
|
||||||
// []byte text is build from the original text buffer
|
|
||||||
// with the actual search strings replaced by tagged regions
|
|
||||||
// to highlight.
|
|
||||||
for i, match := range searchIdx {
|
|
||||||
isMatch = true
|
|
||||||
for _, tag := range tagsIdx {
|
|
||||||
if match[0] >= tag[0] && match[1] <= tag[1] {
|
|
||||||
isMatch = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if isMatch {
|
|
||||||
matches++
|
|
||||||
text = append(text, originalText[lastMatch:match[0]]...)
|
|
||||||
replacement := []byte(fmt.Sprint("[\"search-", i, "\"]", searchString, "[\"\"]"))
|
|
||||||
text = append(text, replacement...)
|
|
||||||
lastMatch = match[0] + len(searchString)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
text = append(text, originalText[lastMatch:]...)
|
|
||||||
|
|
||||||
tabs[curTab].view.SetBytes(text)
|
|
||||||
|
|
||||||
curMatch = 0
|
|
||||||
tabs[curTab].view.Highlight(fmt.Sprint("search-", "0"))
|
|
||||||
tabs[curTab].view.ScrollToHighlight()
|
|
||||||
App.SetFocus(tabs[tab].view)
|
|
||||||
|
|
||||||
case tcell.KeyEsc:
|
|
||||||
// Set back to what it was
|
|
||||||
reset()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Other potential keys are Tab and Backtab, they are ignored
|
|
||||||
})
|
|
||||||
|
|
||||||
// Render the default new tab content ONCE and store it for later
|
// Render the default new tab content ONCE and store it for later
|
||||||
// This code is repeated in Reload()
|
// This code is repeated in Reload()
|
||||||
@ -368,9 +355,16 @@ func Init(version, commit, builtBy string) {
|
|||||||
// It's focused on a modal right now, nothing should interrupt
|
// It's focused on a modal right now, nothing should interrupt
|
||||||
return event
|
return event
|
||||||
}
|
}
|
||||||
_, ok = App.GetFocus().(*cview.Table)
|
frontPanelName, _ := panels.GetFrontPanel()
|
||||||
if ok {
|
if frontPanelName == PanelHelp {
|
||||||
// It's focused on help right now
|
// It's focused on help right now
|
||||||
|
if config.TranslateKeyEvent(event) == config.CmdQuit {
|
||||||
|
// Allow quit key to work, but nothing else
|
||||||
|
Stop()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Pass everything else directly, inhibiting other keybindings
|
||||||
|
// like for editing the URL
|
||||||
return event
|
return event
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -398,9 +392,7 @@ func Init(version, commit, builtBy string) {
|
|||||||
return nil
|
return nil
|
||||||
case config.CmdInvalid:
|
case config.CmdInvalid:
|
||||||
if event.Key() == tcell.KeyEsc {
|
if event.Key() == tcell.KeyEsc {
|
||||||
tabs[curTab].mode = tabModeDone
|
resetSearch()
|
||||||
tabs[curTab].view.SetBytes(originalText)
|
|
||||||
layout.RemoveItem(searchBar)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -425,10 +417,11 @@ func Init(version, commit, builtBy string) {
|
|||||||
App.SetFocus(bottomBar)
|
App.SetFocus(bottomBar)
|
||||||
return nil
|
return nil
|
||||||
case config.CmdSearch:
|
case config.CmdSearch:
|
||||||
layout.AddItem(searchBar, 2, 1, false)
|
bottomBar.SetLabel("[::b]Search: [::-]")
|
||||||
searchBar.SetLabel("")
|
bottomBarText = bottomBar.GetText()
|
||||||
searchBar.SetText("")
|
bottomBar.SetText("")
|
||||||
App.SetFocus(searchBar)
|
searchMode = true
|
||||||
|
App.SetFocus(bottomBar)
|
||||||
return nil
|
return nil
|
||||||
case config.CmdEdit:
|
case config.CmdEdit:
|
||||||
// Letter e allows to edit current URL
|
// Letter e allows to edit current URL
|
||||||
@ -452,8 +445,7 @@ func Init(version, commit, builtBy string) {
|
|||||||
Error("URL Error", err.Error())
|
Error("URL Error", err.Error())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
NewTab()
|
NewTabWithURL(next)
|
||||||
URL(next)
|
|
||||||
} else {
|
} else {
|
||||||
NewTab()
|
NewTab()
|
||||||
}
|
}
|
||||||
@ -501,6 +493,17 @@ func Stop() {
|
|||||||
// NewTab opens a new tab and switches to it, displaying the
|
// NewTab opens a new tab and switches to it, displaying the
|
||||||
// the default empty content because there's no URL.
|
// the default empty content because there's no URL.
|
||||||
func NewTab() {
|
func NewTab() {
|
||||||
|
NewTabWithURL("about:newtab")
|
||||||
|
|
||||||
|
bottomBar.SetLabel("")
|
||||||
|
bottomBar.SetText("")
|
||||||
|
tabs[NumTabs()-1].saveBottomBar()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTabWithURL opens a new tab and switches to it, displaying the
|
||||||
|
// the URL provided.
|
||||||
|
func NewTabWithURL(url string) {
|
||||||
// Create TextView and change curTab
|
// Create TextView and change curTab
|
||||||
// Set the TextView options, and the changed func to App.Draw()
|
// Set the TextView options, and the changed func to App.Draw()
|
||||||
// SetDoneFunc to do link highlighting
|
// SetDoneFunc to do link highlighting
|
||||||
@ -517,22 +520,28 @@ func NewTab() {
|
|||||||
curTab = NumTabs()
|
curTab = NumTabs()
|
||||||
|
|
||||||
tabs = append(tabs, makeNewTab())
|
tabs = append(tabs, makeNewTab())
|
||||||
temp := newTabPage // Copy
|
|
||||||
setPage(tabs[curTab], &temp)
|
var interstitial string
|
||||||
|
if !strings.HasPrefix(url, "about:") {
|
||||||
|
interstitial = "Loading " + url + "..."
|
||||||
|
}
|
||||||
|
|
||||||
|
setPage(tabs[curTab], renderPageFromString(interstitial))
|
||||||
|
|
||||||
|
// Regardless of the starting URL, about:newtab will
|
||||||
|
// be the history root.
|
||||||
tabs[curTab].addToHistory("about:newtab")
|
tabs[curTab].addToHistory("about:newtab")
|
||||||
tabs[curTab].history.pos = 0 // Manually set as first page
|
tabs[curTab].history.pos = 0 // Manually set as first page
|
||||||
|
|
||||||
browser.AddTab(
|
browser.AddTab(
|
||||||
strconv.Itoa(curTab),
|
strconv.Itoa(curTab),
|
||||||
makeTabLabel(strconv.Itoa(curTab+1)),
|
tabs[curTab].label(),
|
||||||
makeContentLayout(tabs[curTab].view, leftMargin()),
|
makeContentLayout(tabs[curTab].view, leftMargin()),
|
||||||
)
|
)
|
||||||
browser.SetCurrentTab(strconv.Itoa(curTab))
|
browser.SetCurrentTab(strconv.Itoa(curTab))
|
||||||
App.SetFocus(tabs[curTab].view)
|
App.SetFocus(tabs[curTab].view)
|
||||||
|
|
||||||
bottomBar.SetLabel("")
|
URL(url)
|
||||||
bottomBar.SetText("")
|
|
||||||
tabs[curTab].saveBottomBar()
|
|
||||||
|
|
||||||
// Draw just in case
|
// Draw just in case
|
||||||
App.Draw()
|
App.Draw()
|
||||||
@ -642,15 +651,39 @@ func Reload() {
|
|||||||
func URL(u string) {
|
func URL(u string) {
|
||||||
t := tabs[curTab]
|
t := tabs[curTab]
|
||||||
if strings.HasPrefix(u, "about:") {
|
if strings.HasPrefix(u, "about:") {
|
||||||
if final, ok := handleAbout(t, u); ok {
|
go goURL(t, u)
|
||||||
t.addToHistory(final)
|
} else {
|
||||||
}
|
go goURL(t, client.FixUserURL(u))
|
||||||
return
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RenderFromString(str string) {
|
||||||
|
t := tabs[curTab]
|
||||||
|
page := renderPageFromString(str)
|
||||||
|
setPage(t, page)
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderPageFromString(str string) *structs.Page {
|
||||||
|
rendered, links := renderer.RenderGemini(str, textWidth(), false)
|
||||||
|
page := &structs.Page{
|
||||||
|
Mediatype: structs.TextGemini,
|
||||||
|
Raw: str,
|
||||||
|
Content: rendered,
|
||||||
|
Links: links,
|
||||||
|
TermWidth: termW,
|
||||||
}
|
}
|
||||||
|
|
||||||
go goURL(t, fixUserURL(u))
|
return page
|
||||||
}
|
}
|
||||||
|
|
||||||
func NumTabs() int {
|
func NumTabs() int {
|
||||||
return len(tabs)
|
return len(tabs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resetSearch() {
|
||||||
|
tabs[curTab].view.SetBytes(originalText)
|
||||||
|
tabs[curTab].mode = tabModeDone
|
||||||
|
searchMode = false
|
||||||
|
bottomBar.SetLabel("")
|
||||||
|
bottomBar.SetText(bottomBarText)
|
||||||
|
}
|
||||||
|
@ -33,8 +33,8 @@ var dlChoiceCh = make(chan string)
|
|||||||
var dlModal = cview.NewModal()
|
var dlModal = cview.NewModal()
|
||||||
|
|
||||||
func dlInit() {
|
func dlInit() {
|
||||||
panels.AddPanel("dl", dlModal, false, false)
|
panels.AddPanel(PanelDownload, dlModal, false, false)
|
||||||
panels.AddPanel("dlChoice", dlChoiceModal, false, false)
|
panels.AddPanel(PanelDownloadChoiceModal, dlChoiceModal, false, false)
|
||||||
|
|
||||||
dlm := dlModal
|
dlm := dlModal
|
||||||
chm := dlChoiceModal
|
chm := dlChoiceModal
|
||||||
@ -45,7 +45,7 @@ func dlInit() {
|
|||||||
chm.SetTextColor(config.GetColor("dl_choice_modal_text"))
|
chm.SetTextColor(config.GetColor("dl_choice_modal_text"))
|
||||||
form := chm.GetForm()
|
form := chm.GetForm()
|
||||||
form.SetButtonBackgroundColorFocused(config.GetColor("btn_text"))
|
form.SetButtonBackgroundColorFocused(config.GetColor("btn_text"))
|
||||||
form.SetButtonTextColorFocused(config.GetColor("btn_bg"))
|
form.SetButtonTextColorFocused(config.GetTextColor("btn_bg", "btn_text"))
|
||||||
frame := chm.GetFrame()
|
frame := chm.GetFrame()
|
||||||
frame.SetBorderColor(config.GetColor("dl_choice_modal_text"))
|
frame.SetBorderColor(config.GetColor("dl_choice_modal_text"))
|
||||||
frame.SetTitleColor(config.GetColor("dl_choice_modal_text"))
|
frame.SetTitleColor(config.GetColor("dl_choice_modal_text"))
|
||||||
@ -56,7 +56,7 @@ func dlInit() {
|
|||||||
dlm.SetTextColor(config.GetColor("dl_modal_text"))
|
dlm.SetTextColor(config.GetColor("dl_modal_text"))
|
||||||
form = dlm.GetForm()
|
form = dlm.GetForm()
|
||||||
form.SetButtonBackgroundColorFocused(config.GetColor("btn_text"))
|
form.SetButtonBackgroundColorFocused(config.GetColor("btn_text"))
|
||||||
form.SetButtonTextColorFocused(config.GetColor("btn_bg"))
|
form.SetButtonTextColorFocused(config.GetTextColor("btn_bg", "btn_text"))
|
||||||
frame = dlm.GetFrame()
|
frame = dlm.GetFrame()
|
||||||
frame.SetBorderColor(config.GetColor("dl_modal_text"))
|
frame.SetBorderColor(config.GetColor("dl_modal_text"))
|
||||||
frame.SetTitleColor(config.GetColor("dl_modal_text"))
|
frame.SetTitleColor(config.GetColor("dl_modal_text"))
|
||||||
@ -96,7 +96,7 @@ func dlInit() {
|
|||||||
frame.SetTitle(" Download ")
|
frame.SetTitle(" Download ")
|
||||||
dlm.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
|
dlm.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
|
||||||
if buttonLabel == "Ok" {
|
if buttonLabel == "Ok" {
|
||||||
panels.HidePanel("dl")
|
panels.HidePanel(PanelDownload)
|
||||||
App.SetFocus(tabs[curTab].view)
|
App.SetFocus(tabs[curTab].view)
|
||||||
App.Draw()
|
App.Draw()
|
||||||
}
|
}
|
||||||
@ -141,29 +141,29 @@ func dlChoice(text, u string, resp *gemini.Response) {
|
|||||||
choice = "Open"
|
choice = "Open"
|
||||||
} else {
|
} else {
|
||||||
dlChoiceModal.SetText(text)
|
dlChoiceModal.SetText(text)
|
||||||
panels.ShowPanel("dlChoice")
|
panels.ShowPanel(PanelDownloadChoiceModal)
|
||||||
panels.SendToFront("dlChoice")
|
panels.SendToFront(PanelDownloadChoiceModal)
|
||||||
App.SetFocus(dlChoiceModal)
|
App.SetFocus(dlChoiceModal)
|
||||||
App.Draw()
|
App.Draw()
|
||||||
choice = <-dlChoiceCh
|
choice = <-dlChoiceCh
|
||||||
}
|
}
|
||||||
|
|
||||||
if choice == "Download" {
|
if choice == "Download" {
|
||||||
panels.HidePanel("dlChoice")
|
panels.HidePanel(PanelDownloadChoiceModal)
|
||||||
App.Draw()
|
App.Draw()
|
||||||
downloadURL(config.DownloadsDir, u, resp)
|
downloadURL(config.DownloadsDir, u, resp)
|
||||||
resp.Body.Close() // Only close when the file is downloaded
|
resp.Body.Close() // Only close when the file is downloaded
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if choice == "Open" {
|
if choice == "Open" {
|
||||||
panels.HidePanel("dlChoice")
|
panels.HidePanel(PanelDownloadChoiceModal)
|
||||||
App.Draw()
|
App.Draw()
|
||||||
open(u, resp)
|
open(u, resp)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// They chose the "Cancel" button
|
// They chose the "Cancel" button
|
||||||
panels.HidePanel("dlChoice")
|
panels.HidePanel(PanelDownloadChoiceModal)
|
||||||
App.SetFocus(tabs[curTab].view)
|
App.SetFocus(tabs[curTab].view)
|
||||||
App.Draw()
|
App.Draw()
|
||||||
}
|
}
|
||||||
@ -191,6 +191,8 @@ func open(u string, resp *gemini.Response) {
|
|||||||
Error("File Opening Error", "Error executing custom command: "+err.Error())
|
Error("File Opening Error", "Error executing custom command: "+err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
//nolint:errcheck
|
||||||
|
go proc.Wait() // Prevent zombies, see #219
|
||||||
Info("Opened with " + cmd[0])
|
Info("Opened with " + cmd[0])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -200,7 +202,7 @@ func open(u string, resp *gemini.Response) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
panels.HidePanel("dl")
|
panels.HidePanel(PanelDownload)
|
||||||
App.SetFocus(tabs[curTab].view)
|
App.SetFocus(tabs[curTab].view)
|
||||||
App.Draw()
|
App.Draw()
|
||||||
|
|
||||||
@ -214,11 +216,14 @@ func open(u string, resp *gemini.Response) {
|
|||||||
Info("Opened in default system viewer")
|
Info("Opened in default system viewer")
|
||||||
} else {
|
} else {
|
||||||
cmd := mediaHandler.Cmd
|
cmd := mediaHandler.Cmd
|
||||||
err := exec.Command(cmd[0], append(cmd[1:], path)...).Start()
|
proc := exec.Command(cmd[0], append(cmd[1:], path)...)
|
||||||
|
err := proc.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Error("File Opening Error", "Error executing custom command: "+err.Error())
|
Error("File Opening Error", "Error executing custom command: "+err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
//nolint:errcheck
|
||||||
|
go proc.Wait() // Prevent zombies, see #219
|
||||||
Info("Opened with " + cmd[0])
|
Info("Opened with " + cmd[0])
|
||||||
}
|
}
|
||||||
App.Draw()
|
App.Draw()
|
||||||
@ -267,15 +272,15 @@ func downloadURL(dir, u string, resp *gemini.Response) string {
|
|||||||
// Display
|
// Display
|
||||||
dlModal.ClearButtons()
|
dlModal.ClearButtons()
|
||||||
dlModal.AddButtons([]string{"Downloading..."})
|
dlModal.AddButtons([]string{"Downloading..."})
|
||||||
panels.ShowPanel("dl")
|
panels.ShowPanel(PanelDownload)
|
||||||
panels.SendToFront("dl")
|
panels.SendToFront(PanelDownload)
|
||||||
App.SetFocus(dlModal)
|
App.SetFocus(dlModal)
|
||||||
App.Draw()
|
App.Draw()
|
||||||
|
|
||||||
_, err = io.Copy(io.MultiWriter(f, bar), resp.Body)
|
_, err = io.Copy(io.MultiWriter(f, bar), resp.Body)
|
||||||
done = true
|
done = true
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panels.HidePanel("dl")
|
panels.HidePanel(PanelDownload)
|
||||||
Error("Download Error", err.Error())
|
Error("Download Error", err.Error())
|
||||||
f.Close()
|
f.Close()
|
||||||
os.Remove(savePath) // Remove partial file
|
os.Remove(savePath) // Remove partial file
|
||||||
|
@ -35,6 +35,12 @@ func handleFile(u string) (*structs.Page, bool) {
|
|||||||
if u[len(u)-1] != '/' {
|
if u[len(u)-1] != '/' {
|
||||||
u += "/"
|
u += "/"
|
||||||
}
|
}
|
||||||
|
for _, index := range []string{"index.gmi", "index.gemini"} {
|
||||||
|
m, err := os.Stat(uri.Path + "/" + index)
|
||||||
|
if err == nil && !m.IsDir() {
|
||||||
|
return handleFile(u + index)
|
||||||
|
}
|
||||||
|
}
|
||||||
return createDirectoryListing(u)
|
return createDirectoryListing(u)
|
||||||
case mode.IsRegular():
|
case mode.IsRegular():
|
||||||
if fi.Size() > viper.GetInt64("a-general.page_max_size") {
|
if fi.Size() > viper.GetInt64("a-general.page_max_size") {
|
||||||
|
@ -2,6 +2,7 @@ package display
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"mime"
|
"mime"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -13,11 +14,12 @@ import (
|
|||||||
"github.com/makeworld-the-better-one/amfora/client"
|
"github.com/makeworld-the-better-one/amfora/client"
|
||||||
"github.com/makeworld-the-better-one/amfora/config"
|
"github.com/makeworld-the-better-one/amfora/config"
|
||||||
"github.com/makeworld-the-better-one/amfora/renderer"
|
"github.com/makeworld-the-better-one/amfora/renderer"
|
||||||
"github.com/makeworld-the-better-one/amfora/rr"
|
|
||||||
"github.com/makeworld-the-better-one/amfora/structs"
|
"github.com/makeworld-the-better-one/amfora/structs"
|
||||||
"github.com/makeworld-the-better-one/amfora/subscriptions"
|
"github.com/makeworld-the-better-one/amfora/subscriptions"
|
||||||
|
"github.com/makeworld-the-better-one/amfora/sysopen"
|
||||||
"github.com/makeworld-the-better-one/amfora/webbrowser"
|
"github.com/makeworld-the-better-one/amfora/webbrowser"
|
||||||
"github.com/makeworld-the-better-one/go-gemini"
|
"github.com/makeworld-the-better-one/go-gemini"
|
||||||
|
"github.com/makeworld-the-better-one/rr"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -46,16 +48,20 @@ func handleHTTP(u string, showInfo bool) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Custom command
|
// Custom command
|
||||||
var err error = nil
|
var proc *exec.Cmd
|
||||||
if len(config.HTTPCommand) > 1 {
|
if len(config.HTTPCommand) > 1 {
|
||||||
err = exec.Command(config.HTTPCommand[0], append(config.HTTPCommand[1:], u)...).Start()
|
proc = exec.Command(config.HTTPCommand[0], append(config.HTTPCommand[1:], u)...)
|
||||||
} else {
|
} else {
|
||||||
err = exec.Command(config.HTTPCommand[0], u).Start()
|
proc = exec.Command(config.HTTPCommand[0], u)
|
||||||
}
|
}
|
||||||
|
err := proc.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Error("HTTP Error", "Error executing custom browser command: "+err.Error())
|
Error("HTTP Error", "Error executing custom browser command: "+err.Error())
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
//nolint:errcheck
|
||||||
|
go proc.Wait() // Prevent zombies, see #219
|
||||||
|
Info("Opened with: " + config.HTTPCommand[0])
|
||||||
|
|
||||||
App.Draw()
|
App.Draw()
|
||||||
return true
|
return true
|
||||||
@ -68,21 +74,52 @@ func handleOther(u string) {
|
|||||||
parsed, _ := url.Parse(u)
|
parsed, _ := url.Parse(u)
|
||||||
|
|
||||||
// Search for a handler for the URL scheme
|
// Search for a handler for the URL scheme
|
||||||
handler := strings.TrimSpace(viper.GetString("url-handlers." + parsed.Scheme))
|
handler := viper.GetStringSlice("url-handlers." + parsed.Scheme)
|
||||||
if len(handler) == 0 {
|
if len(handler) == 0 {
|
||||||
handler = strings.TrimSpace(viper.GetString("url-handlers.other"))
|
// A string and not a list of strings, use old method of parsing
|
||||||
}
|
// #214
|
||||||
switch handler {
|
handler = strings.Fields(viper.GetString("url-handlers." + parsed.Scheme))
|
||||||
case "", "off":
|
if len(handler) == 0 {
|
||||||
Error("URL Error", "Opening "+parsed.Scheme+" URLs is turned off.")
|
handler = viper.GetStringSlice("url-handlers.other")
|
||||||
default:
|
if len(handler) == 0 {
|
||||||
// The config has a custom command to execute for URLs
|
handler = strings.Fields(viper.GetString("url-handlers.other"))
|
||||||
fields := strings.Fields(handler)
|
}
|
||||||
err := exec.Command(fields[0], append(fields[1:], u)...).Start()
|
|
||||||
if err != nil {
|
|
||||||
Error("URL Error", "Error executing custom command: "+err.Error())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(handler) == 1 {
|
||||||
|
// Maybe special key
|
||||||
|
|
||||||
|
switch strings.TrimSpace(handler[0]) {
|
||||||
|
case "", "off":
|
||||||
|
Error("URL Error", "Opening "+parsed.Scheme+" URLs is turned off.")
|
||||||
|
return
|
||||||
|
case "default":
|
||||||
|
_, err := sysopen.Open(u)
|
||||||
|
if err != nil {
|
||||||
|
Error("Application Error", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Info("Opened in default application")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom application command
|
||||||
|
|
||||||
|
var proc *exec.Cmd
|
||||||
|
if len(handler) > 1 {
|
||||||
|
proc = exec.Command(handler[0], append(handler[1:], u)...)
|
||||||
|
} else {
|
||||||
|
proc = exec.Command(handler[0], u)
|
||||||
|
}
|
||||||
|
err := proc.Start()
|
||||||
|
if err != nil {
|
||||||
|
Error("URL Error", "Error executing custom command: "+err.Error())
|
||||||
|
}
|
||||||
|
//nolint:errcheck
|
||||||
|
go proc.Wait() // Prevent zombies, see #219
|
||||||
|
Info("Opened with: " + handler[0])
|
||||||
App.Draw()
|
App.Draw()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,6 +216,8 @@ func handleURL(t *tab, u string, numRedirects int) (string, bool) {
|
|||||||
}
|
}
|
||||||
t.mode = tabModeDone
|
t.mode = tabModeDone
|
||||||
|
|
||||||
|
t.preferURLHandler = false
|
||||||
|
|
||||||
go func(p *structs.Page) {
|
go func(p *structs.Page) {
|
||||||
if b && t.hasContent() && !t.isAnAboutPage() && viper.GetBool("subscriptions.popup") {
|
if b && t.hasContent() && !t.isAnAboutPage() && viper.GetBool("subscriptions.popup") {
|
||||||
// The current page might be an untracked feed, and the user wants
|
// The current page might be an untracked feed, and the user wants
|
||||||
@ -204,7 +243,7 @@ func handleURL(t *tab, u string, numRedirects int) (string, bool) {
|
|||||||
return ret(handleAbout(t, u))
|
return ret(handleAbout(t, u))
|
||||||
}
|
}
|
||||||
|
|
||||||
u = normalizeURL(u)
|
u = client.NormalizeURL(u)
|
||||||
u = cache.Redirect(u)
|
u = cache.Redirect(u)
|
||||||
|
|
||||||
parsed, err := url.Parse(u)
|
parsed, err := url.Parse(u)
|
||||||
@ -213,6 +252,15 @@ func handleURL(t *tab, u string, numRedirects int) (string, bool) {
|
|||||||
return ret("", false)
|
return ret("", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check if a prompt is needed to handle this url
|
||||||
|
prompt := viper.GetBool("url-prompts.other")
|
||||||
|
if viper.IsSet("url-prompts." + parsed.Scheme) {
|
||||||
|
prompt = viper.GetBool("url-prompts." + parsed.Scheme)
|
||||||
|
}
|
||||||
|
if prompt && !(YesNo("Follow URL?\n" + u)) {
|
||||||
|
return ret("", false)
|
||||||
|
}
|
||||||
|
|
||||||
proxy := strings.TrimSpace(viper.GetString("proxies." + parsed.Scheme))
|
proxy := strings.TrimSpace(viper.GetString("proxies." + parsed.Scheme))
|
||||||
usingProxy := false
|
usingProxy := false
|
||||||
|
|
||||||
@ -224,7 +272,7 @@ func handleURL(t *tab, u string, numRedirects int) (string, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(u, "http") {
|
if strings.HasPrefix(u, "http") {
|
||||||
if proxy == "" || proxy == "off" {
|
if proxy == "" || proxy == "off" || t.preferURLHandler {
|
||||||
// No proxy available
|
// No proxy available
|
||||||
handleHTTP(u, true)
|
handleHTTP(u, true)
|
||||||
return ret("", false)
|
return ret("", false)
|
||||||
@ -243,7 +291,7 @@ func handleURL(t *tab, u string, numRedirects int) (string, bool) {
|
|||||||
|
|
||||||
if !strings.HasPrefix(u, "http") && !strings.HasPrefix(u, "gemini") && !strings.HasPrefix(u, "file") {
|
if !strings.HasPrefix(u, "http") && !strings.HasPrefix(u, "gemini") && !strings.HasPrefix(u, "file") {
|
||||||
// Not a Gemini URL
|
// Not a Gemini URL
|
||||||
if proxy == "" || proxy == "off" {
|
if proxy == "" || proxy == "off" || t.preferURLHandler {
|
||||||
// No proxy available
|
// No proxy available
|
||||||
handleOther(u)
|
handleOther(u)
|
||||||
return ret("", false)
|
return ret("", false)
|
||||||
@ -321,7 +369,7 @@ func handleURL(t *tab, u string, numRedirects int) (string, bool) {
|
|||||||
// Disable read timeout and go back to start
|
// Disable read timeout and go back to start
|
||||||
res.SetReadTimeout(0) //nolint: errcheck
|
res.SetReadTimeout(0) //nolint: errcheck
|
||||||
res.Body.(*rr.RestartReader).Restart()
|
res.Body.(*rr.RestartReader).Restart()
|
||||||
go dlChoice("That page is too large. What would you like to do?", u, res)
|
dlChoice("That page is too large. What would you like to do?", u, res)
|
||||||
return ret("", false)
|
return ret("", false)
|
||||||
}
|
}
|
||||||
if errors.Is(err, renderer.ErrTimedOut) {
|
if errors.Is(err, renderer.ErrTimedOut) {
|
||||||
@ -329,7 +377,7 @@ func handleURL(t *tab, u string, numRedirects int) (string, bool) {
|
|||||||
// Disable read timeout and go back to start
|
// Disable read timeout and go back to start
|
||||||
res.SetReadTimeout(0) //nolint: errcheck
|
res.SetReadTimeout(0) //nolint: errcheck
|
||||||
res.Body.(*rr.RestartReader).Restart()
|
res.Body.(*rr.RestartReader).Restart()
|
||||||
go dlChoice("Loading that page timed out. What would you like to do?", u, res)
|
dlChoice("Loading that page timed out. What would you like to do?", u, res)
|
||||||
return ret("", false)
|
return ret("", false)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -339,7 +387,7 @@ func handleURL(t *tab, u string, numRedirects int) (string, bool) {
|
|||||||
|
|
||||||
page.TermWidth = termW
|
page.TermWidth = termW
|
||||||
|
|
||||||
if !client.HasClientCert(parsed.Host) {
|
if !client.HasClientCert(parsed.Host, parsed.Path) {
|
||||||
// Don't cache pages with client certs
|
// Don't cache pages with client certs
|
||||||
go cache.AddPage(page)
|
go cache.AddPage(page)
|
||||||
}
|
}
|
||||||
@ -351,12 +399,14 @@ func handleURL(t *tab, u string, numRedirects int) (string, bool) {
|
|||||||
// Could be a non 20 status code, or a different kind of document
|
// Could be a non 20 status code, or a different kind of document
|
||||||
|
|
||||||
// Handle each status code
|
// Handle each status code
|
||||||
switch res.Status {
|
// Except 20, that's handled after the switch
|
||||||
|
status := gemini.CleanStatus(res.Status)
|
||||||
|
switch status {
|
||||||
case 10, 11:
|
case 10, 11:
|
||||||
var userInput string
|
var userInput string
|
||||||
var ok bool
|
var ok bool
|
||||||
|
|
||||||
if res.Status == 10 {
|
if status == 10 {
|
||||||
// Regular input
|
// Regular input
|
||||||
userInput, ok = Input(res.Meta, false)
|
userInput, ok = Input(res.Meta, false)
|
||||||
} else {
|
} else {
|
||||||
@ -380,9 +430,10 @@ func handleURL(t *tab, u string, numRedirects int) (string, bool) {
|
|||||||
return ret("", false)
|
return ret("", false)
|
||||||
}
|
}
|
||||||
redir := parsed.ResolveReference(parsedMeta).String()
|
redir := parsed.ResolveReference(parsedMeta).String()
|
||||||
|
justAddsSlash := (redir == u+"/")
|
||||||
// Prompt before redirecting to non-Gemini protocol
|
// Prompt before redirecting to non-Gemini protocol
|
||||||
redirect := false
|
redirect := false
|
||||||
if !strings.HasPrefix(redir, "gemini") {
|
if !justAddsSlash && !strings.HasPrefix(redir, "gemini") {
|
||||||
if YesNo("Follow redirect to non-Gemini URL?\n" + redir) {
|
if YesNo("Follow redirect to non-Gemini URL?\n" + redir) {
|
||||||
redirect = true
|
redirect = true
|
||||||
} else {
|
} else {
|
||||||
@ -390,9 +441,9 @@ func handleURL(t *tab, u string, numRedirects int) (string, bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Prompt before redirecting
|
// Prompt before redirecting
|
||||||
autoRedirect := viper.GetBool("a-general.auto_redirect")
|
autoRedirect := justAddsSlash || viper.GetBool("a-general.auto_redirect")
|
||||||
if redirect || (autoRedirect && numRedirects < 5) || YesNo("Follow redirect?\n"+redir) {
|
if redirect || (autoRedirect && numRedirects < 5) || YesNo("Follow redirect?\n"+redir) {
|
||||||
if res.Status == gemini.StatusRedirectPermanent {
|
if status == gemini.StatusRedirectPermanent {
|
||||||
go cache.AddRedir(u, redir)
|
go cache.AddRedir(u, redir)
|
||||||
}
|
}
|
||||||
return ret(handleURL(t, redir, numRedirects+1))
|
return ret(handleURL(t, redir, numRedirects+1))
|
||||||
@ -437,6 +488,12 @@ func handleURL(t *tab, u string, numRedirects int) (string, bool) {
|
|||||||
case 62:
|
case 62:
|
||||||
Error("Certificate Not Valid", escapeMeta(res.Meta))
|
Error("Certificate Not Valid", escapeMeta(res.Meta))
|
||||||
return ret("", false)
|
return ret("", false)
|
||||||
|
default:
|
||||||
|
if !gemini.StatusInRange(status) {
|
||||||
|
// Status code not in a valid range
|
||||||
|
Error("Status Code Error", fmt.Sprintf("Out of range status code: %d", status))
|
||||||
|
return ret("", false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Status code 20, but not a document that can be displayed
|
// Status code 20, but not a document that can be displayed
|
||||||
@ -453,7 +510,7 @@ func handleURL(t *tab, u string, numRedirects int) (string, bool) {
|
|||||||
// Disable read timeout and go back to start
|
// Disable read timeout and go back to start
|
||||||
res.SetReadTimeout(0) //nolint: errcheck
|
res.SetReadTimeout(0) //nolint: errcheck
|
||||||
res.Body.(*rr.RestartReader).Restart()
|
res.Body.(*rr.RestartReader).Restart()
|
||||||
go dlChoice("That file could not be displayed. What would you like to do?", u, res)
|
dlChoice("That file could not be displayed. What would you like to do?", u, res)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
return ret("", false)
|
return ret("", false)
|
||||||
@ -463,6 +520,6 @@ func handleURL(t *tab, u string, numRedirects int) (string, bool) {
|
|||||||
// Disable read timeout and go back to start
|
// Disable read timeout and go back to start
|
||||||
res.SetReadTimeout(0) //nolint: errcheck
|
res.SetReadTimeout(0) //nolint: errcheck
|
||||||
res.Body.(*rr.RestartReader).Restart()
|
res.Body.(*rr.RestartReader).Restart()
|
||||||
go dlChoice("That file could not be displayed. What would you like to do?", u, res)
|
dlChoice("That file could not be displayed. What would you like to do?", u, res)
|
||||||
return ret("", false)
|
return ret("", false)
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ var helpCells = strings.TrimSpace(
|
|||||||
"Enter, Tab\tOn a page this will start link highlighting.\n" +
|
"Enter, Tab\tOn a page this will start link highlighting.\n" +
|
||||||
"\tPress Tab and Shift-Tab to pick different links.\n" +
|
"\tPress Tab and Shift-Tab to pick different links.\n" +
|
||||||
"\tPress Enter again to go to one, or Esc to stop.\n" +
|
"\tPress Enter again to go to one, or Esc to stop.\n" +
|
||||||
|
"%s\tOpen the highlighted URL with a URL handler instead of the configured proxy\n" +
|
||||||
"%s\tGo to a specific tab. (Default: Shift-NUMBER)\n" +
|
"%s\tGo to a specific tab. (Default: Shift-NUMBER)\n" +
|
||||||
"%s\tGo to the last tab.\n" +
|
"%s\tGo to the last tab.\n" +
|
||||||
"%s\tPrevious tab\n" +
|
"%s\tPrevious tab\n" +
|
||||||
@ -48,6 +49,9 @@ var helpCells = strings.TrimSpace(
|
|||||||
"%s\tSave the current page to your downloads.\n" +
|
"%s\tSave the current page to your downloads.\n" +
|
||||||
"%s\tView subscriptions\n" +
|
"%s\tView subscriptions\n" +
|
||||||
"%s\tAdd or update a subscription\n" +
|
"%s\tAdd or update a subscription\n" +
|
||||||
|
"%s\tSearch the page content for a string\n" +
|
||||||
|
"%s\tFind next search match\n" +
|
||||||
|
"%s\tFind previous search match\n" +
|
||||||
"%s\tQuit\n")
|
"%s\tQuit\n")
|
||||||
|
|
||||||
var helpTable = cview.NewTextView()
|
var helpTable = cview.NewTextView()
|
||||||
@ -55,8 +59,8 @@ var helpTable = cview.NewTextView()
|
|||||||
// Help displays the help and keybindings.
|
// Help displays the help and keybindings.
|
||||||
func Help() {
|
func Help() {
|
||||||
helpTable.ScrollToBeginning()
|
helpTable.ScrollToBeginning()
|
||||||
panels.ShowPanel("help")
|
panels.ShowPanel(PanelHelp)
|
||||||
panels.SendToFront("help")
|
panels.SendToFront(PanelHelp)
|
||||||
App.SetFocus(helpTable)
|
App.SetFocus(helpTable)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,7 +71,7 @@ func helpInit() {
|
|||||||
helpTable.SetPadding(0, 0, 1, 1)
|
helpTable.SetPadding(0, 0, 1, 1)
|
||||||
helpTable.SetDoneFunc(func(key tcell.Key) {
|
helpTable.SetDoneFunc(func(key tcell.Key) {
|
||||||
if key == tcell.KeyEsc || key == tcell.KeyEnter {
|
if key == tcell.KeyEsc || key == tcell.KeyEnter {
|
||||||
panels.HidePanel("help")
|
panels.HidePanel(PanelHelp)
|
||||||
App.SetFocus(tabs[curTab].view)
|
App.SetFocus(tabs[curTab].view)
|
||||||
App.Draw()
|
App.Draw()
|
||||||
}
|
}
|
||||||
@ -95,6 +99,7 @@ func helpInit() {
|
|||||||
config.GetKeyBinding(config.CmdEdit),
|
config.GetKeyBinding(config.CmdEdit),
|
||||||
config.GetKeyBinding(config.CmdCopyPageURL),
|
config.GetKeyBinding(config.CmdCopyPageURL),
|
||||||
config.GetKeyBinding(config.CmdCopyTargetURL),
|
config.GetKeyBinding(config.CmdCopyTargetURL),
|
||||||
|
config.GetKeyBinding(config.CmdURLHandlerOpen),
|
||||||
tabKeys,
|
tabKeys,
|
||||||
config.GetKeyBinding(config.CmdTab0),
|
config.GetKeyBinding(config.CmdTab0),
|
||||||
config.GetKeyBinding(config.CmdPrevTab),
|
config.GetKeyBinding(config.CmdPrevTab),
|
||||||
@ -108,6 +113,9 @@ func helpInit() {
|
|||||||
config.GetKeyBinding(config.CmdSave),
|
config.GetKeyBinding(config.CmdSave),
|
||||||
config.GetKeyBinding(config.CmdSub),
|
config.GetKeyBinding(config.CmdSub),
|
||||||
config.GetKeyBinding(config.CmdAddSub),
|
config.GetKeyBinding(config.CmdAddSub),
|
||||||
|
config.GetKeyBinding(config.CmdSearch),
|
||||||
|
config.GetKeyBinding(config.CmdNextMatch),
|
||||||
|
config.GetKeyBinding(config.CmdPrevMatch),
|
||||||
config.GetKeyBinding(config.CmdQuit),
|
config.GetKeyBinding(config.CmdQuit),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -122,5 +130,5 @@ func helpInit() {
|
|||||||
|
|
||||||
w.Flush()
|
w.Flush()
|
||||||
|
|
||||||
panels.AddPanel("help", helpTable, true, false)
|
panels.AddPanel(PanelHelp, helpTable, true, false)
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,18 @@ package display
|
|||||||
// applyHist is a history.go internal function, to load a URL in the history.
|
// applyHist is a history.go internal function, to load a URL in the history.
|
||||||
func applyHist(t *tab) {
|
func applyHist(t *tab) {
|
||||||
handleURL(t, t.history.urls[t.history.pos], 0) // Load that position in history
|
handleURL(t, t.history.urls[t.history.pos], 0) // Load that position in history
|
||||||
|
|
||||||
|
// Set page's scroll and link info from history cache, in case it didn't have it in the page already
|
||||||
|
// Like for non-cached pages like about: pages
|
||||||
|
// This fixes #122
|
||||||
|
pg := t.history.pageCache[t.history.pos]
|
||||||
|
p := t.page
|
||||||
|
p.Row = pg.row
|
||||||
|
p.Column = pg.column
|
||||||
|
p.Selected = pg.selected
|
||||||
|
p.SelectedID = pg.selectedID
|
||||||
|
p.Mode = pg.mode
|
||||||
|
|
||||||
t.applyAll()
|
t.applyAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -11,6 +23,10 @@ func histForward(t *tab) {
|
|||||||
// Already on the most recent URL in the history
|
// Already on the most recent URL in the history
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update page cache in history for #122
|
||||||
|
t.historyCachePage()
|
||||||
|
|
||||||
t.history.pos++
|
t.history.pos++
|
||||||
go applyHist(t)
|
go applyHist(t)
|
||||||
}
|
}
|
||||||
@ -20,6 +36,10 @@ func histBack(t *tab) {
|
|||||||
// First tab in history
|
// First tab in history
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update page cache in history for #122
|
||||||
|
t.historyCachePage()
|
||||||
|
|
||||||
t.history.pos--
|
t.history.pos--
|
||||||
go applyHist(t)
|
go applyHist(t)
|
||||||
}
|
}
|
||||||
|
@ -16,29 +16,27 @@ import (
|
|||||||
// The bookmark modal is in bookmarks.go
|
// The bookmark modal is in bookmarks.go
|
||||||
|
|
||||||
var infoModal = cview.NewModal()
|
var infoModal = cview.NewModal()
|
||||||
|
|
||||||
var errorModal = cview.NewModal()
|
var errorModal = cview.NewModal()
|
||||||
|
|
||||||
var inputModal = cview.NewModal()
|
var inputModal = cview.NewModal()
|
||||||
var inputCh = make(chan string)
|
|
||||||
var inputModalText string // The current text of the input field in the modal
|
|
||||||
|
|
||||||
var yesNoModal = cview.NewModal()
|
var yesNoModal = cview.NewModal()
|
||||||
|
|
||||||
// Channel to receive yesNo answer on
|
var inputCh = make(chan string)
|
||||||
var yesNoCh = make(chan bool)
|
var yesNoCh = make(chan bool)
|
||||||
|
|
||||||
|
var inputModalText string // The current text of the input field in the modal
|
||||||
|
|
||||||
|
// Internal channel used to know when a modal has been dismissed
|
||||||
|
var modalDone = make(chan struct{})
|
||||||
|
|
||||||
func modalInit() {
|
func modalInit() {
|
||||||
infoModal.AddButtons([]string{"Ok"})
|
infoModal.AddButtons([]string{"Ok"})
|
||||||
|
|
||||||
errorModal.AddButtons([]string{"Ok"})
|
errorModal.AddButtons([]string{"Ok"})
|
||||||
|
|
||||||
yesNoModal.AddButtons([]string{"Yes", "No"})
|
yesNoModal.AddButtons([]string{"Yes", "No"})
|
||||||
|
|
||||||
panels.AddPanel("info", infoModal, false, false)
|
panels.AddPanel(PanelInfoModal, infoModal, false, false)
|
||||||
panels.AddPanel("error", errorModal, false, false)
|
panels.AddPanel(PanelErrorModal, errorModal, false, false)
|
||||||
panels.AddPanel("input", inputModal, false, false)
|
panels.AddPanel(PanelInputModal, inputModal, false, false)
|
||||||
panels.AddPanel("yesno", yesNoModal, false, false)
|
panels.AddPanel(PanelYesNoModal, yesNoModal, false, false)
|
||||||
|
|
||||||
// Color setup
|
// Color setup
|
||||||
if viper.GetBool("a-general.color") {
|
if viper.GetBool("a-general.color") {
|
||||||
@ -49,7 +47,7 @@ func modalInit() {
|
|||||||
m.SetTextColor(config.GetColor("info_modal_text"))
|
m.SetTextColor(config.GetColor("info_modal_text"))
|
||||||
form := m.GetForm()
|
form := m.GetForm()
|
||||||
form.SetButtonBackgroundColorFocused(config.GetColor("btn_text"))
|
form.SetButtonBackgroundColorFocused(config.GetColor("btn_text"))
|
||||||
form.SetButtonTextColorFocused(config.GetColor("btn_bg"))
|
form.SetButtonTextColorFocused(config.GetTextColor("btn_bg", "btn_text"))
|
||||||
frame := m.GetFrame()
|
frame := m.GetFrame()
|
||||||
frame.SetBorderColor(config.GetColor("info_modal_text"))
|
frame.SetBorderColor(config.GetColor("info_modal_text"))
|
||||||
frame.SetTitleColor(config.GetColor("info_modal_text"))
|
frame.SetTitleColor(config.GetColor("info_modal_text"))
|
||||||
@ -61,7 +59,7 @@ func modalInit() {
|
|||||||
m.SetTextColor(config.GetColor("error_modal_text"))
|
m.SetTextColor(config.GetColor("error_modal_text"))
|
||||||
form = m.GetForm()
|
form = m.GetForm()
|
||||||
form.SetButtonBackgroundColorFocused(config.GetColor("btn_text"))
|
form.SetButtonBackgroundColorFocused(config.GetColor("btn_text"))
|
||||||
form.SetButtonTextColorFocused(config.GetColor("btn_bg"))
|
form.SetButtonTextColorFocused(config.GetTextColor("btn_bg", "btn_text"))
|
||||||
frame = errorModal.GetFrame()
|
frame = errorModal.GetFrame()
|
||||||
frame.SetBorderColor(config.GetColor("error_modal_text"))
|
frame.SetBorderColor(config.GetColor("error_modal_text"))
|
||||||
frame.SetTitleColor(config.GetColor("error_modal_text"))
|
frame.SetTitleColor(config.GetColor("error_modal_text"))
|
||||||
@ -78,14 +76,14 @@ func modalInit() {
|
|||||||
form.SetFieldBackgroundColor(config.GetColor("input_modal_field_bg"))
|
form.SetFieldBackgroundColor(config.GetColor("input_modal_field_bg"))
|
||||||
form.SetFieldTextColor(config.GetColor("input_modal_field_text"))
|
form.SetFieldTextColor(config.GetColor("input_modal_field_text"))
|
||||||
form.SetButtonBackgroundColorFocused(config.GetColor("btn_text"))
|
form.SetButtonBackgroundColorFocused(config.GetColor("btn_text"))
|
||||||
form.SetButtonTextColorFocused(config.GetColor("btn_bg"))
|
form.SetButtonTextColorFocused(config.GetTextColor("btn_bg", "btn_text"))
|
||||||
|
|
||||||
m = yesNoModal
|
m = yesNoModal
|
||||||
m.SetButtonBackgroundColor(config.GetColor("btn_bg"))
|
m.SetButtonBackgroundColor(config.GetColor("btn_bg"))
|
||||||
m.SetButtonTextColor(config.GetColor("btn_text"))
|
m.SetButtonTextColor(config.GetColor("btn_text"))
|
||||||
form = m.GetForm()
|
form = m.GetForm()
|
||||||
form.SetButtonBackgroundColorFocused(config.GetColor("btn_text"))
|
form.SetButtonBackgroundColorFocused(config.GetColor("btn_text"))
|
||||||
form.SetButtonTextColorFocused(config.GetColor("btn_bg"))
|
form.SetButtonTextColorFocused(config.GetTextColor("btn_bg", "btn_text"))
|
||||||
} else {
|
} else {
|
||||||
m := infoModal
|
m := infoModal
|
||||||
m.SetBackgroundColor(tcell.ColorBlack)
|
m.SetBackgroundColor(tcell.ColorBlack)
|
||||||
@ -141,17 +139,19 @@ func modalInit() {
|
|||||||
frame.SetTitleAlign(cview.AlignCenter)
|
frame.SetTitleAlign(cview.AlignCenter)
|
||||||
frame.SetTitle(" Info ")
|
frame.SetTitle(" Info ")
|
||||||
infoModal.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
|
infoModal.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
|
||||||
panels.HidePanel("info")
|
panels.HidePanel(PanelInfoModal)
|
||||||
App.SetFocus(tabs[curTab].view)
|
App.SetFocus(tabs[curTab].view)
|
||||||
App.Draw()
|
App.Draw()
|
||||||
|
modalDone <- struct{}{}
|
||||||
})
|
})
|
||||||
|
|
||||||
errorModal.SetBorder(true)
|
errorModal.SetBorder(true)
|
||||||
errorModal.GetFrame().SetTitleAlign(cview.AlignCenter)
|
errorModal.GetFrame().SetTitleAlign(cview.AlignCenter)
|
||||||
errorModal.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
|
errorModal.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
|
||||||
panels.HidePanel("error")
|
panels.HidePanel(PanelErrorModal)
|
||||||
App.SetFocus(tabs[curTab].view)
|
App.SetFocus(tabs[curTab].view)
|
||||||
App.Draw()
|
App.Draw()
|
||||||
|
modalDone <- struct{}{}
|
||||||
})
|
})
|
||||||
|
|
||||||
inputModal.SetBorder(true)
|
inputModal.SetBorder(true)
|
||||||
@ -181,7 +181,7 @@ func modalInit() {
|
|||||||
dlInit()
|
dlInit()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error displays an error on the screen in a modal.
|
// Error displays an error on the screen in a modal, and blocks until dismissed by the user.
|
||||||
func Error(title, text string) {
|
func Error(title, text string) {
|
||||||
if text == "" {
|
if text == "" {
|
||||||
text = "No additional information."
|
text = "No additional information."
|
||||||
@ -196,22 +196,26 @@ func Error(title, text string) {
|
|||||||
|
|
||||||
errorModal.GetFrame().SetTitle(title)
|
errorModal.GetFrame().SetTitle(title)
|
||||||
errorModal.SetText(text)
|
errorModal.SetText(text)
|
||||||
panels.ShowPanel("error")
|
panels.ShowPanel(PanelErrorModal)
|
||||||
panels.SendToFront("error")
|
panels.SendToFront(PanelErrorModal)
|
||||||
App.SetFocus(errorModal)
|
App.SetFocus(errorModal)
|
||||||
App.Draw()
|
App.Draw()
|
||||||
|
|
||||||
|
<-modalDone
|
||||||
}
|
}
|
||||||
|
|
||||||
// Info displays some info on the screen in a modal.
|
// Info displays some info on the screen in a modal, and blocks until dismissed by the user.
|
||||||
func Info(s string) {
|
func Info(s string) {
|
||||||
infoModal.SetText(s)
|
infoModal.SetText(s)
|
||||||
panels.ShowPanel("info")
|
panels.ShowPanel(PanelInfoModal)
|
||||||
panels.SendToFront("info")
|
panels.SendToFront(PanelInfoModal)
|
||||||
App.SetFocus(infoModal)
|
App.SetFocus(infoModal)
|
||||||
App.Draw()
|
App.Draw()
|
||||||
|
|
||||||
|
<-modalDone
|
||||||
}
|
}
|
||||||
|
|
||||||
// Input pulls up a modal that asks for input, and returns the user's input.
|
// Input pulls up a modal that asks for input, waits for that input, and returns it.
|
||||||
// It returns an bool indicating if the user chose to send input or not.
|
// It returns an bool indicating if the user chose to send input or not.
|
||||||
func Input(prompt string, sensitive bool) (string, bool) {
|
func Input(prompt string, sensitive bool) (string, bool) {
|
||||||
// Remove elements and re-add them - to clear input text and keep input in focus
|
// Remove elements and re-add them - to clear input text and keep input in focus
|
||||||
@ -236,14 +240,14 @@ func Input(prompt string, sensitive bool) (string, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inputModal.SetText(prompt + " ")
|
inputModal.SetText(prompt + " ")
|
||||||
panels.ShowPanel("input")
|
panels.ShowPanel(PanelInputModal)
|
||||||
panels.SendToFront("input")
|
panels.SendToFront(PanelInputModal)
|
||||||
App.SetFocus(inputModal)
|
App.SetFocus(inputModal)
|
||||||
App.Draw()
|
App.Draw()
|
||||||
|
|
||||||
resp := <-inputCh
|
resp := <-inputCh
|
||||||
|
|
||||||
panels.HidePanel("input")
|
panels.HidePanel(PanelInputModal)
|
||||||
App.SetFocus(tabs[curTab].view)
|
App.SetFocus(tabs[curTab].view)
|
||||||
App.Draw()
|
App.Draw()
|
||||||
|
|
||||||
@ -253,7 +257,7 @@ func Input(prompt string, sensitive bool) (string, bool) {
|
|||||||
return resp, true
|
return resp, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// YesNo displays a modal asking a yes-or-no question.
|
// YesNo displays a modal asking a yes-or-no question, waits for an answer, then returns it as a bool.
|
||||||
func YesNo(prompt string) bool {
|
func YesNo(prompt string) bool {
|
||||||
if viper.GetBool("a-general.color") {
|
if viper.GetBool("a-general.color") {
|
||||||
m := yesNoModal
|
m := yesNoModal
|
||||||
@ -272,20 +276,20 @@ func YesNo(prompt string) bool {
|
|||||||
}
|
}
|
||||||
yesNoModal.GetFrame().SetTitle("")
|
yesNoModal.GetFrame().SetTitle("")
|
||||||
yesNoModal.SetText(prompt)
|
yesNoModal.SetText(prompt)
|
||||||
panels.ShowPanel("yesno")
|
panels.ShowPanel(PanelYesNoModal)
|
||||||
panels.SendToFront("yesno")
|
panels.SendToFront(PanelYesNoModal)
|
||||||
App.SetFocus(yesNoModal)
|
App.SetFocus(yesNoModal)
|
||||||
App.Draw()
|
App.Draw()
|
||||||
|
|
||||||
resp := <-yesNoCh
|
resp := <-yesNoCh
|
||||||
panels.HidePanel("yesno")
|
panels.HidePanel(PanelYesNoModal)
|
||||||
App.SetFocus(tabs[curTab].view)
|
App.SetFocus(tabs[curTab].view)
|
||||||
App.Draw()
|
App.Draw()
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tofu displays the TOFU warning modal.
|
// Tofu displays the TOFU warning modal.
|
||||||
// It returns a bool indicating whether the user wants to continue.
|
// It blocks then returns a bool indicating whether the user wants to continue.
|
||||||
func Tofu(host string, expiry time.Time) bool {
|
func Tofu(host string, expiry time.Time) bool {
|
||||||
// Reuses yesNoModal, with error color
|
// Reuses yesNoModal, with error color
|
||||||
|
|
||||||
@ -305,18 +309,18 @@ func Tofu(host string, expiry time.Time) bool {
|
|||||||
frame.SetTitle(" TOFU ")
|
frame.SetTitle(" TOFU ")
|
||||||
m.SetText(
|
m.SetText(
|
||||||
//nolint:lll
|
//nolint:lll
|
||||||
fmt.Sprintf("%s's certificate has changed, possibly indicating an security issue. The certificate would have expired %s. Are you sure you want to continue? ",
|
fmt.Sprintf("%s's certificate has changed, possibly indicating a security issue. The certificate would have expired %s. Are you sure you want to continue? ",
|
||||||
host,
|
host,
|
||||||
humanize.Time(expiry),
|
humanize.Time(expiry),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
panels.ShowPanel("yesno")
|
panels.ShowPanel(PanelYesNoModal)
|
||||||
panels.SendToFront("yesno")
|
panels.SendToFront(PanelYesNoModal)
|
||||||
App.SetFocus(yesNoModal)
|
App.SetFocus(yesNoModal)
|
||||||
App.Draw()
|
App.Draw()
|
||||||
|
|
||||||
resp := <-yesNoCh
|
resp := <-yesNoCh
|
||||||
panels.HidePanel("yesno")
|
panels.HidePanel(PanelYesNoModal)
|
||||||
App.SetFocus(tabs[curTab].view)
|
App.SetFocus(tabs[curTab].view)
|
||||||
App.Draw()
|
App.Draw()
|
||||||
return resp
|
return resp
|
||||||
|
@ -29,7 +29,7 @@ Happy browsing!
|
|||||||
=> https://github.com/makeworld-the-better-one/amfora/wiki Amfora Wiki [GitHub]
|
=> https://github.com/makeworld-the-better-one/amfora/wiki Amfora Wiki [GitHub]
|
||||||
=> gemini://makeworld.space/amfora-wiki/ Amfora Wiki [On Gemini!]
|
=> gemini://makeworld.space/amfora-wiki/ Amfora Wiki [On Gemini!]
|
||||||
|
|
||||||
=> //gemini.circumlunar.space Project Gemini
|
=> gemini://geminiprotocol.net Project Gemini
|
||||||
`
|
`
|
||||||
|
|
||||||
// Read the new tab content from a file if it exists or fallback to a default page.
|
// Read the new tab content from a file if it exists or fallback to a default page.
|
||||||
|
14
display/panels.go
Normal file
14
display/panels.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package display
|
||||||
|
|
||||||
|
const (
|
||||||
|
PanelBrowser = "browser"
|
||||||
|
PanelBookmarks = "bkmk"
|
||||||
|
PanelDownload = "dl"
|
||||||
|
PanelDownloadChoiceModal = "dlChoice"
|
||||||
|
PanelHelp = "help"
|
||||||
|
|
||||||
|
PanelYesNoModal = "yesno"
|
||||||
|
PanelInfoModal = "info"
|
||||||
|
PanelErrorModal = "error"
|
||||||
|
PanelInputModal = "input"
|
||||||
|
)
|
@ -1,5 +1,8 @@
|
|||||||
package display
|
package display
|
||||||
|
|
||||||
|
// This file contains the functions that aren't part of the public API.
|
||||||
|
// The funcs are for network and displaying.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -9,17 +12,20 @@ import (
|
|||||||
"github.com/makeworld-the-better-one/amfora/structs"
|
"github.com/makeworld-the-better-one/amfora/structs"
|
||||||
)
|
)
|
||||||
|
|
||||||
// This file contains the functions that aren't part of the public API.
|
// followLink should be used when the user "clicks" a link on a page,
|
||||||
// The funcs are for network and displaying.
|
// but not when a URL is opened on a new tab for the first time.
|
||||||
|
//
|
||||||
// followLink should be used when the user "clicks" a link on a page.
|
// It will handle updating the bottomBar.
|
||||||
// Not when a URL is opened on a new tab for the first time.
|
//
|
||||||
// It will handle setting the bottomBar.
|
// It should be called with the `go` keyword to spawn a new goroutine if
|
||||||
|
// it would otherwise block the UI loop, such as when called from an input
|
||||||
|
// handler.
|
||||||
|
//
|
||||||
|
// It blocks until navigation is finished, and we've completed any user
|
||||||
|
// interaction related to loading the URL (such as info, error modals)
|
||||||
func followLink(t *tab, prev, next string) {
|
func followLink(t *tab, prev, next string) {
|
||||||
if strings.HasPrefix(next, "about:") {
|
if strings.HasPrefix(next, "about:") {
|
||||||
if final, ok := handleAbout(t, next); ok {
|
goURL(t, next)
|
||||||
t.addToHistory(final)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,7 +35,7 @@ func followLink(t *tab, prev, next string) {
|
|||||||
Error("URL Error", err.Error())
|
Error("URL Error", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
go goURL(t, nextURL)
|
goURL(t, nextURL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// No content on current tab, so the "prev" URL is not valid.
|
// No content on current tab, so the "prev" URL is not valid.
|
||||||
@ -39,7 +45,7 @@ func followLink(t *tab, prev, next string) {
|
|||||||
Error("URL Error", "Link URL could not be parsed")
|
Error("URL Error", "Link URL could not be parsed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
go goURL(t, next)
|
goURL(t, next)
|
||||||
}
|
}
|
||||||
|
|
||||||
// reformatPage will take the raw page content and reformat it according to the current terminal dimensions.
|
// reformatPage will take the raw page content and reformat it according to the current terminal dimensions.
|
||||||
@ -112,7 +118,7 @@ func setPage(t *tab, p *structs.Page) {
|
|||||||
tabNum := tabNumber(t)
|
tabNum := tabNumber(t)
|
||||||
browser.AddTab(
|
browser.AddTab(
|
||||||
strconv.Itoa(tabNum),
|
strconv.Itoa(tabNum),
|
||||||
makeTabLabel(strconv.Itoa(tabNum+1)),
|
t.label(),
|
||||||
makeContentLayout(t.view, leftMargin()),
|
makeContentLayout(t.view, leftMargin()),
|
||||||
)
|
)
|
||||||
App.Draw()
|
App.Draw()
|
||||||
@ -131,9 +137,15 @@ func setPage(t *tab, p *structs.Page) {
|
|||||||
//
|
//
|
||||||
// It should be called in a goroutine.
|
// It should be called in a goroutine.
|
||||||
func goURL(t *tab, u string) {
|
func goURL(t *tab, u string) {
|
||||||
|
// Update page cache in history for #122
|
||||||
|
t.historyCachePage()
|
||||||
|
|
||||||
final, displayed := handleURL(t, u, 0)
|
final, displayed := handleURL(t, u, 0)
|
||||||
if displayed {
|
if displayed {
|
||||||
t.addToHistory(final)
|
t.addToHistory(final)
|
||||||
|
} else if t.page.URL == "" {
|
||||||
|
// The tab is showing interstitial or no content. Let's go to about:newtab.
|
||||||
|
handleAbout(t, "about:newtab")
|
||||||
}
|
}
|
||||||
if t == tabs[curTab] {
|
if t == tabs[curTab] {
|
||||||
// Display the bottomBar state that handleURL set
|
// Display the bottomBar state that handleURL set
|
||||||
|
@ -97,9 +97,12 @@ func Subscriptions(t *tab, u string) string {
|
|||||||
} else {
|
} else {
|
||||||
// Render page
|
// Render page
|
||||||
|
|
||||||
rawPage += "You can use Ctrl-X to subscribe to a page, or to an Atom/RSS/JSON feed. See the online wiki for more.\n" +
|
if viper.GetBool("subscriptions.header") {
|
||||||
"If you just opened Amfora then updates may appear incrementally. Reload the page to see them.\n\n" +
|
rawPage += "You can use Ctrl-X to subscribe to a page, or to an Atom/RSS/JSON feed." +
|
||||||
"=> about:manage-subscriptions Manage subscriptions\n\n"
|
"See the online wiki for more.\n" +
|
||||||
|
"If you just opened Amfora then updates may appear incrementally. Reload the page to see them.\n\n"
|
||||||
|
}
|
||||||
|
rawPage += "=> about:manage-subscriptions Manage subscriptions\n\n"
|
||||||
|
|
||||||
// curDay represents what day of posts the loop is on.
|
// curDay represents what day of posts the loop is on.
|
||||||
// It only goes backwards in time.
|
// It only goes backwards in time.
|
||||||
@ -260,13 +263,13 @@ func openSubscriptionModal(validFeed, subscribed bool) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
panels.ShowPanel("yesno")
|
panels.ShowPanel(PanelYesNoModal)
|
||||||
panels.SendToFront("yesno")
|
panels.SendToFront(PanelYesNoModal)
|
||||||
App.SetFocus(yesNoModal)
|
App.SetFocus(yesNoModal)
|
||||||
App.Draw()
|
App.Draw()
|
||||||
|
|
||||||
resp := <-yesNoCh
|
resp := <-yesNoCh
|
||||||
panels.HidePanel("yesno")
|
panels.HidePanel(PanelYesNoModal)
|
||||||
App.SetFocus(tabs[curTab].view)
|
App.SetFocus(tabs[curTab].view)
|
||||||
App.Draw()
|
App.Draw()
|
||||||
return resp
|
return resp
|
||||||
|
164
display/tab.go
164
display/tab.go
@ -3,6 +3,7 @@ package display
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -21,19 +22,31 @@ const (
|
|||||||
tabModeSearch
|
tabModeSearch
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// tabHistoryPageCache is fields from the Page struct, cached here to solve #122
|
||||||
|
// See structs/structs.go for an explanation of the fields.
|
||||||
|
type tabHistoryPageCache struct {
|
||||||
|
row int
|
||||||
|
column int
|
||||||
|
selected string
|
||||||
|
selectedID string
|
||||||
|
mode structs.PageMode
|
||||||
|
}
|
||||||
|
|
||||||
type tabHistory struct {
|
type tabHistory struct {
|
||||||
urls []string
|
urls []string
|
||||||
pos int // Position: where in the list of URLs we are
|
pos int // Position: where in the list of URLs we are
|
||||||
|
pageCache []*tabHistoryPageCache
|
||||||
}
|
}
|
||||||
|
|
||||||
// tab hold the information needed for each browser tab.
|
// tab hold the information needed for each browser tab.
|
||||||
type tab struct {
|
type tab struct {
|
||||||
page *structs.Page
|
page *structs.Page
|
||||||
view *cview.TextView
|
view *cview.TextView
|
||||||
history *tabHistory
|
history *tabHistory
|
||||||
mode tabMode
|
mode tabMode
|
||||||
barLabel string // The bottomBar label for the tab
|
barLabel string // The bottomBar label for the tab
|
||||||
barText string // The bottomBar text for the tab
|
barText string // The bottomBar text for the tab
|
||||||
|
preferURLHandler bool // For #143, use URL handler over proxy
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeNewTab initializes an tab struct with no content.
|
// makeNewTab initializes an tab struct with no content.
|
||||||
@ -87,7 +100,8 @@ func makeNewTab() *tab {
|
|||||||
linkN, _ := strconv.Atoi(currentSelection[0])
|
linkN, _ := strconv.Atoi(currentSelection[0])
|
||||||
tabs[tab].page.Selected = tabs[tab].page.Links[linkN]
|
tabs[tab].page.Selected = tabs[tab].page.Links[linkN]
|
||||||
tabs[tab].page.SelectedID = currentSelection[0]
|
tabs[tab].page.SelectedID = currentSelection[0]
|
||||||
followLink(tabs[tab], tabs[tab].page.URL, tabs[tab].page.Links[linkN])
|
tabs[tab].preferURLHandler = false // Reset in case
|
||||||
|
go followLink(tabs[tab], tabs[tab].page.URL, tabs[tab].page.Links[linkN])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(currentSelection) == 0 && (key == tcell.KeyEnter || key == tcell.KeyTab) {
|
if len(currentSelection) == 0 && (key == tcell.KeyEnter || key == tcell.KeyTab) {
|
||||||
@ -95,7 +109,7 @@ func makeNewTab() *tab {
|
|||||||
tabs[tab].page.Mode = structs.ModeLinkSelect
|
tabs[tab].page.Mode = structs.ModeLinkSelect
|
||||||
|
|
||||||
tabs[tab].view.Highlight("0")
|
tabs[tab].view.Highlight("0")
|
||||||
tabs[tab].view.ScrollToHighlight()
|
tabs[tab].scrollToHighlight()
|
||||||
// Display link URL in bottomBar
|
// Display link URL in bottomBar
|
||||||
bottomBar.SetLabel("[::b]Link: [::-]")
|
bottomBar.SetLabel("[::b]Link: [::-]")
|
||||||
bottomBar.SetText(tabs[tab].page.Links[0])
|
bottomBar.SetText(tabs[tab].page.Links[0])
|
||||||
@ -116,7 +130,7 @@ func makeNewTab() *tab {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
tabs[tab].view.Highlight(strconv.Itoa(index))
|
tabs[tab].view.Highlight(strconv.Itoa(index))
|
||||||
tabs[tab].view.ScrollToHighlight()
|
tabs[tab].scrollToHighlight()
|
||||||
// Display link URL in bottomBar
|
// Display link URL in bottomBar
|
||||||
bottomBar.SetLabel("[::b]Link: [::-]")
|
bottomBar.SetLabel("[::b]Link: [::-]")
|
||||||
bottomBar.SetText(tabs[tab].page.Links[index])
|
bottomBar.SetText(tabs[tab].page.Links[index])
|
||||||
@ -157,12 +171,12 @@ func makeNewTab() *tab {
|
|||||||
if t.hasContent() {
|
if t.hasContent() {
|
||||||
savePath, err := downloadPage(t.page)
|
savePath, err := downloadPage(t.page)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Error("Download Error", fmt.Sprintf("Error saving page content: %v", err))
|
go Error("Download Error", fmt.Sprintf("Error saving page content: %v", err))
|
||||||
} else {
|
} else {
|
||||||
Info(fmt.Sprintf("Page content saved to %s. ", savePath))
|
go Info(fmt.Sprintf("Page content saved to %s. ", savePath))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Info("The current page has no content, so it couldn't be downloaded.")
|
go Info("The current page has no content, so it couldn't be downloaded.")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
case config.CmdBack:
|
case config.CmdBack:
|
||||||
@ -179,13 +193,13 @@ func makeNewTab() *tab {
|
|||||||
currentURL := tabs[curTab].page.URL
|
currentURL := tabs[curTab].page.URL
|
||||||
err := clipboard.WriteAll(currentURL)
|
err := clipboard.WriteAll(currentURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Error("Copy Error", err.Error())
|
go Error("Copy Error", err.Error())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
case config.CmdCopyTargetURL:
|
case config.CmdCopyTargetURL:
|
||||||
currentURL := t.page.URL
|
currentURL := t.page.URL
|
||||||
selectedURL := t.HighlightedURL()
|
selectedURL := t.highlightedURL()
|
||||||
if selectedURL == "" {
|
if selectedURL == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -194,28 +208,42 @@ func makeNewTab() *tab {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
err := clipboard.WriteAll(selectedURL)
|
err := clipboard.WriteAll(selectedURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Error("Copy Error", err.Error())
|
go Error("Copy Error", err.Error())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
err = clipboard.WriteAll(copiedURL.String())
|
err = clipboard.WriteAll(copiedURL.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Error("Copy Error", err.Error())
|
go Error("Copy Error", err.Error())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
case config.CmdURLHandlerOpen:
|
||||||
|
currentSelection := t.view.GetHighlights()
|
||||||
|
t.preferURLHandler = true
|
||||||
|
// Copied code from when enter key is pressed
|
||||||
|
if len(currentSelection) > 0 {
|
||||||
|
bottomBar.SetLabel("")
|
||||||
|
linkN, _ := strconv.Atoi(currentSelection[0])
|
||||||
|
t.page.Selected = t.page.Links[linkN]
|
||||||
|
t.page.SelectedID = currentSelection[0]
|
||||||
|
go followLink(&t, t.page.URL, t.page.Links[linkN])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
// Number key: 1-9, 0, LINK1-LINK10
|
// Number key: 1-9, 0, LINK1-LINK10
|
||||||
if cmd >= config.CmdLink1 && cmd <= config.CmdLink0 {
|
if cmd >= config.CmdLink1 && cmd <= config.CmdLink0 {
|
||||||
if int(cmd) <= len(t.page.Links) {
|
if int(cmd) <= len(t.page.Links) {
|
||||||
// It's a valid link number
|
// It's a valid link number
|
||||||
followLink(&t, t.page.URL, t.page.Links[cmd-1])
|
t.preferURLHandler = false // Reset in case
|
||||||
|
go followLink(&t, t.page.URL, t.page.Links[cmd-1])
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scrolling stuff
|
// Scrolling stuff
|
||||||
|
// Copied in scrollTo
|
||||||
|
|
||||||
key := event.Key()
|
key := event.Key()
|
||||||
mod := event.Modifiers()
|
mod := event.Modifiers()
|
||||||
@ -290,6 +318,21 @@ func makeNewTab() *tab {
|
|||||||
return &t
|
return &t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// historyCachePage caches certain info about the current page in the tab's history,
|
||||||
|
// see #122 for details.
|
||||||
|
func (t *tab) historyCachePage() {
|
||||||
|
if t.page == nil || t.page.URL == "" || t.history.pageCache == nil || len(t.history.pageCache) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.history.pageCache[t.history.pos] = &tabHistoryPageCache{
|
||||||
|
row: t.page.Row,
|
||||||
|
column: t.page.Column,
|
||||||
|
selected: t.page.Selected,
|
||||||
|
selectedID: t.page.SelectedID,
|
||||||
|
mode: t.page.Mode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// addToHistory adds the given URL to history.
|
// addToHistory adds the given URL to history.
|
||||||
// It assumes the URL is currently being loaded and displayed on the page.
|
// It assumes the URL is currently being loaded and displayed on the page.
|
||||||
func (t *tab) addToHistory(u string) {
|
func (t *tab) addToHistory(u string) {
|
||||||
@ -297,14 +340,20 @@ func (t *tab) addToHistory(u string) {
|
|||||||
// We're somewhere in the middle of the history instead, with URLs ahead and behind.
|
// We're somewhere in the middle of the history instead, with URLs ahead and behind.
|
||||||
// The URLs ahead need to be removed so this new URL is the most recent item in the history
|
// The URLs ahead need to be removed so this new URL is the most recent item in the history
|
||||||
t.history.urls = t.history.urls[:t.history.pos+1]
|
t.history.urls = t.history.urls[:t.history.pos+1]
|
||||||
|
// Same for page cache
|
||||||
|
t.history.pageCache = t.history.pageCache[:t.history.pos+1]
|
||||||
}
|
}
|
||||||
t.history.urls = append(t.history.urls, u)
|
t.history.urls = append(t.history.urls, u)
|
||||||
t.history.pos++
|
t.history.pos++
|
||||||
|
|
||||||
|
// Cache page info for #122
|
||||||
|
t.history.pageCache = append(t.history.pageCache, &tabHistoryPageCache{}) // Add new spot
|
||||||
|
t.historyCachePage() // Fill it with data
|
||||||
}
|
}
|
||||||
|
|
||||||
// pageUp scrolls up 75% of the height of the terminal, like Bombadillo.
|
// pageUp scrolls up 75% of the height of the terminal, like Bombadillo.
|
||||||
func (t *tab) pageUp() {
|
func (t *tab) pageUp() {
|
||||||
t.page.Row -= (termH / 4) * 3
|
t.page.Row -= termH / 2
|
||||||
if t.page.Row < 0 {
|
if t.page.Row < 0 {
|
||||||
t.page.Row = 0
|
t.page.Row = 0
|
||||||
}
|
}
|
||||||
@ -315,7 +364,7 @@ func (t *tab) pageUp() {
|
|||||||
func (t *tab) pageDown() {
|
func (t *tab) pageDown() {
|
||||||
height, _ := t.view.GetBufferSize()
|
height, _ := t.view.GetBufferSize()
|
||||||
|
|
||||||
t.page.Row += (termH / 4) * 3
|
t.page.Row += termH / 2
|
||||||
if t.page.Row > height {
|
if t.page.Row > height {
|
||||||
t.page.Row = height
|
t.page.Row = height
|
||||||
}
|
}
|
||||||
@ -357,7 +406,7 @@ func (t *tab) applyHorizontalScroll() {
|
|||||||
// Scrolled to the right far enough that no left margin is needed
|
// Scrolled to the right far enough that no left margin is needed
|
||||||
browser.AddTab(
|
browser.AddTab(
|
||||||
strconv.Itoa(i),
|
strconv.Itoa(i),
|
||||||
makeTabLabel(strconv.Itoa(i+1)),
|
t.label(),
|
||||||
makeContentLayout(t.view, 0),
|
makeContentLayout(t.view, 0),
|
||||||
)
|
)
|
||||||
t.view.ScrollTo(t.page.Row, t.page.Column-leftMargin())
|
t.view.ScrollTo(t.page.Row, t.page.Column-leftMargin())
|
||||||
@ -365,7 +414,7 @@ func (t *tab) applyHorizontalScroll() {
|
|||||||
// Left margin is still needed, but is not necessarily at the right size by default
|
// Left margin is still needed, but is not necessarily at the right size by default
|
||||||
browser.AddTab(
|
browser.AddTab(
|
||||||
strconv.Itoa(i),
|
strconv.Itoa(i),
|
||||||
makeTabLabel(strconv.Itoa(i+1)),
|
t.label(),
|
||||||
makeContentLayout(t.view, leftMargin()-t.page.Column),
|
makeContentLayout(t.view, leftMargin()-t.page.Column),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -378,6 +427,39 @@ func (t *tab) applyScroll() {
|
|||||||
t.applyHorizontalScroll()
|
t.applyHorizontalScroll()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// scrollTo scrolls the current tab to specified position. Like
|
||||||
|
// cview.TextView.ScrollTo but using the custom scrolling logic required by #196.
|
||||||
|
func (t *tab) scrollTo(row, col int) {
|
||||||
|
height, width := t.view.GetBufferSize()
|
||||||
|
|
||||||
|
// Keep row and col within limits
|
||||||
|
|
||||||
|
if row < 0 {
|
||||||
|
row = 0
|
||||||
|
} else if row > height {
|
||||||
|
row = height
|
||||||
|
}
|
||||||
|
if col < 0 {
|
||||||
|
col = 0
|
||||||
|
} else if col > width {
|
||||||
|
col = width
|
||||||
|
}
|
||||||
|
|
||||||
|
t.page.Row = row
|
||||||
|
t.page.Column = col
|
||||||
|
t.applyScroll()
|
||||||
|
App.Draw()
|
||||||
|
}
|
||||||
|
|
||||||
|
// scrollToHighlight scrolls the current tab to specified position. Like
|
||||||
|
// cview.TextView.ScrollToHighlight but using the custom scrolling logic
|
||||||
|
// required by #196.
|
||||||
|
func (t *tab) scrollToHighlight() {
|
||||||
|
t.view.ScrollToHighlight()
|
||||||
|
App.Draw()
|
||||||
|
t.scrollTo(t.view.GetScrollOffset())
|
||||||
|
}
|
||||||
|
|
||||||
// saveBottomBar saves the current bottomBar values in the tab.
|
// saveBottomBar saves the current bottomBar values in the tab.
|
||||||
func (t *tab) saveBottomBar() {
|
func (t *tab) saveBottomBar() {
|
||||||
t.barLabel = bottomBar.GetLabel()
|
t.barLabel = bottomBar.GetLabel()
|
||||||
@ -432,8 +514,8 @@ func (t *tab) applyAll() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HighlightedURL returns the currently selected URL
|
// highlightedURL returns the currently selected URL
|
||||||
func (t *tab) HighlightedURL() string {
|
func (t *tab) highlightedURL() string {
|
||||||
currentSelection := tabs[curTab].view.GetHighlights()
|
currentSelection := tabs[curTab].view.GetHighlights()
|
||||||
|
|
||||||
if len(currentSelection) > 0 {
|
if len(currentSelection) > 0 {
|
||||||
@ -443,3 +525,35 @@ func (t *tab) HighlightedURL() string {
|
|||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// label returns the label to use for the tab name
|
||||||
|
func (t *tab) label() string {
|
||||||
|
tn := tabNumber(t)
|
||||||
|
if tn < 0 {
|
||||||
|
// Invalid tab, shouldn't happen
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment so there's no tab 0 in the label
|
||||||
|
tn++
|
||||||
|
|
||||||
|
if t.page.URL == "" || t.page.URL == "about:newtab" {
|
||||||
|
// Just use tab number
|
||||||
|
// Spaces around to keep original Amfora look
|
||||||
|
return fmt.Sprintf(" %d ", tn)
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(t.page.URL, "about:") {
|
||||||
|
// Don't look for domain, put the whole URL except query strings
|
||||||
|
return strings.SplitN(t.page.URL, "?", 2)[0]
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(t.page.URL, "file://") {
|
||||||
|
// File URL, use file or folder as tab name
|
||||||
|
return cview.Escape(path.Base(t.page.URL[7:]))
|
||||||
|
}
|
||||||
|
// Otherwise, it's a Gemini URL
|
||||||
|
pu, err := url.Parse(t.page.URL)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Sprintf(" %d ", tn)
|
||||||
|
}
|
||||||
|
return pu.Host
|
||||||
|
}
|
||||||
|
@ -25,4 +25,13 @@ Thank you to the following contributors, who have helped make Amfora great. FOSS
|
|||||||
* Himanshu (@singalhimanshu)
|
* Himanshu (@singalhimanshu)
|
||||||
* @regr4
|
* @regr4
|
||||||
* Anas Mohamed (@amohamed11)
|
* Anas Mohamed (@amohamed11)
|
||||||
|
* David Jimenez (@dvejmz)
|
||||||
|
* Michael McDonagh (@m-mcdonagh)
|
||||||
|
* mooff (@awfulcooking)
|
||||||
|
* Josias (@justjosias)
|
||||||
|
* mntn (@mntn-xyz)
|
||||||
|
* Maxime Bouillot (@Arkaeriit)
|
||||||
|
* Emily (@emily-is-my-username)
|
||||||
|
* Autumn! (@autumnull)
|
||||||
|
* William Rehwinkel (@FiskFan1999)
|
||||||
`)
|
`)
|
||||||
|
105
display/util.go
105
display/util.go
@ -6,9 +6,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.rocketnine.space/tslocum/cview"
|
"code.rocketnine.space/tslocum/cview"
|
||||||
"github.com/makeworld-the-better-one/go-gemini"
|
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"golang.org/x/text/unicode/norm"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// This file contains funcs that are small, self-contained utilities.
|
// This file contains funcs that are small, self-contained utilities.
|
||||||
@ -34,12 +32,6 @@ func makeContentLayout(tv *cview.TextView, leftMargin int) *cview.Flex {
|
|||||||
return vert
|
return vert
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeTabLabel takes a string and adds spacing to it, making it
|
|
||||||
// suitable for display as a tab label.
|
|
||||||
func makeTabLabel(s string) string {
|
|
||||||
return " " + s + " "
|
|
||||||
}
|
|
||||||
|
|
||||||
// tabNumber gets the index of the tab in the tabs slice. It returns -1
|
// tabNumber gets the index of the tab in the tabs slice. It returns -1
|
||||||
// if the tab is not in that slice.
|
// if the tab is not in that slice.
|
||||||
func tabNumber(t *tab) int {
|
func tabNumber(t *tab) int {
|
||||||
@ -63,7 +55,14 @@ func isValidTab(t *tab) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func leftMargin() int {
|
func leftMargin() int {
|
||||||
return int(float64(termW) * viper.GetFloat64("a-general.left_margin"))
|
// Return the left margin size that centers the text, assuming it's the max width
|
||||||
|
// https://github.com/makeworld-the-better-one/amfora/issues/233
|
||||||
|
|
||||||
|
lm := (termW - viper.GetInt("a-general.max_width")) / 2
|
||||||
|
if lm < 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return lm
|
||||||
}
|
}
|
||||||
|
|
||||||
func textWidth() int {
|
func textWidth() int {
|
||||||
@ -73,13 +72,11 @@ func textWidth() int {
|
|||||||
return viper.GetInt("a-general.max_width")
|
return viper.GetInt("a-general.max_width")
|
||||||
}
|
}
|
||||||
|
|
||||||
rightMargin := leftMargin()
|
// Subtract left and right margin from total width to get text width
|
||||||
if leftMargin() > 10 {
|
// Left and right margin are equal because text is automatically centered, see:
|
||||||
// 10 is the max right margin
|
// https://github.com/makeworld-the-better-one/amfora/issues/233
|
||||||
rightMargin = 10
|
|
||||||
}
|
|
||||||
|
|
||||||
max := termW - leftMargin() - rightMargin
|
max := termW - leftMargin()*2
|
||||||
if max < viper.GetInt("a-general.max_width") {
|
if max < viper.GetInt("a-general.max_width") {
|
||||||
return max
|
return max
|
||||||
}
|
}
|
||||||
@ -101,81 +98,3 @@ func resolveRelLink(t *tab, prev, next string) (string, error) {
|
|||||||
}
|
}
|
||||||
return prevParsed.ResolveReference(nextParsed).String(), nil
|
return prevParsed.ResolveReference(nextParsed).String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// normalizeURL attempts to make URLs that are different strings
|
|
||||||
// but point to the same place all look the same.
|
|
||||||
//
|
|
||||||
// Example: gemini://gus.guru:1965/ and //gus.guru/.
|
|
||||||
// This function will take both output the same URL each time.
|
|
||||||
//
|
|
||||||
// It will also percent-encode invalid characters, and decode chars
|
|
||||||
// that don't need to be encoded. It will also apply Unicode NFC
|
|
||||||
// normalization.
|
|
||||||
//
|
|
||||||
// The string passed must already be confirmed to be a URL.
|
|
||||||
// Detection of a search string vs. a URL must happen elsewhere.
|
|
||||||
//
|
|
||||||
// It only works with absolute URLs.
|
|
||||||
func normalizeURL(u string) string {
|
|
||||||
u = norm.NFC.String(u)
|
|
||||||
|
|
||||||
tmp, err := gemini.GetPunycodeURL(u)
|
|
||||||
if err != nil {
|
|
||||||
return u
|
|
||||||
}
|
|
||||||
u = tmp
|
|
||||||
parsed, _ := url.Parse(u)
|
|
||||||
|
|
||||||
if parsed.Scheme == "" {
|
|
||||||
// Always add scheme
|
|
||||||
parsed.Scheme = "gemini"
|
|
||||||
} else if parsed.Scheme != "gemini" {
|
|
||||||
// Not a gemini URL, nothing to do
|
|
||||||
return u
|
|
||||||
}
|
|
||||||
|
|
||||||
parsed.User = nil // No passwords in Gemini
|
|
||||||
parsed.Fragment = "" // No fragments either
|
|
||||||
if parsed.Port() == "1965" {
|
|
||||||
// Always remove default port
|
|
||||||
hostname := parsed.Hostname()
|
|
||||||
if strings.Contains(hostname, ":") {
|
|
||||||
parsed.Host = "[" + parsed.Hostname() + "]"
|
|
||||||
} else {
|
|
||||||
parsed.Host = parsed.Hostname()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add slash to the end of a URL with just a domain
|
|
||||||
// gemini://example.com -> gemini://example.com/
|
|
||||||
if parsed.Path == "" {
|
|
||||||
parsed.Path = "/"
|
|
||||||
} else {
|
|
||||||
// Decode and re-encode path
|
|
||||||
// This removes needless encoding, like that of ASCII chars
|
|
||||||
// And encodes anything that wasn't but should've been
|
|
||||||
parsed.RawPath = strings.ReplaceAll(url.PathEscape(parsed.Path), "%2F", "/")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do the same to the query string
|
|
||||||
un, err := gemini.QueryUnescape(parsed.RawQuery)
|
|
||||||
if err == nil {
|
|
||||||
parsed.RawQuery = gemini.QueryEscape(un)
|
|
||||||
}
|
|
||||||
|
|
||||||
return parsed.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// fixUserURL will take a user-typed URL and add a gemini scheme to it if
|
|
||||||
// necessary. It is not the same as normalizeURL, and that func should still
|
|
||||||
// be used, afterward.
|
|
||||||
//
|
|
||||||
// For example "example.com" will become "gemini://example.com", but
|
|
||||||
// "//example.com" will be left untouched.
|
|
||||||
func fixUserURL(u string) string {
|
|
||||||
if !strings.HasPrefix(u, "//") && !strings.HasPrefix(u, "gemini://") && !strings.Contains(u, "://") {
|
|
||||||
// Assume it's a Gemini URL
|
|
||||||
u = "gemini://" + u
|
|
||||||
}
|
|
||||||
return u
|
|
||||||
}
|
|
||||||
|
69
go.mod
69
go.mod
@ -1,29 +1,58 @@
|
|||||||
module github.com/makeworld-the-better-one/amfora
|
module github.com/makeworld-the-better-one/amfora
|
||||||
|
|
||||||
go 1.14
|
go 1.19
|
||||||
|
|
||||||
require (
|
require (
|
||||||
code.rocketnine.space/tslocum/cview v1.5.6-0.20210525194531-92dca67ac283
|
code.rocketnine.space/tslocum/cview v1.5.6-0.20210530175404-7e8817f20bdc
|
||||||
|
github.com/alecthomas/chroma v0.10.0
|
||||||
github.com/atotto/clipboard v0.1.4
|
github.com/atotto/clipboard v0.1.4
|
||||||
github.com/dustin/go-humanize v1.0.0
|
github.com/dustin/go-humanize v1.0.1
|
||||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
github.com/gdamore/tcell/v2 v2.6.0
|
||||||
github.com/gdamore/tcell/v2 v2.3.3
|
github.com/makeworld-the-better-one/go-gemini v0.13.1
|
||||||
github.com/google/go-cmp v0.5.0 // indirect
|
github.com/makeworld-the-better-one/go-gemini-socks5 v1.0.0
|
||||||
github.com/makeworld-the-better-one/go-gemini v0.11.0
|
github.com/makeworld-the-better-one/rr v1.0.0
|
||||||
github.com/mitchellh/go-homedir v1.1.0
|
github.com/mitchellh/go-homedir v1.1.0
|
||||||
github.com/mitchellh/mapstructure v1.3.1 // indirect
|
github.com/mmcdole/gofeed v1.2.1
|
||||||
github.com/mmcdole/gofeed v1.1.2
|
github.com/muesli/termenv v0.15.2
|
||||||
github.com/pelletier/go-toml v1.8.0 // indirect
|
github.com/rkoesters/xdg v0.0.1
|
||||||
github.com/rkoesters/xdg v0.0.0-20181125232953-edd15b846f9b
|
github.com/schollz/progressbar/v3 v3.13.1
|
||||||
github.com/schollz/progressbar/v3 v3.8.0
|
github.com/spf13/viper v1.16.0
|
||||||
github.com/spf13/afero v1.2.2 // indirect
|
github.com/stretchr/testify v1.8.4
|
||||||
github.com/spf13/cast v1.3.1 // indirect
|
golang.org/x/text v0.13.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
code.rocketnine.space/tslocum/cbind v0.1.5 // indirect
|
||||||
|
github.com/PuerkitoBio/goquery v1.8.0 // indirect
|
||||||
|
github.com/andybalholm/cascadia v1.3.1 // indirect
|
||||||
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/dlclark/regexp2 v1.4.0 // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||||
|
github.com/gdamore/encoding v1.0.0 // indirect
|
||||||
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
|
github.com/magiconair/properties v1.8.7 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.18 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||||
|
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
|
github.com/mmcdole/goxpp v1.1.0 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/rivo/uniseg v0.4.3 // indirect
|
||||||
|
github.com/spf13/afero v1.9.5 // indirect
|
||||||
|
github.com/spf13/cast v1.5.1 // indirect
|
||||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/spf13/viper v1.7.1
|
github.com/subosito/gotenv v1.4.2 // indirect
|
||||||
github.com/stretchr/testify v1.6.1
|
golang.org/x/net v0.17.0 // indirect
|
||||||
golang.org/x/text v0.3.6
|
golang.org/x/sys v0.13.0 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
golang.org/x/term v0.13.0 // indirect
|
||||||
gopkg.in/ini.v1 v1.62.0 // indirect
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
588
go.sum
588
go.sum
@ -3,257 +3,269 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
|
|||||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
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.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.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.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
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.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.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||||
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
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.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.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=
|
||||||
code.rocketnine.space/tslocum/cbind v0.1.5 h1:i6NkeLLNPNMS4NWNi3302Ay3zSU6MrqOT+yJskiodxE=
|
code.rocketnine.space/tslocum/cbind v0.1.5 h1:i6NkeLLNPNMS4NWNi3302Ay3zSU6MrqOT+yJskiodxE=
|
||||||
code.rocketnine.space/tslocum/cbind v0.1.5/go.mod h1:LtfqJTzM7qhg88nAvNhx+VnTjZ0SXBJtxBObbfBWo/M=
|
code.rocketnine.space/tslocum/cbind v0.1.5/go.mod h1:LtfqJTzM7qhg88nAvNhx+VnTjZ0SXBJtxBObbfBWo/M=
|
||||||
code.rocketnine.space/tslocum/cview v1.5.6-0.20210525194531-92dca67ac283 h1:5KBGXdQdfV09eYXOZuFTxqDujndqtRraXj+lyFcxlPk=
|
code.rocketnine.space/tslocum/cview v1.5.6-0.20210530175404-7e8817f20bdc h1:nAcBp7ZCWHpa8fHpynCbULDTAZgPQv28+Z+QnhnFG7E=
|
||||||
code.rocketnine.space/tslocum/cview v1.5.6-0.20210525194531-92dca67ac283/go.mod h1:KBRxzIsj8bfgFpnMpkGVoxsrPUvnQsRnX29XJ2yzB6M=
|
code.rocketnine.space/tslocum/cview v1.5.6-0.20210530175404-7e8817f20bdc/go.mod h1:KBRxzIsj8bfgFpnMpkGVoxsrPUvnQsRnX29XJ2yzB6M=
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
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/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/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U=
|
||||||
github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE=
|
github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI=
|
||||||
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
|
||||||
github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo=
|
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
|
||||||
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
|
||||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
|
||||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
|
||||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
|
||||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
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/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||||
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-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
|
||||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
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/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
|
||||||
|
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||||
|
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||||
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||||
github.com/gdamore/tcell/v2 v2.2.0/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU=
|
github.com/gdamore/tcell/v2 v2.2.0/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU=
|
||||||
github.com/gdamore/tcell/v2 v2.3.3 h1:RKoI6OcqYrr/Do8yHZklecdGzDTJH9ACKdfECbRdw3M=
|
|
||||||
github.com/gdamore/tcell/v2 v2.3.3/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU=
|
github.com/gdamore/tcell/v2 v2.3.3/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/gdamore/tcell/v2 v2.6.0 h1:OKbluoP9VYmJwZwq/iLb4BxwKcwGthaa1YNBJIyCySg=
|
||||||
|
github.com/gdamore/tcell/v2 v2.6.0/go.mod h1:be9omFATkdr0D9qewWW3d+MEvl5dha+Etb5y65J2H8Y=
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
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/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.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.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.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.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.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.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 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/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.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.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.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
|
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.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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
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-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-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/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
|
github.com/google/uuid v1.1.2/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.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/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
|
||||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
|
||||||
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/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
|
||||||
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
|
||||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
|
||||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
|
||||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
|
||||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
|
||||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
|
||||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
|
||||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
|
||||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
|
||||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
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/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 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
|
||||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
|
||||||
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
|
|
||||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
|
||||||
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
|
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
|
||||||
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/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 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
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/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
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/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
|
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||||
github.com/makeworld-the-better-one/go-gemini v0.11.0 h1:MNGiULJFvcqls9oCy40tE897hDeKvNmEK9i5kRucgQk=
|
github.com/makeworld-the-better-one/go-gemini v0.13.1 h1:qStBcQhgE29ViPCwCAyW65ibqeIEeyUV8TSp8hHJRkU=
|
||||||
github.com/makeworld-the-better-one/go-gemini v0.11.0/go.mod h1:F+3x+R1xeYK90jMtBq+U+8Sh64r2dHleDZ/en3YgSmg=
|
github.com/makeworld-the-better-one/go-gemini v0.13.1/go.mod h1:SL62NFyZi6zcjtGwBc1euN1S3x/MHgcYdA/Ninrnwmo=
|
||||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
github.com/makeworld-the-better-one/go-gemini-socks5 v1.0.0 h1:D2o1rIfP/KOxcL3m3rzo4cfWNqfcGaMIhnU0keJc1+o=
|
||||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
github.com/makeworld-the-better-one/go-gemini-socks5 v1.0.0/go.mod h1:mfPK9BfBAAyLKuxPEbZi8mgrGmVlzMKVTGElVspuVR8=
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/makeworld-the-better-one/rr v1.0.0 h1:NclI3Z32Q/+kNzP8OOlpPFuYeN0BFGgKU0MLd9ZmfQQ=
|
||||||
|
github.com/makeworld-the-better-one/rr v1.0.0/go.mod h1:sd3i5WAdkx/7ALu3V6AbVUyDw8uqmDQv55LgHta0f7g=
|
||||||
|
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-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
|
||||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
|
||||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
|
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
|
||||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
|
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
|
||||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
|
||||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
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/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
github.com/mmcdole/gofeed v1.2.1 h1:tPbFN+mfOLcM1kDF1x2c/N68ChbdBatkppdzf/vDe1s=
|
||||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mmcdole/gofeed v1.2.1/go.mod h1:2wVInNpgmC85q16QTTuwbuKxtKkHLCDDtf0dCmnrNr4=
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mmcdole/goxpp v1.1.0 h1:WwslZNF7KNAXTFuzRtn/OKZxFLJAAyOA9w82mDz2ZGI=
|
||||||
github.com/mitchellh/mapstructure v1.3.1 h1:cCBH2gTD2K0OtLlv/Y5H01VQCqmlDxz30kS5Y5bqfLA=
|
github.com/mmcdole/goxpp v1.1.0/go.mod h1:v+25+lT2ViuQ7mVxcncQ8ch1URund48oH+jhjiwEgS8=
|
||||||
github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
|
||||||
github.com/mmcdole/gofeed v1.1.2 h1:7I5su6dO5/Rg2LEKS5ofPISVbi2vfxO2SNVSA/QN1y4=
|
|
||||||
github.com/mmcdole/gofeed v1.1.2/go.mod h1:QQO3maftbOu+hiVOGOZDRLymqGQCos4zxbA4j89gMrE=
|
|
||||||
github.com/mmcdole/goxpp v0.0.0-20181012175147-0068e33feabf h1:sWGE2v+hO0Nd4yFU/S/mDBM5plIU8v/Qhfz41hkDIAI=
|
|
||||||
github.com/mmcdole/goxpp v0.0.0-20181012175147-0068e33feabf/go.mod h1:pasqhqstspkosTneA62Nc+2p9SOBBYAPbnmRRWPQ0V8=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
||||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
||||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
||||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMFtw=
|
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
||||||
github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs=
|
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
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/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.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rkoesters/xdg v0.0.0-20181125232953-edd15b846f9b h1:8NiY6v9/IlFU8osj1L7kqzRbrG6e3izRQQjGze1Q1R0=
|
github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw=
|
||||||
github.com/rkoesters/xdg v0.0.0-20181125232953-edd15b846f9b/go.mod h1:T1HolqzmdHnJIH6p7A9LDuvYGQgEHx9ijX3vKgDKU60=
|
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
github.com/rkoesters/xdg v0.0.1 h1:RmfYxghVvIsb4d51u5LtNOcwqY5r3P44u6o86qqvBMA=
|
||||||
|
github.com/rkoesters/xdg v0.0.1/go.mod h1:5DcbjvJkY00fIOKkaBnylbC/rmc1NNJP5dmUcnlcm7U=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
github.com/schollz/progressbar/v3 v3.13.1 h1:o8rySDYiQ59Mwzy2FELeHY5ZARXZTVJC7iHD6PEFUiE=
|
||||||
github.com/schollz/progressbar/v3 v3.8.0 h1:BKyefEMgFBDbo+JaeqHcm/9QdSj8qG8sUY+6UppGpnw=
|
github.com/schollz/progressbar/v3 v3.13.1/go.mod h1:xvrbki8kfT1fzWzBT/UZd9L6GA+jdL7HAgq2RFnO6fQ=
|
||||||
github.com/schollz/progressbar/v3 v3.8.0/go.mod h1:Y9mmL2knZj3LUaBDyBEzFdPrymIr08hnlFMZmfxwbx4=
|
github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
|
||||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
|
||||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
|
||||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
|
||||||
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.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
|
|
||||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
|
||||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
|
||||||
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
|
|
||||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
|
||||||
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 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
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 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
|
github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc=
|
||||||
github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
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/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/urfave/cli v1.22.3/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
|
||||||
|
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
|
||||||
|
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.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
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.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/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-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-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-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
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-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-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-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-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/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
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/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-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
@ -263,17 +275,23 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
|
|||||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/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-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-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-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/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.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.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
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/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
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-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/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-20181201002055-351d144fa1fc/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-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-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-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
@ -282,24 +300,54 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn
|
|||||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/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-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-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-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20201216054612-986b41b23924 h1:QsnDpLLOKwHBBDa8nDws4DYNc/ryVW2vCpxCs09d4PY=
|
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
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-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
|
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-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-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-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-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-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-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-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-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
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.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
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-20181026203630-95b1ffbd15a5/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-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-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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@ -307,58 +355,145 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/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-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-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/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-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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210223095934-7937bea0104d/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-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210309040221-94ec62e08169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210309040221-94ec62e08169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea h1:+WiDlPBBaO+h9vPNZi8uJ3k4BkKQB7Iow3aqwHVA5hI=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||||
|
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210503060354-a79de5458b56 h1:b8jxX3zqjpqb2LklXPzKSGJhzyxCOZSz8ncv8Nv+y7w=
|
|
||||||
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
|
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||||
|
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
|
||||||
|
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||||
|
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.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.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.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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||||
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
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-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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-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-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-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-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-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
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-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-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-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-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-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-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-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/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/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
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-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.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.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.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||||
google.golang.org/api v0.9.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.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.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.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.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.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-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-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-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
@ -368,30 +503,77 @@ google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98
|
|||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/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-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-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.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
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/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
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-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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||||
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
|
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/ini.v1 v1.62.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.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
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-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-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-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-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/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=
|
||||||
|
@ -3,18 +3,42 @@ package logger
|
|||||||
// For debugging
|
// For debugging
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Log *log.Logger
|
var Logger *log.Logger
|
||||||
|
|
||||||
func Init() error {
|
func GetLogger() (*log.Logger, error) {
|
||||||
f, err := os.Create("debug.log")
|
if Logger != nil {
|
||||||
if err != nil {
|
return Logger, nil
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
Log = log.New(f, "", log.LstdFlags)
|
|
||||||
Log.Println("Started Log")
|
var writer io.Writer
|
||||||
return nil
|
var err error
|
||||||
|
|
||||||
|
debugModeEnabled := os.Getenv("AMFORA_DEBUG") == "1"
|
||||||
|
if debugModeEnabled {
|
||||||
|
writer, err = os.Create("debug.log")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Suppress all logging output if debug mode is disabled
|
||||||
|
writer = ioutil.Discard
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger = log.New(writer, "", log.LstdFlags)
|
||||||
|
|
||||||
|
if !debugModeEnabled {
|
||||||
|
// Clear all flags to skip log output formatting step to increase
|
||||||
|
// performance somewhat if we're not logging anything
|
||||||
|
Logger.SetFlags(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.Println("Started logger")
|
||||||
|
|
||||||
|
return Logger, nil
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
package renderer
|
package renderer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
urlPkg "net/url"
|
urlPkg "net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
@ -12,13 +13,25 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.rocketnine.space/tslocum/cview"
|
"code.rocketnine.space/tslocum/cview"
|
||||||
|
"github.com/alecthomas/chroma/formatters"
|
||||||
|
"github.com/alecthomas/chroma/lexers"
|
||||||
|
"github.com/alecthomas/chroma/styles"
|
||||||
"github.com/makeworld-the-better-one/amfora/config"
|
"github.com/makeworld-the-better-one/amfora/config"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Terminal color information, set during display initialization by display/display.go
|
||||||
|
var TermColor string
|
||||||
|
|
||||||
// Regex for identifying ANSI color codes
|
// Regex for identifying ANSI color codes
|
||||||
var ansiRegex = regexp.MustCompile(`\x1b\[[0-9;]*m`)
|
var ansiRegex = regexp.MustCompile(`\x1b\[[0-9;]*m`)
|
||||||
|
|
||||||
|
// Regex for identifying possible language string, based on RFC 6838 and lexers used by Chroma
|
||||||
|
var langRegex = regexp.MustCompile(`^([a-zA-Z0-9]+/)?[a-zA-Z0-9]+([a-zA-Z0-9!_\#\$\&\-\^\.\+]+)*`)
|
||||||
|
|
||||||
|
// Regex for removing trailing newline (without disturbing ANSI codes) from code formatted with Chroma
|
||||||
|
var trailingNewline = regexp.MustCompile(`(\r?\n)(?:\x1b\[[0-9;]*m)*$`)
|
||||||
|
|
||||||
// RenderANSI renders plain text pages containing ANSI codes.
|
// RenderANSI renders plain text pages containing ANSI codes.
|
||||||
// Practically, it is used for the text/x-ansi.
|
// Practically, it is used for the text/x-ansi.
|
||||||
func RenderANSI(s string) string {
|
func RenderANSI(s string) string {
|
||||||
@ -45,6 +58,10 @@ func RenderPlainText(s string) string {
|
|||||||
//
|
//
|
||||||
// Set includeFirst to true if the prefix and suffix should be applied to the first wrapped line as well
|
// Set includeFirst to true if the prefix and suffix should be applied to the first wrapped line as well
|
||||||
func wrapLine(line string, width int, prefix, suffix string, includeFirst bool) []string {
|
func wrapLine(line string, width int, prefix, suffix string, includeFirst bool) []string {
|
||||||
|
if width < 1 {
|
||||||
|
width = 1
|
||||||
|
}
|
||||||
|
|
||||||
// Anonymous function to allow recovery from potential WordWrap panic
|
// Anonymous function to allow recovery from potential WordWrap panic
|
||||||
var ret []string
|
var ret []string
|
||||||
func() {
|
func() {
|
||||||
@ -159,6 +176,14 @@ func convertRegularGemini(s string, numLinks, width int, proxied bool) (string,
|
|||||||
spacing = " "
|
spacing = " "
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Underline non-gemini links if enabled
|
||||||
|
var linkTag string
|
||||||
|
if viper.GetBool("a-general.underline") {
|
||||||
|
linkTag = `[` + config.GetColorString("foreign_link") + `::u]`
|
||||||
|
} else {
|
||||||
|
linkTag = `[` + config.GetColorString("foreign_link") + `]`
|
||||||
|
}
|
||||||
|
|
||||||
// Wrap and add link text
|
// Wrap and add link text
|
||||||
// Wrap the link text, but add some spaces to indent the wrapped lines past the link number
|
// Wrap the link text, but add some spaces to indent the wrapped lines past the link number
|
||||||
// Set the style tags
|
// Set the style tags
|
||||||
@ -166,15 +191,16 @@ func convertRegularGemini(s string, numLinks, width int, proxied bool) (string,
|
|||||||
|
|
||||||
var wrappedLink []string
|
var wrappedLink []string
|
||||||
|
|
||||||
if viper.GetBool("a-general.color") {
|
pU, err := urlPkg.Parse(url)
|
||||||
pU, err := urlPkg.Parse(url)
|
if !proxied && err == nil &&
|
||||||
if !proxied && err == nil &&
|
(pU.Scheme == "" || pU.Scheme == "gemini" || pU.Scheme == "about") {
|
||||||
(pU.Scheme == "" || pU.Scheme == "gemini" || pU.Scheme == "about") {
|
// A gemini link
|
||||||
// A gemini link
|
|
||||||
|
if viper.GetBool("a-general.color") {
|
||||||
// Add the link text in blue (in a region), and a gray link number to the left of it
|
// Add the link text in blue (in a region), and a gray link number to the left of it
|
||||||
// Those are the default colors, anyway
|
// Those are the default colors, anyway
|
||||||
|
|
||||||
wrappedLink = wrapLine(linkText, width,
|
wrappedLink = wrapLine(linkText, width-indent,
|
||||||
strings.Repeat(" ", indent)+
|
strings.Repeat(" ", indent)+
|
||||||
`["`+strconv.Itoa(num-1)+`"][`+config.GetColorString("amfora_link")+`]`,
|
`["`+strconv.Itoa(num-1)+`"][`+config.GetColorString("amfora_link")+`]`,
|
||||||
`[-][""]`,
|
`[-][""]`,
|
||||||
@ -187,33 +213,50 @@ func convertRegularGemini(s string, numLinks, width int, proxied bool) (string,
|
|||||||
`["` + strconv.Itoa(num-1) + `"][` + config.GetColorString("amfora_link") + `]` +
|
`["` + strconv.Itoa(num-1) + `"][` + config.GetColorString("amfora_link") + `]` +
|
||||||
wrappedLink[0] + `[-][""]`
|
wrappedLink[0] + `[-][""]`
|
||||||
} else {
|
} else {
|
||||||
// Not a gemini link
|
// No color
|
||||||
|
|
||||||
wrappedLink = wrapLine(linkText, width,
|
wrappedLink = wrapLine(linkText, width-indent,
|
||||||
|
strings.Repeat(" ", indent)+ // +4 for spaces and brackets
|
||||||
|
`["`+strconv.Itoa(num-1)+`"]`,
|
||||||
|
`[""]`,
|
||||||
|
false, // Don't indent the first line, it's the one with link number
|
||||||
|
)
|
||||||
|
|
||||||
|
wrappedLink[0] = `[::b][` + strconv.Itoa(num) + "[][::-] " +
|
||||||
|
`["` + strconv.Itoa(num-1) + `"]` +
|
||||||
|
wrappedLink[0] + `[""]`
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Not a gemini link
|
||||||
|
|
||||||
|
if viper.GetBool("a-general.color") {
|
||||||
|
// Color
|
||||||
|
|
||||||
|
wrappedLink = wrapLine(linkText, width-indent,
|
||||||
strings.Repeat(" ", indent)+
|
strings.Repeat(" ", indent)+
|
||||||
`["`+strconv.Itoa(num-1)+`"][`+config.GetColorString("foreign_link")+`]`,
|
`["`+strconv.Itoa(num-1)+`"]`+linkTag,
|
||||||
`[-][""]`,
|
`[-::-][""]`,
|
||||||
false, // Don't indent the first line, it's the one with link number
|
false, // Don't indent the first line, it's the one with link number
|
||||||
)
|
)
|
||||||
|
|
||||||
wrappedLink[0] = fmt.Sprintf(`[%s::b][`, config.GetColorString("link_number")) +
|
wrappedLink[0] = fmt.Sprintf(`[%s::b][`, config.GetColorString("link_number")) +
|
||||||
strconv.Itoa(num) + "[]" + "[-::-]" + spacing +
|
strconv.Itoa(num) + "[][-::-]" + spacing +
|
||||||
`["` + strconv.Itoa(num-1) + `"][` + config.GetColorString("foreign_link") + `]` +
|
`["` + strconv.Itoa(num-1) + `"]` + linkTag +
|
||||||
wrappedLink[0] + `[-][""]`
|
wrappedLink[0] + `[-::-][""]`
|
||||||
|
} else {
|
||||||
|
// No color
|
||||||
|
|
||||||
|
wrappedLink = wrapLine(linkText, width-indent,
|
||||||
|
strings.Repeat(" ", indent)+
|
||||||
|
`["`+strconv.Itoa(num-1)+`"]`,
|
||||||
|
`[::-][""]`,
|
||||||
|
false, // Don't indent the first line, it's the one with link number
|
||||||
|
)
|
||||||
|
|
||||||
|
wrappedLink[0] = `[::b][` + strconv.Itoa(num) + "[][::-]" + spacing +
|
||||||
|
`["` + strconv.Itoa(num-1) + `"]` +
|
||||||
|
wrappedLink[0] + `[::-][""]`
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// No colors allowed
|
|
||||||
|
|
||||||
wrappedLink = wrapLine(linkText, width,
|
|
||||||
strings.Repeat(" ", len(strconv.Itoa(num))+4)+ // +4 for spaces and brackets
|
|
||||||
`["`+strconv.Itoa(num-1)+`"]`,
|
|
||||||
`[""]`,
|
|
||||||
false, // Don't indent the first line, it's the one with link number
|
|
||||||
)
|
|
||||||
|
|
||||||
wrappedLink[0] = `[::b][` + strconv.Itoa(num) + "[][::-] " +
|
|
||||||
`["` + strconv.Itoa(num-1) + `"]` +
|
|
||||||
wrappedLink[0] + `[""]`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wrappedLines = append(wrappedLines, wrappedLink...)
|
wrappedLines = append(wrappedLines, wrappedLink...)
|
||||||
@ -222,7 +265,8 @@ func convertRegularGemini(s string, numLinks, width int, proxied bool) (string,
|
|||||||
} else if strings.HasPrefix(lines[i], "* ") {
|
} else if strings.HasPrefix(lines[i], "* ") {
|
||||||
if viper.GetBool("a-general.bullets") {
|
if viper.GetBool("a-general.bullets") {
|
||||||
// Wrap list item, and indent wrapped lines past the bullet
|
// Wrap list item, and indent wrapped lines past the bullet
|
||||||
wrappedItem := wrapLine(lines[i][1:], width,
|
wrappedItem := wrapLine(lines[i][1:],
|
||||||
|
width-4, // Subtract the 4 indent spaces
|
||||||
fmt.Sprintf(" [%s]", config.GetColorString("list_text")),
|
fmt.Sprintf(" [%s]", config.GetColorString("list_text")),
|
||||||
"[-]", false)
|
"[-]", false)
|
||||||
// Add bullet
|
// Add bullet
|
||||||
@ -230,7 +274,8 @@ func convertRegularGemini(s string, numLinks, width int, proxied bool) (string,
|
|||||||
wrappedItem[0] + "[-]"
|
wrappedItem[0] + "[-]"
|
||||||
wrappedLines = append(wrappedLines, wrappedItem...)
|
wrappedLines = append(wrappedLines, wrappedItem...)
|
||||||
} else {
|
} else {
|
||||||
wrappedItem := wrapLine(lines[i][1:], width,
|
wrappedItem := wrapLine(lines[i][1:],
|
||||||
|
width-4, // Subtract the 4 indent spaces
|
||||||
fmt.Sprintf(" [%s]", config.GetColorString("list_text")),
|
fmt.Sprintf(" [%s]", config.GetColorString("list_text")),
|
||||||
"[-]", false)
|
"[-]", false)
|
||||||
// Add "*"
|
// Add "*"
|
||||||
@ -251,7 +296,9 @@ func convertRegularGemini(s string, numLinks, width int, proxied bool) (string,
|
|||||||
lines[i] = strings.TrimPrefix(lines[i], ">")
|
lines[i] = strings.TrimPrefix(lines[i], ">")
|
||||||
lines[i] = strings.TrimPrefix(lines[i], " ")
|
lines[i] = strings.TrimPrefix(lines[i], " ")
|
||||||
wrappedLines = append(wrappedLines,
|
wrappedLines = append(wrappedLines,
|
||||||
wrapLine(lines[i], width, fmt.Sprintf("[%s::i]> ", config.GetColorString("quote_text")),
|
wrapLine(lines[i],
|
||||||
|
width-2, // Subtract 2 for width of prefix string
|
||||||
|
fmt.Sprintf("[%s::i]> ", config.GetColorString("quote_text")),
|
||||||
"[-::-]", true)...,
|
"[-::-]", true)...,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -289,11 +336,46 @@ func RenderGemini(s string, width int, proxied bool) (string, []string) {
|
|||||||
pre := false
|
pre := false
|
||||||
buf := "" // Block of regular or preformatted lines
|
buf := "" // Block of regular or preformatted lines
|
||||||
|
|
||||||
|
// Language, formatter, and style for syntax highlighting
|
||||||
|
lang := ""
|
||||||
|
formatterName := TermColor
|
||||||
|
styleName := viper.GetString("a-general.highlight_style")
|
||||||
|
|
||||||
// processPre is for rendering preformatted blocks
|
// processPre is for rendering preformatted blocks
|
||||||
processPre := func() {
|
processPre := func() {
|
||||||
|
|
||||||
|
syntaxHighlighted := false
|
||||||
|
|
||||||
|
// Perform syntax highlighting if language is set
|
||||||
|
if lang != "" {
|
||||||
|
style := styles.Get(styleName)
|
||||||
|
if style == nil {
|
||||||
|
style = styles.Fallback
|
||||||
|
}
|
||||||
|
formatter := formatters.Get(formatterName)
|
||||||
|
if formatter == nil {
|
||||||
|
formatter = formatters.Fallback
|
||||||
|
}
|
||||||
|
lexer := lexers.Get(lang)
|
||||||
|
if lexer == nil {
|
||||||
|
lexer = lexers.Fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tokenize and format the text after stripping ANSI codes, replacing buffer if there are no errors
|
||||||
|
iterator, err := lexer.Tokenise(nil, ansiRegex.ReplaceAllString(buf, ""))
|
||||||
|
if err == nil {
|
||||||
|
formattedBuffer := new(bytes.Buffer)
|
||||||
|
if formatter.Format(formattedBuffer, style, iterator) == nil {
|
||||||
|
// Strip extra newline added by Chroma and replace buffer
|
||||||
|
buf = string(trailingNewline.ReplaceAll(formattedBuffer.Bytes(), []byte{}))
|
||||||
|
}
|
||||||
|
syntaxHighlighted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Support ANSI color codes in preformatted blocks - see #59
|
// Support ANSI color codes in preformatted blocks - see #59
|
||||||
if viper.GetBool("a-general.color") && viper.GetBool("a-general.ansi") {
|
// This will also execute if code highlighting was successful for this block
|
||||||
|
if viper.GetBool("a-general.color") && (viper.GetBool("a-general.ansi") || syntaxHighlighted) {
|
||||||
buf = cview.TranslateANSI(buf)
|
buf = cview.TranslateANSI(buf)
|
||||||
// The TranslateANSI function will reset the colors when it encounters
|
// The TranslateANSI function will reset the colors when it encounters
|
||||||
// an ANSI reset code, injecting a full reset tag: [-:-:-]
|
// an ANSI reset code, injecting a full reset tag: [-:-:-]
|
||||||
@ -315,8 +397,12 @@ func RenderGemini(s string, width int, proxied bool) (string, []string) {
|
|||||||
// Lines are modified below to always end with \r\n
|
// Lines are modified below to always end with \r\n
|
||||||
buf = strings.TrimSuffix(buf, "\r\n")
|
buf = strings.TrimSuffix(buf, "\r\n")
|
||||||
|
|
||||||
rendered += fmt.Sprintf("[%s]", config.GetColorString("preformatted_text")) +
|
if viper.GetBool("a-general.color") {
|
||||||
buf + fmt.Sprintf("[%s:%s:-]\r\n", config.GetColorString("regular_text"), config.GetColorString("bg"))
|
rendered += fmt.Sprintf("[%s]", config.GetColorString("preformatted_text")) +
|
||||||
|
buf + fmt.Sprintf("[%s:%s:-]\r\n", config.GetColorString("regular_text"), config.GetColorString("bg"))
|
||||||
|
} else {
|
||||||
|
rendered += buf + "\r\n"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// processRegular processes non-preformatted sections
|
// processRegular processes non-preformatted sections
|
||||||
@ -336,9 +422,21 @@ func RenderGemini(s string, width int, proxied bool) (string, []string) {
|
|||||||
// Don't add the current line with backticks
|
// Don't add the current line with backticks
|
||||||
processPre()
|
processPre()
|
||||||
|
|
||||||
|
// Clear the language
|
||||||
|
lang = ""
|
||||||
} else {
|
} else {
|
||||||
// Not preformatted, regular text
|
// Not preformatted, regular text
|
||||||
processRegular()
|
processRegular()
|
||||||
|
|
||||||
|
if viper.GetBool("a-general.highlight_code") {
|
||||||
|
// Check for alt text indicating a language that Chroma can highlight
|
||||||
|
alt := strings.TrimSpace(strings.TrimPrefix(lines[i], "```"))
|
||||||
|
if matches := langRegex.FindStringSubmatch(alt); matches != nil {
|
||||||
|
if lexers.Get(matches[0]) != nil {
|
||||||
|
lang = matches[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
buf = "" // Clear buffer for next block
|
buf = "" // Clear buffer for next block
|
||||||
pre = !pre
|
pre = !pre
|
||||||
|
37
rr/README.md
37
rr/README.md
@ -1,37 +0,0 @@
|
|||||||
# package `rr`, aka `RestartReader`
|
|
||||||
|
|
||||||
This package exists just to hold the `RestartReader` type. It wraps `io.ReadCloser` and implements it. It holds the data from every `Read` in a `[]byte` buffer, and allows you to call `.Restart()`, causing subsequent `Read` calls to start from the beginning again.
|
|
||||||
|
|
||||||
See [#140](https://github.com/makeworld-the-better-one/amfora/issues/140) for why this was needed.
|
|
||||||
|
|
||||||
Other projects are encouraged to copy this code if it's useful to them, and this package may move out of Amfora if I end up using it in multiple projects.
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
If you prefer, you can consider the code in this package, and this package only, to be licensed under the MIT license instead. So the code in this package is dual-licensed. You can use the LICENSE file in the root of this repo, or the license text below.
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Click to see MIT license terms</summary>
|
|
||||||
|
|
||||||
```
|
|
||||||
Copyright (c) 2020 makeworld
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
```
|
|
||||||
</details>
|
|
81
rr/rr.go
81
rr/rr.go
@ -1,81 +0,0 @@
|
|||||||
package rr
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
var ErrClosed = errors.New("RestartReader: closed")
|
|
||||||
|
|
||||||
type RestartReader struct {
|
|
||||||
r io.ReadCloser
|
|
||||||
buf []byte
|
|
||||||
|
|
||||||
// Where in the buffer we are. If it's equal to len(buf) then the reader
|
|
||||||
// should be used.
|
|
||||||
i int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rr *RestartReader) Read(p []byte) (n int, err error) {
|
|
||||||
if rr.buf == nil {
|
|
||||||
return 0, ErrClosed
|
|
||||||
}
|
|
||||||
|
|
||||||
if rr.i >= int64(len(rr.buf)) {
|
|
||||||
// Read new data
|
|
||||||
tmp := make([]byte, len(p))
|
|
||||||
n, err = rr.r.Read(tmp)
|
|
||||||
if n > 0 {
|
|
||||||
rr.buf = append(rr.buf, tmp[:n]...)
|
|
||||||
copy(p, tmp[:n])
|
|
||||||
}
|
|
||||||
rr.i = int64(len(rr.buf))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reading from buffer
|
|
||||||
|
|
||||||
bufSize := len(rr.buf[rr.i:])
|
|
||||||
|
|
||||||
if len(p) > bufSize {
|
|
||||||
// It wants more data then what's in the buffer
|
|
||||||
tmp := make([]byte, len(p)-bufSize)
|
|
||||||
n, err = rr.r.Read(tmp)
|
|
||||||
if n > 0 {
|
|
||||||
rr.buf = append(rr.buf, tmp[:n]...)
|
|
||||||
}
|
|
||||||
copy(p, rr.buf[rr.i:])
|
|
||||||
n += bufSize
|
|
||||||
rr.i = int64(len(rr.buf))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// All the required data is in the buffer
|
|
||||||
end := rr.i + int64(len(p))
|
|
||||||
copy(p, rr.buf[rr.i:end])
|
|
||||||
rr.i = end
|
|
||||||
n = len(p)
|
|
||||||
err = nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restart causes subsequent Read calls to read from the beginning, instead
|
|
||||||
// of where they left off.
|
|
||||||
func (rr *RestartReader) Restart() {
|
|
||||||
rr.i = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close clears the buffer and closes the underlying io.ReadCloser, returning
|
|
||||||
// its error.
|
|
||||||
func (rr *RestartReader) Close() error {
|
|
||||||
rr.buf = nil
|
|
||||||
return rr.r.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRestartReader creates and initializes a new RestartReader that reads from
|
|
||||||
// the provided io.ReadCloser.
|
|
||||||
func NewRestartReader(r io.ReadCloser) *RestartReader {
|
|
||||||
return &RestartReader{
|
|
||||||
r: r,
|
|
||||||
buf: make([]byte, 0),
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
package rr
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
var r1 *RestartReader
|
|
||||||
|
|
||||||
func reset() {
|
|
||||||
r1 = NewRestartReader(ioutil.NopCloser(strings.NewReader("1234567890")))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRead(t *testing.T) {
|
|
||||||
reset()
|
|
||||||
p := make([]byte, 1)
|
|
||||||
n, err := r1.Read(p)
|
|
||||||
assert.Equal(t, 1, n, "should read one byte")
|
|
||||||
assert.Equal(t, nil, err, "should be no error")
|
|
||||||
assert.Equal(t, []byte{'1'}, p, "should have read one byte, '1'")
|
|
||||||
}
|
|
||||||
|
|
||||||
//nolint
|
|
||||||
func TestRestart(t *testing.T) {
|
|
||||||
reset()
|
|
||||||
p := make([]byte, 4)
|
|
||||||
r1.Read(p)
|
|
||||||
|
|
||||||
r1.Restart()
|
|
||||||
p = make([]byte, 5)
|
|
||||||
n, err := r1.Read(p)
|
|
||||||
assert.Equal(t, []byte("12345"), p, "should read the first 5 bytes again")
|
|
||||||
assert.Equal(t, 5, n, "should have read 4 bytes")
|
|
||||||
assert.Equal(t, nil, err, "err should be nil")
|
|
||||||
|
|
||||||
r1.Restart()
|
|
||||||
p = make([]byte, 4)
|
|
||||||
n, err = r1.Read(p)
|
|
||||||
assert.Equal(t, []byte("1234"), p, "should read the first 4 bytes again")
|
|
||||||
assert.Equal(t, 4, n, "should have read 4 bytes")
|
|
||||||
assert.Equal(t, nil, err, "err should be nil")
|
|
||||||
}
|
|
@ -251,7 +251,9 @@ func getResource(url string) (string, *gemini.Response, error) {
|
|||||||
return url, nil, err
|
return url, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if res.Status == gemini.StatusSuccess {
|
status := gemini.CleanStatus(res.Status)
|
||||||
|
|
||||||
|
if status == gemini.StatusSuccess {
|
||||||
// No redirects
|
// No redirects
|
||||||
return url, res, nil
|
return url, res, nil
|
||||||
}
|
}
|
||||||
@ -266,8 +268,8 @@ func getResource(url string) (string, *gemini.Response, error) {
|
|||||||
urls := make([]*urlPkg.URL, 0)
|
urls := make([]*urlPkg.URL, 0)
|
||||||
|
|
||||||
// Loop through redirects
|
// Loop through redirects
|
||||||
for (res.Status == gemini.StatusRedirectPermanent || res.Status == gemini.StatusRedirectTemporary) && i < 5 {
|
for (status == gemini.StatusRedirectPermanent || status == gemini.StatusRedirectTemporary) && i < 5 {
|
||||||
redirs = append(redirs, res.Status)
|
redirs = append(redirs, status)
|
||||||
urls = append(urls, parsed)
|
urls = append(urls, parsed)
|
||||||
|
|
||||||
tmp, err := parsed.Parse(res.Meta)
|
tmp, err := parsed.Parse(res.Meta)
|
||||||
@ -302,7 +304,7 @@ func getResource(url string) (string, *gemini.Response, error) {
|
|||||||
if i < 5 {
|
if i < 5 {
|
||||||
// The server stopped redirecting after <5 redirects
|
// The server stopped redirecting after <5 redirects
|
||||||
|
|
||||||
if res.Status == gemini.StatusSuccess {
|
if status == gemini.StatusSuccess {
|
||||||
// It ended by succeeding
|
// It ended by succeeding
|
||||||
|
|
||||||
for j := range redirs {
|
for j := range redirs {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//go:build darwin
|
||||||
// +build darwin
|
// +build darwin
|
||||||
|
|
||||||
package sysopen
|
package sysopen
|
||||||
@ -6,9 +7,12 @@ import "os/exec"
|
|||||||
|
|
||||||
// Open opens `path` in default system viewer.
|
// Open opens `path` in default system viewer.
|
||||||
func Open(path string) (string, error) {
|
func Open(path string) (string, error) {
|
||||||
err := exec.Command("open", path).Start()
|
proc := exec.Command("open", path)
|
||||||
|
err := proc.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
//nolint:errcheck
|
||||||
|
go proc.Wait() // Prevent zombies, see #219
|
||||||
return "Opened in default system viewer", nil
|
return "Opened in default system viewer", nil
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//go:build !linux && !darwin && !windows && !freebsd && !netbsd && !openbsd
|
||||||
// +build !linux,!darwin,!windows,!freebsd,!netbsd,!openbsd
|
// +build !linux,!darwin,!windows,!freebsd,!netbsd,!openbsd
|
||||||
|
|
||||||
package sysopen
|
package sysopen
|
||||||
@ -7,5 +8,5 @@ import "fmt"
|
|||||||
// Open opens `path` in default system viewer, but not on this OS.
|
// Open opens `path` in default system viewer, but not on this OS.
|
||||||
func Open(path string) (string, error) {
|
func Open(path string) (string, error) {
|
||||||
return "", fmt.Errorf("unsupported OS for default system viewer. " +
|
return "", fmt.Errorf("unsupported OS for default system viewer. " +
|
||||||
"Set a catch-all [[mediatype-handlers]] command in the config")
|
"Set a catch-all command in the config")
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//go:build linux || freebsd || netbsd || openbsd
|
||||||
// +build linux freebsd netbsd openbsd
|
// +build linux freebsd netbsd openbsd
|
||||||
|
|
||||||
//nolint:goerr113
|
//nolint:goerr113
|
||||||
@ -20,16 +21,19 @@ func Open(path string) (string, error) {
|
|||||||
switch {
|
switch {
|
||||||
case xorgDisplay == "" && waylandDisplay == "":
|
case xorgDisplay == "" && waylandDisplay == "":
|
||||||
return "", fmt.Errorf("no display server was found. " +
|
return "", fmt.Errorf("no display server was found. " +
|
||||||
"You may set a default [[mediatype-handlers]] command in the config")
|
"You may set a default command in the config")
|
||||||
case xdgOpenNotFoundErr == nil:
|
case xdgOpenNotFoundErr == nil:
|
||||||
// Use start rather than run or output in order
|
// Use start rather than run or output in order
|
||||||
// to make application run in background.
|
// to make application run in background.
|
||||||
if err := exec.Command(xdgOpenPath, path).Start(); err != nil {
|
proc := exec.Command(xdgOpenPath, path)
|
||||||
|
if err := proc.Start(); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
//nolint:errcheck
|
||||||
|
go proc.Wait() // Prevent zombies, see #219
|
||||||
return "Opened in default system viewer", nil
|
return "Opened in default system viewer", nil
|
||||||
default:
|
default:
|
||||||
return "", fmt.Errorf("could not determine default system viewer. " +
|
return "", fmt.Errorf("could not determine default system viewer. " +
|
||||||
"Set a catch-all [[mediatype-handlers]] command in the config")
|
"Set a catch-all command in the config")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//go:build windows && (!linux || !darwin || !freebsd || !netbsd || !openbsd)
|
||||||
// +build windows
|
// +build windows
|
||||||
// +build !linux !darwin !freebsd !netbsd !openbsd
|
// +build !linux !darwin !freebsd !netbsd !openbsd
|
||||||
|
|
||||||
@ -7,9 +8,12 @@ import "os/exec"
|
|||||||
|
|
||||||
// Open opens `path` in default system vierwer.
|
// Open opens `path` in default system vierwer.
|
||||||
func Open(path string) (string, error) {
|
func Open(path string) (string, error) {
|
||||||
err := exec.Command("rundll32", "url.dll,FileProtocolHandler", path).Start()
|
proc := exec.Command("rundll32", "url.dll,FileProtocolHandler", path)
|
||||||
|
err := proc.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
//nolint:errcheck
|
||||||
|
go proc.Wait() // Prevent zombies, see #219
|
||||||
return "Opened in default system viewer", nil
|
return "Opened in default system viewer", nil
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//go:build darwin
|
||||||
// +build darwin
|
// +build darwin
|
||||||
|
|
||||||
package webbrowser
|
package webbrowser
|
||||||
@ -6,9 +7,12 @@ import "os/exec"
|
|||||||
|
|
||||||
// Open opens `url` in default system browser.
|
// Open opens `url` in default system browser.
|
||||||
func Open(url string) (string, error) {
|
func Open(url string) (string, error) {
|
||||||
err := exec.Command("open", url).Start()
|
proc := exec.Command("open", url)
|
||||||
|
err := proc.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
//nolint:errcheck
|
||||||
|
go proc.Wait() // Prevent zombies, see #219
|
||||||
return "Opened in system default web browser", nil
|
return "Opened in system default web browser", nil
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//go:build !linux && !darwin && !windows && !freebsd && !netbsd && !openbsd
|
||||||
// +build !linux,!darwin,!windows,!freebsd,!netbsd,!openbsd
|
// +build !linux,!darwin,!windows,!freebsd,!netbsd,!openbsd
|
||||||
|
|
||||||
package webbrowser
|
package webbrowser
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//go:build linux || freebsd || netbsd || openbsd
|
||||||
// +build linux freebsd netbsd openbsd
|
// +build linux freebsd netbsd openbsd
|
||||||
|
|
||||||
//nolint:goerr113
|
//nolint:goerr113
|
||||||
@ -33,14 +34,20 @@ func Open(url string) (string, error) {
|
|||||||
case xdgOpenNotFoundErr == nil: // Prefer xdg-open over $BROWSER
|
case xdgOpenNotFoundErr == nil: // Prefer xdg-open over $BROWSER
|
||||||
// Use start rather than run or output in order
|
// Use start rather than run or output in order
|
||||||
// to make browser running in background.
|
// to make browser running in background.
|
||||||
if err := exec.Command(xdgOpenPath, url).Start(); err != nil {
|
proc := exec.Command(xdgOpenPath, url)
|
||||||
|
if err := proc.Start(); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
//nolint:errcheck
|
||||||
|
go proc.Wait() // Prevent zombies, see #219
|
||||||
return "Opened in system default web browser", nil
|
return "Opened in system default web browser", nil
|
||||||
case envBrowser != "":
|
case envBrowser != "":
|
||||||
if err := exec.Command(envBrowser, url).Start(); err != nil {
|
proc := exec.Command(envBrowser, url)
|
||||||
|
if err := proc.Start(); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
//nolint:errcheck
|
||||||
|
go proc.Wait() // Prevent zombies, see #219
|
||||||
return "Opened in system default web browser", nil
|
return "Opened in system default web browser", nil
|
||||||
default:
|
default:
|
||||||
return "", fmt.Errorf("could not determine system browser")
|
return "", fmt.Errorf("could not determine system browser")
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//go:build windows && (!linux || !darwin || !freebsd || !netbsd || !openbsd)
|
||||||
// +build windows
|
// +build windows
|
||||||
// +build !linux !darwin !freebsd !netbsd !openbsd
|
// +build !linux !darwin !freebsd !netbsd !openbsd
|
||||||
|
|
||||||
@ -7,9 +8,12 @@ import "os/exec"
|
|||||||
|
|
||||||
// Open opens `url` in default system browser.
|
// Open opens `url` in default system browser.
|
||||||
func Open(url string) (string, error) {
|
func Open(url string) (string, error) {
|
||||||
err := exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
|
proc := exec.Command("rundll32", "url.dll,FileProtocolHandler", url)
|
||||||
|
err := proc.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
//nolint:errcheck
|
||||||
|
go proc.Wait() // Prevent zombies, see #219
|
||||||
return "Opened in system default web browser", nil
|
return "Opened in system default web browser", nil
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user