Compare commits

...

171 Commits

Author SHA1 Message Date
Sloom Sloum Sluom IV b171dc2230 Merge pull request 'Develop -> Master patch version push' (#222) from develop into master
Reviewed-on: #222
2022-10-08 20:47:11 +00:00
Sloom Sloum Sluom IV 3cbf52fe6d Merge pull request 'Adds license info and copyright statement to each source file' (#221) from licenses into develop
Reviewed-on: #221
2022-10-08 20:46:17 +00:00
sloum 38e7ccea52 Adds license info and copyright statement to each source file 2022-10-08 20:43:47 +00:00
Sloom Sloum Sluom IV 30e550c183 Merge pull request 'Release to master 2.4.0' (#217) from develop into master
Reviewed-on: #217
2022-03-06 21:50:42 +00:00
Sloom Sloum Sluom IV df99584222 Merge pull request 'release-2.4.0' (#215) from release-2.4.0 into develop
Reviewed-on: #215
2022-03-06 21:48:38 +00:00
sloum 93cf4321d4 Last minute update to add information about the maxwidth setting to the manpage 2022-02-15 21:19:22 -08:00
Sloom Sloum Sluom IV 5a91f4ac94 Merge pull request 'Better word wrapping' (#214) from raidancampbell/bombadillo:master into release-2.4.0
Reviewed-on: #214
2022-02-16 04:03:48 +00:00
Sloom Sloum Sluom IV 2a254a42ff Merge pull request 'Adds UP hot key' (#213) from up-dir into release-2.4.0
Reviewed-on: #213
2022-02-10 23:59:51 +00:00
R. Aidan Campbell 1bef2e51a1
when wrapping, omit leading whitespace created if the wrap occurs at a space char 2022-02-08 20:47:16 -07:00
Sloom Sloum Sluom IV ceb83eb5e3 Merge branch 'release-2.4.0' into up-dir 2022-02-08 18:26:35 +00:00
R. Aidan Campbell 070f7eb6ba
extract logic for reading maxwidth config into a function: now safely handles fallbacks for missing or malformed configuration 2022-02-07 21:54:12 -07:00
R. Aidan Campbell dac13e1669
soft wrap between words 2022-02-06 22:50:25 -07:00
R. Aidan Campbell 984fb4eb2f
max width for softwraping now configured via maxwidth configuration item: default or invalid value falls back to previous 100 column 2022-02-06 22:08:19 -07:00
Sloom Sloum Sluom IV 59321b8977 Merge pull request 'Don't use GNU-specific gzip -k flag' (#212) from jaywilliams/bombadillo:master into release-2.4.0
Reviewed-on: #212

Looks good. Merging into release now. Thanks!
2021-05-24 05:24:28 +00:00
Jay Williams 06a47e9c3d Merge branch 'release-2.4.0' into master 2021-05-18 12:44:15 +00:00
Jay Williams 80aabcd531 Don't use GNU-specific gzip -k flag 2021-05-14 12:18:04 -05:00
sloum d747249069 Adds UP hot key 2021-04-24 14:26:33 -07:00
Sloom Sloum Sluom IV 757305db66 Merge pull request 'Release 2.3.3 to master' (#204) from develop into master
Reviewed-on: #204
2020-11-20 04:38:12 +00:00
sloum ec61d49497 Merge branch 'master' of https://tildegit.org/sloum/Bombadillo into develop 2020-11-19 20:37:32 -08:00
Sloom Sloum Sluom IV a8456f7e98 Merge pull request 'release2.3.3 -> develop' (#203) from release2.3.3 into develop
Reviewed-on: #203
2020-11-20 04:26:34 +00:00
sloum 8796dffd6e Minor adjustment to help output for jump command 2020-11-19 20:09:53 -08:00
Sloom Sloum Sluom IV a6a324acb3 Merge pull request 'Adds a jump command' (#202) from over-command into release2.3.3
Reviewed-on: #202
2020-11-20 04:07:15 +00:00
sloum 4de473441a Solves merge conflicts with base and updates man page language 2020-11-19 20:06:22 -08:00
Sloom Sloum Sluom IV b8ae9d659c Merge pull request 'Adds version command' (#198) from version-command into release2.3.3
Reviewed-on: #198
2020-11-20 04:02:19 +00:00
sloum 7d722d7dfa Adds a jump command to allow for navigating by history location as an alternate to a tabbed workflow 2020-11-14 15:40:18 -08:00
sloum f18dc293fd Removes previous version build system in favor of simple variable 2020-11-12 20:51:21 -08:00
sloum ee56961b20 Fixes a regression where searches did not clear out previous search highlights 2020-11-12 20:24:19 -08:00
sloum d78e2f4205 Adds further fixes to the man page. I had noticed some issues. 2020-11-08 10:30:22 -08:00
sloum 7219f6cae1 Merge branch 'version-command' of https://tildegit.org/sloum/Bombadillo into version-command 2020-11-05 19:34:16 -08:00
sloum 1ae3312ad6 Handles the case where no version information is available 2020-11-05 19:33:54 -08:00
Sloom Sloum Sluom IV 20b92fdc02 Merge branch 'release2.3.3' into version-command 2020-11-06 03:28:49 +00:00
Sloom Sloum Sluom IV ebcd1ff1d7 Merge pull request 'Minor fix to how we verify hostnames' (#197) from tls-commonname-check into release2.3.3
Reviewed-on: #197
2020-11-06 03:28:31 +00:00
Sloom Sloum Sluom IV 14340034f6 Merge branch 'release2.3.3' into tls-commonname-check 2020-11-06 03:27:53 +00:00
asdf 8c838ce491 Merge pull request 'WIP Initial support for some unicode line endings' (#200) from unicode-line-endings into release2.3.3
Reviewed-on: #200
2020-11-06 02:34:08 +00:00
sloum ed084298c7 Fixes merge conflicts with base branch 2020-11-05 07:48:42 -08:00
sloum 82d0742d52 Adds update to man page 2020-11-05 07:47:08 -08:00
Sloom Sloum Sluom IV 33e78753ea Merge branch 'release2.3.3' into unicode-line-endings 2020-11-05 15:44:50 +00:00
asdf 388218a5b6 Initial support for some unicode line endings 2020-11-05 21:28:54 +11:00
Sloom Sloum Sluom IV ddb73018d9 Merge pull request 'Solves the lynx lack of 404 issue' (#196) from lynx404 into release2.3.3
Reviewed-on: #196
2020-11-02 02:55:29 +00:00
Sloom Sloum Sluom IV b48126d971 Merge branch 'release2.3.3' into lynx404 2020-11-02 02:53:45 +00:00
Sloom Sloum Sluom IV a6375ecc21 Merge pull request 'improve-command-error-messages' (#195) from improve-command-error-messages into release2.3.3
Reviewed-on: #195
2020-11-02 02:53:23 +00:00
Sloom Sloum Sluom IV 49ba8313c7 Merge branch 'release2.3.3' into improve-command-error-messages 2020-11-02 02:53:15 +00:00
sloum 228ec598ab Adds version command 2020-11-01 06:55:46 -08:00
sloum 3624fd9510 Minor fix to how we verify hostnames 2020-11-01 06:45:11 -08:00
Sloom Sloum Sluom IV 7136a49fbe Merge branch 'release2.3.3' into lynx404 2020-11-01 14:41:43 +00:00
Sloom Sloum Sluom IV d190e0ad00 Merge pull request 'Adds spacing to gemini docs to normalize the formatting with gopher docs' (#188) from gemini-gutter into release2.3.3
Reviewed-on: #188

Merging in gemini and gopher link number gutter updates. This will create a more unified look between the protocols.
2020-11-01 14:09:25 +00:00
sloum 4f166ca61a Adds a prefix string to help output 2020-11-01 06:01:17 -08:00
asdf 53de1b5662 Minor correction to help text 2020-11-01 21:09:41 +11:00
Sloom Sloum Sluom IV 5581419489 Merge branch 'release2.3.3' into improve-command-error-messages 2020-11-01 05:18:47 +00:00
sloum c9a7e0ff25 Merge branch 'improve-command-error-messages' of https://tildegit.org/sloum/Bombadillo into improve-command-error-messages 2020-10-31 22:05:54 -07:00
sloum 89095351a8 Fixes help to take an optional action as a value to get syntax for a given command 2020-10-31 22:05:27 -07:00
sloum a3cb016449 Brings gopher link numbering style inline with the other protocols 2020-10-31 21:44:45 -07:00
Sloom Sloum Sluom IV 8789b4d6af Merge branch 'release2.3.3' into gemini-gutter 2020-11-01 04:39:05 +00:00
sloum 2270dee513 Uses asdf's suggestion to reduce HasPrefix overhead by setting the spacer at the beginning of the document wrap code 2020-10-24 10:10:06 -07:00
Sloom Sloum Sluom IV d2e238baac Merge pull request 'Improved command error messages' (#189) from improve-command-error-messages into release2.3.3
Reviewed-on: #189
2020-10-24 16:42:19 +00:00
Sloom Sloum Sluom IV 7ef32afd4a Merge branch 'release2.3.3' into improve-command-error-messages 2020-10-24 16:42:07 +00:00
sloum bafa5a0739 Solves the lynx lack of 404 issue 2020-09-17 21:24:16 -07:00
asdf 7dc68a18bf Corrected several issues identified during review 2020-09-14 06:17:54 +10:00
asdf 86ba01b1d9 Updated branch, reduced to implementing improved error messages 2020-09-11 14:30:17 +10:00
asdf 568de7d3b3 Merge branch 'develop' into improve-command-error-messages 2020-09-10 14:49:00 +10:00
Sloom Sloum Sluom IV 7d326eaba6 Updates gitinore to ignore swap files 2020-09-08 21:37:01 -07:00
Sloom Sloum Sluom IV 92c04fed7f Merge branch 'gemini-gutter' of https://tildegit.org/sloum/bombadillo into gemini-gutter 2020-09-08 21:36:23 -07:00
Sloom Sloum Sluom IV 36b256da07 Removes swap files 2020-09-08 21:35:59 -07:00
Sloom Sloum Sluom IV 824d8bbf81 Merge branch 'release2.3.3' into gemini-gutter 2020-09-09 04:34:07 +00:00
Sloom Sloum Sluom IV 01d071e510 Adds spacing to gemini docs to normalize the formatting with gopher docs 2020-09-08 21:20:34 -07:00
Sloom Sloum Sluom IV 51eade565f Merge pull request 'Update development documentation to reflect the current release process' (#187) from update-release-process-documentation into release2.3.3
Reviewed-on: #187
2020-09-09 02:25:48 +00:00
asdf ad9b5f567e Merge branch 'release2.3.3' into update-release-process-documentation 2020-09-08 05:21:08 +00:00
asdf 3031536a0c Updated to reflect the current release process 2020-09-08 15:10:52 +10:00
Sloom Sloum Sluom IV 26a5e3869d Merge pull request 'Handle input errors while returning to correct terminal mode' (#186) from handle_input_errors into release2.3.3
Reviewed-on: #186
2020-09-08 04:18:07 +00:00
asdf 2f8eedded7 Handle input errors while returning to correct terminal mode 2020-09-08 13:41:52 +10:00
Sloom Sloum Sluom IV 97b74ea767 Merge pull request 'Release 2.3.2 into develop' (#185) from release2.3.2 into develop
Reviewed-on: #185
2020-08-08 21:31:59 +00:00
Sloom Sloum Sluom IV 7f3705fba6 Adds more help documentation 2020-07-16 22:05:37 -07:00
Sloom Sloum Sluom IV 0e2b80626e Merge pull request 'Removes support for client certificates in Bombadillo' (#181) from remove-client-certs into release2.3.2
Reviewed-on: #181
2020-07-10 00:46:48 -04:00
Sloom Sloum Sluom IV 99fb1ce20b Solves merge conflicts with target 2020-07-09 21:45:58 -07:00
Sloom Sloum Sluom IV a346d78c7b Merge pull request 'Updates `handleGemini` to allow for handling relative redirects' (#182) from fixes-relative-redirects into release2.3.2
Reviewed-on: #182
2020-07-10 00:38:28 -04:00
Sloom Sloum Sluom IV 44dc3f5029 Merge branch 'release2.3.2' into fixes-relative-redirects 2020-07-10 00:38:17 -04:00
Sloom Sloum Sluom IV f8a9465eaf Merge pull request 'Uses 'linux' OpenInBrowser settings for any non-darwin and non-windows os' (#180) from fixes-browser-opening-for-bsd into release2.3.2
Reviewed-on: #180
2020-07-10 00:37:33 -04:00
Sloom Sloum Sluom IV 6308d3e761 Merge branch 'release2.3.2' into fixes-browser-opening-for-bsd 2020-07-10 00:37:18 -04:00
Sloom Sloum Sluom IV 3dcd0c5e13 Merge pull request 'Adds user configurable timeout to gopher and gemini connections (as single value)' (#178) from timeout-setting into release2.3.2
Reviewed-on: #178
2020-07-10 00:37:01 -04:00
Sloom Sloum Sluom IV 3d85b43732 Merge branch 'release2.3.2' into timeout-setting 2020-07-10 00:36:34 -04:00
Sloom Sloum Sluom IV 688a0b3ef2 Merge branch 'release2.3.2' into remove-client-certs 2020-07-03 18:04:39 -04:00
sloum 328d16a191 Removes additional cert references and improves error messaging 2020-07-03 15:04:16 -07:00
Sloom Sloum Sluom IV 953ae8e28f Merge pull request 'Adds default mime per gemini spec' (#183) from mime-fix into release2.3.2
Reviewed-on: #183
2020-07-03 17:59:00 -04:00
sloum df4f09a6bb Adds default mime per gemini spec 2020-07-03 14:56:12 -07:00
Sloom Sloum Sluom IV d7a65e6679 Allows for relative redirects 2020-07-02 14:47:38 -07:00
Sloom Sloum Sluom IV b6207c7b96 Merge branch 'release2.3.2' of https://tildegit.org/sloum/bombadillo into remove-client-certs 2020-07-02 11:51:37 -07:00
Sloom Sloum Sluom IV 21e87706aa Removes gemini client certificate support from Bombadillo 2020-07-02 11:51:07 -07:00
Sloom Sloum Sluom IV 51cdb686f6 Adds more help pages 2020-07-02 11:37:00 -07:00
Sloom Sloum Sluom IV e3a0292638 Removes unneeded build tags from darwin and windows files 2020-07-02 10:19:53 -07:00
Sloom Sloum Sluom IV 4ac6c8c2e5 Removes unneeded file and simplifies gui mode opening 2020-07-02 10:08:36 -07:00
Sloom Sloum Sluom IV df3b03c661 Merge branch 'release2.3.2' into timeout-setting 2020-07-01 22:39:01 -04:00
Sloom Sloum Sluom IV 6b2ccc5ad4 Merge pull request 'Line wrap correction' (#179) from line-wrap-correction into release2.3.2
This is approved/merging in now :) For historical conversation on this PR please see: #175
2020-07-01 22:38:37 -04:00
Sloom Sloum Sluom IV baf7932ab5 Merge branch 'release2.3.2' into line-wrap-correction 2020-07-01 22:37:23 -04:00
Sloom Sloum Sluom IV 81457d8406 increments version for release 2020-06-29 22:21:43 -07:00
Sloom Sloum Sluom IV 185c62f272 Adds timeout config option 2020-06-29 22:19:14 -07:00
Sloom Sloum Sluom IV 80bdbb642d Adds timeout to tls for gemini and increases gopher timeout 2020-06-29 21:45:24 -07:00
sloum 55a31fa8df Adds more help documentation 2020-06-29 21:18:20 -07:00
Sloom Sloum Sluom IV 8baf53d32b Adds more help sections 2020-06-23 20:31:32 -07:00
asdf d2c8af2a08 Page content now renders to the entire terminal width 2020-06-23 14:56:10 +10:00
Sloom Sloum Sluom IV 8b7e52bd5a Adds help files to Makefile and adds more help content 2020-06-21 21:04:40 -07:00
Sloom Sloum Sluom IV 975a3e34fd Adds more help files 2020-06-21 15:53:31 -07:00
Sloom Sloum Sluom IV 3ffd878ad6 Working help requests! 2020-06-21 10:16:05 -07:00
Sloom Sloum Sluom IV 4a297490c1 Adds better error messaging for commands and starts the help documentation 2020-06-20 22:33:59 -07:00
asdf 57b3afd4d7 Add supporting unit tests for WrapContent 2020-06-21 14:58:51 +10:00
asdf 980f236d84 Corrects wrapped lines to ensure they fit the required terminal width 2020-06-21 14:24:12 +10:00
Sloom Sloum Sluom IV abc6a170dc Merge pull request 'Develop -> Master' (#172) from develop into master 2020-05-30 12:52:48 -04:00
Sloom Sloum Sluom IV bc38cb8fb5 Merge pull request 'Release 2.3.1 -> Develop' (#171) from release-2.3.1 into develop 2020-05-30 12:51:03 -04:00
Sloom Sloum Sluom IV 3af583da9b Merge branch 'release-2.3.1' of https://tildegit.org/sloum/bombadillo into release-2.3.1 2020-05-28 22:31:39 -07:00
Sloom Sloum Sluom IV 573eb0d230 Fixes issue in makefile that made it so VERSION file was never referenced 2020-05-28 22:30:48 -07:00
Sloom Sloum Sluom IV f683845136 Merge pull request 'Adds a release target to makefile' (#170) from make-release into release-2.3.1
Merging in since there is no code to the application here and none of the core makefile targets are altered.
2020-05-28 22:34:30 -04:00
Sloom Sloum Sluom IV d7a08268b0 Updates version info 2020-05-27 22:39:44 -07:00
Sloom Sloum Sluom IV 733893edc0 Adds prerequisit to readme 2020-05-27 22:31:23 -07:00
Sloom Sloum Sluom IV 15d67ef230 Adds a make target for release and updates clean to clean it up 2020-05-27 22:29:41 -07:00
Sloom Sloum Sluom IV b8721366a2 Merge pull request 'Alternative way of rendering when bookmarks are not open' (#167) from rendering-fix into release-2.3.1
Since this has been reviewed as functional I am merging into the release branch. There will be another opportunity to review, if need be, for the release branch when a PR is made into `develop`.
2020-05-28 00:50:47 -04:00
Sloom Sloum Sluom IV 7a0506853a Merge pull request 'Fixes gemini relative links to work for all relative link types' (#168) from fix-relative-links into release-2.3.1
Since this has been reviewed as functional I am merging into the release branch. There will be another opportunity to review, if need be, for the release branch when a PR is made into `develop`.
2020-05-28 00:50:13 -04:00
Sloom Sloum Sluom IV 38144a0e2a Merge pull request 'Adds in the correct variable when checking for existing querystring value' (#166) from fix-repeated-query into release-2.3.1
Since this has been reviewed as functional I am merging into the release branch. There will be another opportunity to review, if need be, for the release branch when a PR is made into `develop`.
2020-05-28 00:49:28 -04:00
Sloom Sloum Sluom IV c175b45cb9 Switches relative URLs to RelativeReference lib method 2020-05-26 21:46:17 -07:00
sloum cba3682c27 Adds up one dir relative linking fix 2020-05-26 15:10:12 -07:00
sloum 0227523487 Adds query escaping 2020-05-25 19:43:19 -07:00
sloum f47f6597f4 Adds in the correct variable when checking for existing querystring value 2020-05-25 19:12:52 -07:00
sloum c9d634499b Alternative way of rendering when bookmarks are not open 2020-05-25 09:49:34 -07:00
Sloom Sloum Sluom IV e06de9bac1 Merge pull request 'Release of v.2.3.0 to master' (#160) from develop into master 2020-05-24 12:47:34 -04:00
Sloom Sloum Sluom IV f793bdd806 Merge pull request 'Release candidate for 2.3.0' (#147) from release-2.3.0 into develop
Alright. This is a big one for Gemini. I am going to merge in. :)
2020-05-24 12:42:27 -04:00
Sloom Sloum Sluom IV 47863519a2 Merge pull request 'Fixes querystring chaining issue' (#159) from querystring into release-2.3.0 2020-05-24 12:40:30 -04:00
sloum 90cf1d6681 Resolves merge conflict 2020-05-24 09:39:29 -07:00
sloum 0d8b3e015b Fixes querystring chaining issue 2020-05-24 07:45:34 -07:00
Sloom Sloum Sluom IV cf340edc36 Merge pull request 'Replace calls to `stty` with syscalls' (#158) from dancek/bombadillo:replace-stty into release-2.3.0 2020-05-24 10:23:17 -04:00
Hannu Hartikainen e3e2afc4fc Replace calls to `stty` with syscalls
- Move termios-related functionality to its own package
- Reimplement stty calls using ioctl calls
- Add per-platform constants for linux and non-linux because the ioctl
  enums are different. The enums in Darwin seem to come from BSD and so
  I'm assuming they might also work for other operating systems. If not,
  we'll need to add other build-constrained const files.

This code has been tested on Darwin and will be tested on Linux before
merging.
2020-05-24 09:25:38 +03:00
Sloom Sloum Sluom IV ba38b78ca6 Merge pull request 'Adds option to handle preformatted code blocks in different ways for gemini' (#148) from gemini-alt-text into release-2.3.0 2020-05-22 17:49:58 -04:00
sloum 0257ca92b1 Adds clarification to man page and removes old comment 2020-05-19 14:40:56 -07:00
Sloom Sloum Sluom IV c34226e4c1 Merge pull request 'Reworks how relative URLs are handled for gemini' (#153) from pathing-fixes into release-2.3.0 2020-05-19 10:36:48 -04:00
sloum 64590f53e7 Resolved merge conflict 2020-05-18 20:14:32 -07:00
sloum b9057508d9 Reworks how relative URLs are handled for gemini 2020-05-18 20:10:02 -07:00
sloum 9f1eb632bc Adds geminiblocks to the man page 2020-05-17 22:01:31 -07:00
sloum b23b9b3121 Fixes the workflow for allowing alt text and handling preformatted blocks 2020-05-17 21:36:49 -07:00
sloum 909131cda8 Lets images be full width 2020-05-15 22:25:56 -07:00
sloum 2bb8272cf9 Combines a few features into a release branch 2020-05-15 22:22:32 -07:00
sloum c508498b42 Merge branch 'gemini-cert-expiry' of https://tildegit.org/sloum/Bombadillo into release-2.3.0 2020-05-15 22:21:38 -07:00
sloum 322002ba66 Adds a max width of 100, anything more gets weird to read. Also adds a slight optimization. 2020-05-15 22:19:09 -07:00
sloum a23e0026fa Adds command aliasing to vim keys for forward and back 2020-05-15 17:32:08 -07:00
sloum c58b40def2 Removes goroutines in search that were causing input issues on gemini cgi scripts 2020-05-14 08:28:39 -07:00
sloum 00313442d4 Hopefully an improvement to the initial way of dealing with expired certs 2020-05-09 16:18:38 -07:00
sloum cb151f75aa Handles cert expirations silently 2020-05-09 11:04:06 -07:00
Sloom Sloum Sluom IV 36ae4a228f Merge pull request 'Release setup for 2.2.1' (#141) from release-setup-2.2.1 into develop 2020-04-23 13:44:46 -04:00
sloum 961bdfc92f Minor release with improved term handling and quick link navigation 2020-04-23 10:41:32 -07:00
sloum 26b3223379 Merge branch 'short-links' of https://tildegit.org/sloum/Bombadillo into release-setup-2.2.1 2020-04-23 10:40:26 -07:00
sloum cdfec887fd Solves the issue where line wrapping is not turned back on 2020-04-14 18:26:03 +00:00
sloum bfb6b85844 Updates man page with quick link information 2020-04-12 22:57:50 -07:00
sloum 9af1a4d642 Adds quick-link navigation by number keys 2020-04-12 22:53:08 -07:00
Sloom Sloum Sluom IV 61ae2859bf Merge pull request 'Release 2.2.0' (#140) from develop into master 2020-03-23 01:29:16 -04:00
Sloom Sloum Sluom IV 23bc3d75a5 Merge pull request 'Release 2.1.4' (#139) from release214 into develop 2020-03-23 01:27:58 -04:00
sloum 8b7441ea17 Updated version 2020-03-22 22:27:38 -07:00
Sloom Sloum Sluom IV b2d6e2ff1e Merge pull request 'Adds support for gemini tripple backticks' (#138) from gemini-spec-update into release214 2020-03-05 00:55:09 -05:00
sloum 021f1290d4 Adds support for gemini tripple backticks 2020-03-04 21:51:54 -08:00
Sloom Sloum Sluom IV ca552bf383 Merge pull request 'Adds support for image rendering in the terminal' (#137) from render-images into release214 2020-03-05 00:33:35 -05:00
sloum cd1be92d34 Changing default to true 2020-03-04 21:27:21 -08:00
sloum 7edf01eb99 Adds a color property to the page struct to track the color mode 2020-02-15 10:51:39 -08:00
sloum 4f9c8877b5 Removes unneeded comment 2020-02-15 10:44:41 -08:00
sloum cda502fbb8 Adds a WrapWidth param to the page struct so that pages.go does not have to rewrap on every render. 2020-02-15 08:43:54 -08:00
sloum 996e209c50 Leaves a note re: improving render speeds 2020-02-13 22:46:39 -08:00
sloum c9765bf0fa Adds lo-fi image rendering to Bombadillo 2020-02-13 21:26:23 -08:00
sloum 53cbb9dd02 Improves upon previoius fixes to gophermap parsing 2020-02-01 09:55:55 -08:00
Sloom Sloum Sluom IV d17e530667 Merge branch 'fix-gopher-crash' of sloum/bombadillo into develop 2020-01-30 22:47:28 -05:00
sloum 9e68f383a0 Updates version number 2020-01-30 19:42:45 -08:00
sloum 8444784221 Fixes bug that causes crash on broken gophermaps 2020-01-30 19:41:55 -08:00
Sloom Sloum Sluom IV 6af3455da8 Merge branch 'release-process-information' of sloum/bombadillo into develop 2020-01-23 00:46:24 -05:00
asdf 3cf27b82ad Further review of release information 2020-01-23 13:48:38 +11:00
asdf 6720b62276 Added information regarding the VERSION file 2020-01-08 14:57:38 +11:00
asdf 2b231184d8 Added DEVELOPING.md to include versioning process steps 2020-01-08 14:33:49 +11:00
Sloom Sloum Sluom IV f88b2c41b0 Merge branch 'master' of sloum/bombadillo into develop 2020-01-04 15:58:34 -05:00
sloumdrone 77fe67525e Fixing go 1.11 compatibility issue in strings lib w replaceAll vs replace 2020-01-04 12:48:15 -08:00
36 changed files with 1564 additions and 284 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
bombadillo
*.asciinema
*.swp

64
DEVELOPING.md Normal file
View File

@ -0,0 +1,64 @@
# Developing Bombadillo
## Getting Started
Following the standard install instructions should lead you to have nearly everything you need to commence development. The only additions to this are:
- To be able to submit pull requests, you will need to fork this repository first.
- The build process must be tested with Go 1.11 to ensure backward compatibility. This version can be installed as per the [Go install documentation](https://golang.org/doc/install#extra_versions). Check that changes build with this version using `make test`.
- Linting must be performed on new changes using `gofmt` and [golangci-lint](https://github.com/golangci/golangci-lint)
## How changes are made
A stable version of Bombadillo is kept in the default branch, so that people can easily clone the repo and get a good version of the software.
New changes are implemented to the **develop** branch as **development releases**.
Changes are implemented to the default branch when:
- There are a set of changes in **develop** that are good enough to be considered stable.
- This may be a **minor** set of changes for a **minor release**, or
- a large **major** change for **major release**.
- An urgent issue is identified in the stable version that requires an immediate **patch release**.
### Process for introducing a new change
Before you begin, please refer to our [notes on contributing](README.md#contributing) to get an understanding of how new changes are initiated, the type of changes accepted and the review process.
1. Create a new feature branch based on the **develop** branch.
1. Raise a pull request (PR) targeting the current release branch (confirm this in the issue comments before proceeding).
1. The PR is reviewed.
1. If the PR is approved, it is merged.
1. The version number is incremented, along with any other release activity.
### Process for incrementing the version number
The version number is incremented during a **development release**, **patch release**, and **minor** and **major releases**. This is primarily managed through git tags in the following way:
```shell
# switch to the branch the release is being performed for
git checkout branch
# ensure everything is up to date
git pull
# get the commit ID for the recent merge
git log
# get the current version number (the highest number)
git tag
# for a development release, add the incremented version number to the commit-id, for example:
git tag 2.0.2 abcdef
# for releases to the default branch, this tag can also be added with annotations
git tag 2.1.0 abdef -a "This version adds several new features..."
```
Releases to the default branch also include the following tasks:
1. The version number in the VERSION file is incremented and committed.
1. Release information should also be verified on the [tildegit releases page](https://tildegit.org/sloum/bombadillo/releases).

View File

@ -13,24 +13,16 @@ test : GOCMD := go1.11.13
# %:z - so settle for %z.
BUILD_TIME := ${shell date "+%Y-%m-%dT%H:%M%z"}
# If VERSION is empty or not defined use the contents of the VERSION file
VERSION := ${shell git describe --tags 2> /dev/null}
ifndef VERSION
VERSION := ${shell cat ./VERSION}
endif
LDFLAGS := -ldflags "-s -X main.version=${VERSION} -X main.build=${BUILD_TIME}"
.PHONY: build
build:
${GOCMD} build ${LDFLAGS} -o ${BINARY}
${GOCMD} build -o ${BINARY}
.PHONY: install
install: install-bin install-man install-desktop clean
.PHONY: install-man
install-man: bombadillo.1
gzip -k ./bombadillo.1
gzip -c ./bombadillo.1 > ./bombadillo.1.gz
install -d ${DESTDIR}${MAN1DIR}
install -m 0644 ./bombadillo.1.gz ${DESTDIR}${MAN1DIR}
@ -57,6 +49,7 @@ install-bin: build
clean:
${GOCMD} clean
rm -f ./bombadillo.1.gz 2> /dev/null
rm -f ./${BINARY}_* 2> /dev/null
.PHONY: uninstall
uninstall: clean
@ -66,6 +59,13 @@ uninstall: clean
rm -f ${DESTDIR}${DATAROOTDIR}/pixmaps/bombadillo-icon.png
-update-desktop-database 2> /dev/null
.PHONY: release
release:
GOOS=linux GOARCH=amd64 ${GOCMD} build ${LDFLAGS} -o ${BINARY}_linux_64
GOOS=linux GOARCH=arm ${GOCMD} build ${LDFLAGS} -o ${BINARY}_linux_arm
GOOS=linux GOARCH=386 ${GOCMD} build ${LDFLAGS} -o ${BINARY}_linux_32
GOOS=darwin GOARCH=amd64 ${GOCMD} build ${LDFLAGS} -o ${BINARY}_darwin_64
.PHONY: test
test: clean build

View File

@ -27,6 +27,7 @@ These instructions will get a copy of the project up and running on your local m
### Prerequisites
You will need to have [Go](https://golang.org/) version >= 1.11.
To use the Makefile you will need a make that is GNU Make compatible (sorry BSD folks)
### Building, Installing, Uninstalling
@ -127,11 +128,7 @@ The maintainers use the [tildegit](https://tildegit.org) issues system to discus
## Development
Following the standard install instructions should lead you to have nearly everything you need to commence development. The only additions to this are:
- To be able to submit pull requests, you will need to fork this repository first.
- The build process must be tested with Go 1.11 to ensure backward compatibility. This version can be installed as per the [Go install documentation](https://golang.org/doc/install#extra_versions). Check that changes build with this version using `make test`.
- Linting must be performed on new changes using `gofmt` and [golangci-lint](https://github.com/golangci/golangci-lint)
See [DEVELOPING.md](DEVELOPING.md) for information on how changes to Bombadillo are made, along with other technical information for developers.
## License

View File

@ -1 +1 @@
2.1.0
2.3.3

View File

@ -33,7 +33,7 @@ Gopher is the default protocol for \fBbombadillo\fP. Any textual item types will
.TP
.B
gemini
Gemini is supported, but as a new protocol with an incomplete specification, features may change over time. At present Bombadillo supports TLS with a trust on first use certificate pinning system (similar to SSH). Client certificates are also supported as a configurable option. Gemini maps and other text types are rendered in the browser and non-text types will be downloaded.
Gemini is supported, but as a new protocol with an incomplete specification, features may change over time. At present Bombadillo supports TLS with a trust on first use certificate pinning system (similar to SSH). Gemini maps and other text types are rendered in the browser and non-text types will be downloaded.
.TP
.B
finger
@ -59,7 +59,7 @@ Displaying web content directly in \fBbombadillo\fP requires lynx, w3m or elinks
These commands work as a single keypress anytime \fBbombadillo\fP is not taking in a line based command or when the user is being prompted for action. This is the default command mode of \fBbombadillo\fP.
.TP
.B
b
b, h
Navigate back one place in your document history.
.TP
.B
@ -71,7 +71,7 @@ d
Scroll down an amount corresponding to 75% of your terminal window height in the current document.
.TP
.B
f
f, l
Navigate forward one place in your document history.
.TP
.B
@ -95,6 +95,7 @@ n
Jump to next found text item.
.TP
.B
N
Jump to previous found text item.
.TP
.B
@ -106,6 +107,14 @@ R
Reload the current page (does not destroy forward history).
.TP
.B
1, 2, 3, 4, 5, 6, 7, 8, 9, 0
Quick navigation to the first 10 links on a page. The 0 key will navigate to the link numbered '10', all other numbers navigate to their matching link number.
.TP
.B
U
Move up a level in the current url path. \fI/mydir/mysubdir/myfile.txt\fP would become \fI/mydir/mysubdir/\fP, and so on.
.TP
.B
u
Scroll up an amount corresponding to 75% of your terminal window height in the current document.
.TP
@ -176,6 +185,14 @@ home
Navigates to the document set by the \fIhomeurl\fP setting. \fIh\fP can be entered, rather than the full \fIhome\fP.
.TP
.B
jump
Navigates to the previous page in history from the current page. Useful for keeping the current page in your history while still browsing. \fIj\fP can be used instead of the full \fIjump\fP.
.TP
.B
jump [history location]
Navigates to the given history location. The history location should be an integer between 0 and 20. \fIj\fP can be used instead of the full \fIjump\fP.
.TP
.B
purge *
Deletes all pinned gemini server certificates. \fIp\fP can be used instead of the full \fIpurge\fP.
.TP
@ -204,6 +221,10 @@ set [setting name] [value]
Sets the value for a given configuration setting. \fIs\fP can be used instead of the full \fIset\fP.
.TP
.B
version
Shows the current Bombadillo version number.
.TP
.B
write .
Writes the current document to a file. The file is named by the last component of the url path. If the last component is blank or \fI/\fP a default name will be used. The file saves to the directory set by the \fIsavelocation\fP setting. \fIw\fP can be entered rather than the full \fIwrite\fP.
.TP
@ -228,16 +249,24 @@ defaultscheme
The scheme that should be used when no scheme is present in a given URL. \fIgopher\fP, \fIgemini\fP, \fIhttp\fP, and \fIhttps\fP are valid values.
.TP
.B
geminiblocks
Determines how to treat preformatted text blocks in text/gemini documents. \fIblock\fP will show the contents of the block, \fIalt\fP will show any available alt text for the block, \fIboth\fP will show both the content and the alt text, and \fIneither\fP will show neither. Unlike other settings, a change to this value will require a fresh page load to see the change.
.TP
.B
homeurl
The url that \fBbombadillo\fP navigates to when the program loads or when the \fIhome\fP or \fIh\fP LINE COMMAND is issued. This should be a valid url. If a scheme/protocol is not included, gopher will be assumed.
.TP
.B
maxwidth
The number of characters at which lines should be wrapped. If this is bigger than the available terminal width, the full width of the terminal will be used. If a non-integer or an integer less than 10 is given, a default value will be used.
.TP
.B
savelocation
The path to the directory that \fBbombadillo\fP should write files to. This must be a valid filepath for the system, must be a directory, and must already exist.
.TP
.B
searchengine
The url to use for the LINE COMMANDs \fI?\fP and \fIsearch\fP. Should be a valid search path that terms may be appended to.
The url to use for the LINE COMMAND \fIsearch\fP. Should be a valid search path that terms may be appended to.
.TP
.B
telnetcommand
@ -248,12 +277,8 @@ theme
Can toggle between visual modes. Valid values are \fInormal\fP, \fIcolor\fP, and \fIinverse\fP. When set to inverse, the normal mode colors are inverted. Both normal and inverse modes filter out terminal escape sequences. When set to color, Bombadillo will render terminal escape sequences representing colors when it finds them in documents.
.TP
.B
tlscertificate
A path to a tls certificate file on a user's local filesystem. Defaults to NULL. Both \fItlscertificate\fP and \fItlskey\fP must be set for client certificates to work in gemini.
.TP
.B
tlskey
A path to a tls key that pairs with the tlscertificate setting, on a user's local filesystem. Defaults to NULL. Both \fItlskey\fP and \fItlscertificate\fP must be set for client certificates to work in gemini.
timeout
The number of seconds after which connections to gopher or gemini servers should time out if the server has not responded.
.TP
.B
webmode

View File

@ -1,3 +1,18 @@
/*
* Copyright (C) 2022 Brian Evans
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package main
import (

293
client.go
View File

@ -1,10 +1,25 @@
/*
* Copyright (C) 2022 Brian Evans
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package main
import (
"fmt"
"io/ioutil"
"net/url"
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
@ -19,6 +34,7 @@ import (
"tildegit.org/sloum/bombadillo/http"
"tildegit.org/sloum/bombadillo/local"
"tildegit.org/sloum/bombadillo/telnet"
"tildegit.org/sloum/bombadillo/termios"
)
//------------------------------------------------\\
@ -43,14 +59,7 @@ type client struct {
//--------------------------------------------------\\
func (c *client) GetSizeOnce() {
cmd := exec.Command("stty", "size")
cmd.Stdin = os.Stdin
out, err := cmd.Output()
if err != nil {
cui.Exit(5, "Fatal error: Unable to retrieve terminal size")
}
var h, w int
_, _ = fmt.Sscan(string(out), &h, &w)
var w, h = termios.GetWindowSize()
c.Height = h
c.Width = w
}
@ -61,14 +70,7 @@ func (c *client) GetSize() {
c.Draw()
for {
cmd := exec.Command("stty", "size")
cmd.Stdin = os.Stdin
out, err := cmd.Output()
if err != nil {
cui.Exit(5, "Fatal error: Unable to retrieve terminal size")
}
var h, w int
_, _ = fmt.Sscan(string(out), &h, &w)
var w, h = termios.GetWindowSize()
if h != c.Height || w != c.Width {
c.Height = h
c.Width = w
@ -86,7 +88,7 @@ func (c *client) Draw() {
screen.WriteString("\033[0m")
screen.WriteString(c.TopBar.Render(c.Width, c.Options["theme"]))
screen.WriteString("\n")
pageContent := c.PageState.Render(c.Height, c.Width-1, (c.Options["theme"] == "color"))
pageContent := c.PageState.Render(c.Height, c.Width-1, getMaxWidth(c.Options), (c.Options["theme"] == "color"))
var re *regexp.Regexp
if c.Options["theme"] == "inverse" {
screen.WriteString("\033[7m")
@ -135,15 +137,11 @@ func (c *client) Draw() {
} else {
for i := 0; i < c.Height-3; i++ {
if i < len(pageContent) {
extra := 0
escapes := re.FindAllString(pageContent[i], -1)
for _, esc := range escapes {
extra += len(esc)
}
screen.WriteString(fmt.Sprintf("%-*.*s", c.Width+extra, c.Width+extra, pageContent[i]))
screen.WriteString("\033[0K")
screen.WriteString(pageContent[i])
screen.WriteString("\n")
} else {
screen.WriteString(fmt.Sprintf("%-*.*s", c.Width, c.Width, " "))
screen.WriteString("\033[0K")
screen.WriteString("\n")
}
}
@ -162,37 +160,48 @@ func (c *client) TakeControlInput() {
input := cui.Getch()
switch input {
case 'j', 'J':
// scroll down one line
case '1', '2', '3', '4', '5', '6', '7', '8', '9', '0':
// Quick link
if input == '0' {
c.goToLink("10")
} else {
c.goToLink(string(input))
}
case 'j':
// Scroll down one line
c.ClearMessage()
c.Scroll(1)
case 'k', 'K':
// scroll up one line
case 'k':
// Scroll up one line
c.ClearMessage()
c.Scroll(-1)
case 'q', 'Q':
// quit bombadillo
case 'q':
// Quit
cui.Exit(0, "")
case 'g':
// scroll to top
// Scroll to top
c.ClearMessage()
c.Scroll(-len(c.PageState.History[c.PageState.Position].WrappedContent))
case 'G':
// scroll to bottom
// Scroll to bottom
c.ClearMessage()
c.Scroll(len(c.PageState.History[c.PageState.Position].WrappedContent))
case 'd':
// scroll down 75%
// Scroll down 75%
c.ClearMessage()
distance := c.Height - c.Height/4
c.Scroll(distance)
case 'u':
// scroll up 75%
// Scroll up 75%
c.ClearMessage()
distance := c.Height - c.Height/4
c.Scroll(-distance)
case 'b':
// go back
case 'U':
// Move up a directory for the current host
url := c.PageState.History[c.PageState.Position].Location.Full
c.Visit(UpOneDir(url))
case 'b', 'h':
// Go back
c.ClearMessage()
err := c.PageState.NavigateHistory(-1)
if err != nil {
@ -204,6 +213,7 @@ func (c *client) TakeControlInput() {
c.Draw()
}
case 'R':
// Refresh the current page
c.ClearMessage()
err := c.ReloadPage()
if err != nil {
@ -213,11 +223,11 @@ func (c *client) TakeControlInput() {
c.Draw()
}
case 'B':
// open the bookmarks browser
// Toggle the bookmark browser
c.BookMarks.ToggleOpen()
c.Draw()
case 'f', 'F':
// go forward
case 'f', 'l':
// Go forward
c.ClearMessage()
err := c.PageState.NavigateHistory(1)
if err != nil {
@ -229,7 +239,7 @@ func (c *client) TakeControlInput() {
c.Draw()
}
case '\t':
// Toggle bookmark browser focus on/off
// Toggle bookmark browser focus
c.BookMarks.ToggleFocused()
c.Draw()
case 'n':
@ -269,6 +279,7 @@ func (c *client) TakeControlInput() {
}
err = c.NextSearchItem(0)
if err != nil {
c.PageState.History[c.PageState.Position].WrapContent(c.Width-1,getMaxWidth(c.Options),(c.Options["theme"] == "color"))
c.Draw()
}
case ':', ' ':
@ -355,23 +366,41 @@ func (c *client) simpleCommand(action string) {
case "SEARCH":
c.search("", "", "?")
case "HELP", "?":
go c.Visit(helplocation)
c.Visit(helplocation)
case "JUMP", "J":
err := c.PageState.CopyHistory(-1)
if err != nil {
c.SetMessage(err.Error(), false)
c.DrawMessage()
} else {
c.Draw()
}
case "VERSION":
ver := version
if ver == "" {
ver = "Improperly compiled, no version information"
}
c.SetMessage("Bombadillo version: " + ver, false)
c.DrawMessage()
default:
c.SetMessage(fmt.Sprintf("Unknown action %q", action), true)
c.SetMessage(syntaxErrorMessage(action), true)
c.DrawMessage()
}
}
func (c *client) doCommand(action string, values []string) {
if length := len(values); length != 1 {
c.SetMessage(fmt.Sprintf("Expected 1 argument, received %d", len(values)), true)
c.DrawMessage()
return
}
switch action {
case "CHECK", "C":
case "C", "CHECK":
c.displayConfigValue(values[0])
c.DrawMessage()
case "HELP", "?":
if val, ok := ERRS[values[0]]; ok {
c.SetMessage("Usage: " + val, false)
} else {
msg := fmt.Sprintf("%q is not a valid command; help syntax: %s", values[0], ERRS[action])
c.SetMessage(msg, false)
}
c.DrawMessage()
case "PURGE", "P":
err := c.Certs.Purge(values[0])
if err != nil {
@ -414,26 +443,23 @@ func (c *client) doCommand(action string, values []string) {
fn = "index"
}
c.saveFile(u, fn)
default:
c.SetMessage(fmt.Sprintf("Unknown action %q", action), true)
c.SetMessage(syntaxErrorMessage(action), true)
c.DrawMessage()
}
}
func (c *client) doCommandAs(action string, values []string) {
if len(values) < 2 {
c.SetMessage(fmt.Sprintf("Expected 2+ arguments, received %d", len(values)), true)
c.DrawMessage()
return
}
if values[0] == "." {
values[0] = c.PageState.History[c.PageState.Position].Location.Full
}
switch action {
case "ADD", "A":
if len(values) < 2 {
c.SetMessage(syntaxErrorMessage(action), true)
c.DrawMessage()
return
}
if values[0] == "." {
values[0] = c.PageState.History[c.PageState.Position].Location.Full
}
msg, err := c.BookMarks.Add(values)
if err != nil {
c.SetMessage(err.Error(), true)
@ -452,8 +478,18 @@ func (c *client) doCommandAs(action string, values []string) {
c.Draw()
}
case "SEARCH":
if len(values) < 2 {
c.SetMessage(syntaxErrorMessage(action), true)
c.DrawMessage()
return
}
c.search(strings.Join(values, " "), "", "")
case "SET", "S":
if len(values) < 2 {
c.SetMessage(syntaxErrorMessage(action), true)
c.DrawMessage()
return
}
if _, ok := c.Options[values[0]]; ok {
val := strings.Join(values[1:], " ")
if !validateOpt(values[0], val) {
@ -462,8 +498,10 @@ func (c *client) doCommandAs(action string, values []string) {
return
}
c.Options[values[0]] = lowerCaseOpt(values[0], val)
if values[0] == "tlskey" || values[0] == "tlscertificate" {
c.Certs.LoadCertificate(c.Options["tlscertificate"], c.Options["tlskey"])
if values[0] == "geminiblocks" {
gemini.BlockBehavior = c.Options[values[0]]
} else if values[0] == "timeout" {
updateTimeouts(c.Options[values[0]])
} else if values[0] == "configlocation" {
c.SetMessage("Cannot set READ ONLY setting 'configlocation'", true)
c.DrawMessage()
@ -482,7 +520,7 @@ func (c *client) doCommandAs(action string, values []string) {
c.SetMessage(fmt.Sprintf("Unable to set %s, it does not exist", values[0]), true)
c.DrawMessage()
default:
c.SetMessage(fmt.Sprintf("Unknown command structure"), true)
c.SetMessage(syntaxErrorMessage(action), true)
c.DrawMessage()
}
}
@ -532,7 +570,7 @@ func (c *client) doLinkCommandAs(action, target string, values []string) {
out = append(out, values...)
c.doCommandAs(action, out)
default:
c.SetMessage(fmt.Sprintf("Unknown command structure"), true)
c.SetMessage(syntaxErrorMessage(action), true)
c.DrawMessage()
}
}
@ -639,6 +677,15 @@ func (c *client) doLinkCommand(action, target string) {
link := links[num]
c.SetMessage(fmt.Sprintf("[%d] %s", num+1, link), false)
c.DrawMessage()
case "JUMP", "J":
num--
err = c.PageState.CopyHistory(num)
if err != nil {
c.SetMessage(err.Error(), false)
c.DrawMessage()
} else {
c.Draw()
}
case "WRITE", "W":
links := c.PageState.History[c.PageState.Position].Links
if len(links) < num || num < 1 {
@ -664,13 +711,13 @@ func (c *client) doLinkCommand(action, target string) {
}
c.saveFile(u, fn)
default:
c.SetMessage("Unknown command structure", true)
c.SetMessage(syntaxErrorMessage(action), true)
c.DrawMessage()
}
}
func (c *client) search(query, url, question string) {
func (c *client) search(query, uri, question string) {
var entry string
var err error
if query == "" {
@ -694,22 +741,32 @@ func (c *client) search(query, url, question string) {
} else {
entry = query
}
if url == "" {
url = c.Options["searchengine"]
if uri == "" {
uri = c.Options["searchengine"]
}
u, err := MakeUrl(url)
u, err := MakeUrl(uri)
if err != nil {
c.SetMessage("The search url is not a valid url", true)
c.SetMessage("The search url is not valid", true)
c.DrawMessage()
return
}
var rootUrl string
switch u.Scheme {
case "gopher":
go c.Visit(fmt.Sprintf("%s\t%s", u.Full, entry))
if ind := strings.Index(u.Full, "\t"); ind >= 0 {
rootUrl = u.Full[:ind]
} else {
rootUrl = u.Full
}
c.Visit(fmt.Sprintf("%s\t%s", rootUrl, entry))
case "gemini":
// TODO url escape the entry variable
escapedEntry := entry
go c.Visit(fmt.Sprintf("%s?%s", u.Full, escapedEntry))
if ind := strings.Index(u.Full, "?"); ind >= 0 {
rootUrl = u.Full[:ind]
} else {
rootUrl = u.Full
}
escapedEntry := url.PathEscape(entry)
c.Visit(fmt.Sprintf("%s?%s", rootUrl, escapedEntry))
case "http", "https":
c.Visit(u.Full)
default:
@ -929,7 +986,7 @@ func (c *client) Visit(url string) {
// +++ Begin Protocol Handlers +++
func (c *client) handleGopher(u Url) {
if u.DownloadOnly {
if u.DownloadOnly || (c.Options["showimages"] == "false" && (u.Mime == "I" || u.Mime == "g")) {
nameSplit := strings.Split(u.Resource, "/")
filename := nameSplit[len(nameSplit)-1]
filename = strings.Trim(filename, " \t\r\n\v\f\a")
@ -947,7 +1004,12 @@ func (c *client) handleGopher(u Url) {
return
}
pg := MakePage(u, content, links)
pg.WrapContent(c.Width-1, (c.Options["theme"] == "color"))
if u.Mime == "I" || u.Mime == "g" {
pg.FileType = "image"
} else {
pg.FileType = "text"
}
pg.WrapContent(c.Width-1, getMaxWidth(c.Options), (c.Options["theme"] == "color"))
c.PageState.Add(pg)
c.SetPercentRead()
c.ClearMessage()
@ -966,11 +1028,15 @@ func (c *client) handleGemini(u Url) {
go saveConfig()
switch capsule.Status {
case 1:
// Query
c.search("", u.Full, capsule.Content)
case 2:
if capsule.MimeMaj == "text" {
// Success
if capsule.MimeMaj == "text" || (c.Options["showimages"] == "true" && capsule.MimeMaj == "image") {
u.Mime = capsule.MimeMin
pg := MakePage(u, capsule.Content, capsule.Links)
pg.WrapContent(c.Width-1, (c.Options["theme"] == "color"))
pg.FileType = capsule.MimeMaj
pg.WrapContent(c.Width-1, getMaxWidth(c.Options), (c.Options["theme"] == "color"))
c.PageState.Add(pg)
c.SetPercentRead()
c.ClearMessage()
@ -984,14 +1050,28 @@ func (c *client) handleGemini(u Url) {
c.saveFileFromData(capsule.Content, filename)
}
case 3:
c.SetMessage(fmt.Sprintf("Follow redirect (y/n): %s?", capsule.Content), false)
c.DrawMessage()
ch := cui.Getch()
if ch == 'y' || ch == 'Y' {
// Redirect
lowerRedirect := strings.ToLower(capsule.Content)
lowerOriginal := strings.ToLower(u.Full)
if strings.Replace(lowerRedirect, lowerOriginal, "", 1) == "/" {
c.Visit(capsule.Content)
} else {
c.SetMessage("Redirect aborted", false)
if !strings.Contains(capsule.Content, "://") {
lnk, lnkErr := gemini.HandleRelativeUrl(capsule.Content, u.Full)
if lnkErr == nil {
capsule.Content = lnk
}
}
c.SetMessage(fmt.Sprintf("Follow redirect (y/n): %s?", capsule.Content), false)
c.DrawMessage()
ch := cui.Getch()
if ch == 'y' || ch == 'Y' {
c.Visit(capsule.Content)
} else {
c.SetMessage("Redirect aborted", false)
c.DrawMessage()
}
}
}
}
@ -1018,7 +1098,11 @@ func (c *client) handleLocal(u Url) {
return
}
pg := MakePage(u, content, links)
pg.WrapContent(c.Width-1, (c.Options["theme"] == "color"))
ext := strings.ToLower(filepath.Ext(u.Full))
if ext == ".jpg" || ext == ".jpeg" || ext == ".gif" || ext == ".png" {
pg.FileType = "image"
}
pg.WrapContent(c.Width-1, getMaxWidth(c.Options), (c.Options["theme"] == "color"))
c.PageState.Add(pg)
c.SetPercentRead()
c.ClearMessage()
@ -1034,7 +1118,7 @@ func (c *client) handleFinger(u Url) {
return
}
pg := MakePage(u, content, []string{})
pg.WrapContent(c.Width-1, (c.Options["theme"] == "color"))
pg.WrapContent(c.Width-1, getMaxWidth(c.Options), (c.Options["theme"] == "color"))
c.PageState.Add(pg)
c.SetPercentRead()
c.ClearMessage()
@ -1054,7 +1138,7 @@ func (c *client) handleWeb(u Url) {
return
}
pg := MakePage(u, page.Content, page.Links)
pg.WrapContent(c.Width-1, (c.Options["theme"] == "color"))
pg.WrapContent(c.Width-1, getMaxWidth(c.Options), (c.Options["theme"] == "color"))
c.PageState.Add(pg)
c.SetPercentRead()
c.ClearMessage()
@ -1175,3 +1259,36 @@ func findAvailableFileName(fpath, fname string) (string, error) {
return savePath, nil
}
func syntaxErrorMessage(action string) string {
if val, ok := ERRS[action]; ok {
return fmt.Sprintf("Incorrect syntax. Try: %s", val)
}
return fmt.Sprintf("Unknown command %q", action)
}
func updateTimeouts(timeoutString string) error {
sec, err := strconv.Atoi(timeoutString)
if err != nil {
return err
}
timeout := time.Duration(sec) * time.Second
gopher.Timeout = timeout
gemini.TlsTimeout = timeout
return nil
}
// getMaxWidth looks through the given options map and will safely return a max width to render
// if the option is missing or malformed, it will default to 100. A sane minimum of 10 is enforced.
func getMaxWidth(options map[string]string) int {
out, err := strconv.Atoi(options["maxwidth"])
if err != nil {
out = 100
}
if out < 10 {
out = 10
}
return out
}

View File

@ -1,3 +1,18 @@
/*
* Copyright (C) 2022 Brian Evans
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package cmdparse
import (
@ -72,7 +87,7 @@ func (s *scanner) scanText() Token {
"S", "SET", "R", "RELOAD", "SEARCH",
"Q", "QUIT", "B", "BOOKMARKS", "H",
"HOME", "?", "HELP", "C", "CHECK",
"P", "PURGE":
"P", "PURGE", "JUMP", "J", "VERSION":
return Token{Action, capInput}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2022 Brian Evans
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package cmdparse
import (
@ -94,10 +110,10 @@ func (p *Parser) parseAction() (*Command, error) {
case Value:
cm.Target = t.val
cm.Type = DOLINK
case Word:
case Word, Action:
cm.Value = append(cm.Value, t.val)
cm.Type = DO
case Action, Whitespace:
case Whitespace:
return nil, fmt.Errorf("Found %q (%d), expected value", t.val, t.kind)
}
t = p.scan()

View File

@ -1,3 +1,18 @@
/*
* Copyright (C) 2022 Brian Evans
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package config
import (

View File

@ -1,3 +1,18 @@
/*
* Copyright (C) 2022 Brian Evans
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package config
import (

View File

@ -1,3 +1,18 @@
/*
* Copyright (C) 2022 Brian Evans
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package cui
import (
@ -5,6 +20,8 @@ import (
"fmt"
"os"
"os/exec"
"tildegit.org/sloum/bombadillo/termios"
)
var Shapes = map[string]string{
@ -55,16 +72,17 @@ func Exit(exitCode int, msg string) {
// InitTerm sets the terminal modes appropriate for Bombadillo
func InitTerm() {
SetCharMode()
Tput("rmam") // turn off line wrapping
Tput("smcup") // use alternate screen
termios.SetCharMode()
Tput("smcup") // use alternate screen
Tput("rmam") // turn off line wrapping
fmt.Print("\033[?25l") // hide cursor
}
// CleanupTerm reverts changs to terminal mode made by InitTerm
func CleanupTerm() {
moveCursorToward("down", 500)
moveCursorToward("right", 500)
SetLineMode()
termios.SetLineMode()
fmt.Print("\n")
fmt.Print("\033[?25h") // reenables cursor blinking
@ -98,7 +116,8 @@ func Getch() rune {
}
func GetLine(prefix string) (string, error) {
SetLineMode()
termios.SetLineMode()
defer termios.SetCharMode()
reader := bufio.NewReader(os.Stdin)
fmt.Print(prefix)
@ -107,32 +126,9 @@ func GetLine(prefix string) (string, error) {
return "", err
}
SetCharMode()
return text[:len(text)-1], nil
}
func SetCharMode() {
cmd := exec.Command("stty", "cbreak", "-echo")
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
err := cmd.Run()
if err != nil {
panic(err)
}
fmt.Print("\033[?25l")
}
func SetLineMode() {
cmd := exec.Command("stty", "-cbreak", "echo")
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
err := cmd.Run()
if err != nil {
panic(err)
}
}
func Tput(opt string) {
cmd := exec.Command("tput", opt)
cmd.Stdin = os.Stdin

View File

@ -1,3 +1,18 @@
/*
* Copyright (C) 2022 Brian Evans
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package main
import (
@ -45,16 +60,18 @@ var defaultOptions = map[string]string{
// the "configlocation" as follows:
// "configlocation": xdgConfigPath()
"configlocation": xdgConfigPath(),
"defaultscheme": "gopher", // "gopher", "gemini", "http", "https"
"geminiblocks": "block", // "block", "alt", "neither", "both"
"homeurl": "gopher://bombadillo.colorfield.space:70/1/user-guide.map",
"savelocation": homePath(),
"searchengine": "gopher://gopher.floodgap.com:70/7/v2/vs",
"showimages": "true",
"telnetcommand": "telnet",
"configlocation": xdgConfigPath(),
"defaultscheme": "gopher", // "gopher", "gemini", "http", "https"
"theme": "normal", // "normal", "inverted", "color"
"tlscertificate": "",
"tlskey": "",
"webmode": "none", // "none", "gui", "lynx", "w3m", "elinks"
"timeout": "15", // connection timeout for gopher/gemini in seconds
"webmode": "none", // "none", "gui", "lynx", "w3m", "elinks"
"maxwidth": "100",
}
// homePath will return the path to your home directory as a string

View File

@ -1,3 +1,18 @@
/*
* Copyright (C) 2022 Brian Evans
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package finger
import (

View File

@ -1,3 +1,18 @@
/*
* Copyright (C) 2022 Brian Evans
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package main
import (

View File

@ -1,3 +1,18 @@
/*
* Copyright (C) 2022 Brian Evans
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package gemini
import (
@ -6,6 +21,8 @@ import (
"crypto/tls"
"fmt"
"io/ioutil"
"net"
"net/url"
"strconv"
"strings"
"time"
@ -21,22 +38,15 @@ type Capsule struct {
type TofuDigest struct {
certs map[string]string
ClientCert tls.Certificate
}
var BlockBehavior string = "block"
var TlsTimeout time.Duration = time.Duration(15) * time.Second
//------------------------------------------------\\
// + + + R E C E I V E R S + + + \\
//--------------------------------------------------\\
func (t *TofuDigest) LoadCertificate(cert, key string) {
certificate, err := tls.LoadX509KeyPair(cert, key)
if err != nil {
t.ClientCert = tls.Certificate{}
return
}
t.ClientCert = certificate
}
func (t *TofuDigest) Purge(host string) error {
host = strings.ToLower(host)
if host == "*" {
@ -49,8 +59,8 @@ func (t *TofuDigest) Purge(host string) error {
return fmt.Errorf("Invalid host %q", host)
}
func (t *TofuDigest) Add(host, hash string) {
t.certs[strings.ToLower(host)] = hash
func (t *TofuDigest) Add(host, hash string, time int64) {
t.certs[strings.ToLower(host)] = fmt.Sprintf("%s|%d", hash, time)
}
func (t *TofuDigest) Exists(host string) bool {
@ -67,12 +77,11 @@ func (t *TofuDigest) Find(host string) (string, error) {
return "", fmt.Errorf("Invalid hostname, no key saved")
}
func (t *TofuDigest) Match(host string, cState *tls.ConnectionState) error {
host = strings.ToLower(host)
func (t *TofuDigest) Match(host, localCert string, cState *tls.ConnectionState) error {
now := time.Now()
for _, cert := range cState.PeerCertificates {
if t.certs[host] != hashCert(cert.Raw) {
if localCert != hashCert(cert.Raw) {
continue
}
@ -84,7 +93,7 @@ func (t *TofuDigest) Match(host string, cState *tls.ConnectionState) error {
return fmt.Errorf("EXP")
}
if err := cert.VerifyHostname(host); err != nil {
if err := cert.VerifyHostname(host); err != nil && cert.Subject.CommonName != host {
return fmt.Errorf("Certificate error: %s", err)
}
@ -113,18 +122,45 @@ func (t *TofuDigest) newCert(host string, cState *tls.ConnectionState) error {
continue
}
if err := cert.VerifyHostname(host); err != nil {
if err := cert.VerifyHostname(host); err != nil && cert.Subject.CommonName != host {
reasons.WriteString(fmt.Sprintf("Cert [%d] hostname does not match", index+1))
continue
}
t.Add(host, hashCert(cert.Raw))
t.Add(host, hashCert(cert.Raw), cert.NotAfter.Unix())
return nil
}
return fmt.Errorf(reasons.String())
}
func (t *TofuDigest) GetCertAndTimestamp(host string) (string, int64, error) {
certTs, err := t.Find(host)
if err != nil {
return "", -1, err
}
certTsSplit := strings.SplitN(certTs, "|", -1)
if len(certTsSplit) < 2 {
_ = t.Purge(host)
return certTsSplit[0], -1, fmt.Errorf("Invalid certstring, no delimiter")
}
ts, err := strconv.ParseInt(certTsSplit[1], 10, 64)
if err != nil {
_ = t.Purge(host)
return certTsSplit[0], -1, err
}
now := time.Now()
if ts < now.Unix() {
// Ignore error return here since an error would indicate
// the host does not exist and we have already checked for
// that and the desired outcome of the action is that the
// host will no longer exist, so we are good either way
_ = t.Purge(host)
return "", -1, fmt.Errorf("Expired cert")
}
return certTsSplit[0], ts, nil
}
func (t *TofuDigest) IniDump() string {
if len(t.certs) < 1 {
return ""
@ -156,11 +192,7 @@ func Retrieve(host, port, resource string, td *TofuDigest) (string, error) {
InsecureSkipVerify: true,
}
conf.GetClientCertificate = func(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
return &td.ClientCert, nil
}
conn, err := tls.Dial("tcp", addr, conf)
conn, err := tls.DialWithDialer(&net.Dialer{Timeout: TlsTimeout}, "tcp", addr, conf)
if err != nil {
return "", fmt.Errorf("TLS Dial Error: %s", err.Error())
}
@ -176,9 +208,11 @@ func Retrieve(host, port, resource string, td *TofuDigest) (string, error) {
return "", fmt.Errorf("Insecure, no certificates offered by server")
}
if td.Exists(host) {
localCert, localTs, err := td.GetCertAndTimestamp(host)
if localTs > 0 {
// See if we have a matching cert
err := td.Match(host, &connState)
err := td.Match(host, localCert, &connState)
if err != nil && err.Error() != "EXP" {
// If there is no match and it isnt because of an expiration
// just return the error
@ -251,7 +285,7 @@ func Fetch(host, port, resource string, td *TofuDigest) ([]byte, error) {
case 5:
return make([]byte, 0), fmt.Errorf("[5] Permanent Failure.")
case 6:
return make([]byte, 0), fmt.Errorf("[6] Client Certificate Required")
return make([]byte, 0), fmt.Errorf("[6] Client Certificate Required (Unsupported)")
default:
return make([]byte, 0), fmt.Errorf("Invalid response status from server")
}
@ -300,6 +334,9 @@ func Visit(host, port, resource string, td *TofuDigest) (Capsule, error) {
case 2:
mimeAndCharset := strings.Split(header[1], ";")
meta = mimeAndCharset[0]
if meta == "" {
meta = "text/gemini"
}
minMajMime := strings.Split(meta, "/")
if len(minMajMime) < 2 {
return capsule, fmt.Errorf("Improperly formatted mimetype received from server")
@ -313,8 +350,7 @@ func Visit(host, port, resource string, td *TofuDigest) (Capsule, error) {
resource = "/"
}
currentUrl := fmt.Sprintf("gemini://%s:%s%s", host, port, resource)
rootUrl := fmt.Sprintf("gemini://%s:%s", host, port)
capsule.Content, capsule.Links = parseGemini(body, rootUrl, currentUrl)
capsule.Content, capsule.Links = parseGemini(body, currentUrl)
} else {
capsule.Content = body
}
@ -329,19 +365,34 @@ func Visit(host, port, resource string, td *TofuDigest) (Capsule, error) {
case 5:
return capsule, fmt.Errorf("[5] Permanent Failure. %s", header[1])
case 6:
return capsule, fmt.Errorf("[6] Client Certificate Required")
return capsule, fmt.Errorf("[6] Client Certificate Required (Unsupported)")
default:
return capsule, fmt.Errorf("Invalid response status from server")
}
}
func parseGemini(b, rootUrl, currentUrl string) (string, []string) {
func parseGemini(b, currentUrl string) (string, []string) {
splitContent := strings.Split(b, "\n")
links := make([]string, 0, 10)
inPreBlock := false
spacer := " "
outputIndex := 0
for i, ln := range splitContent {
splitContent[i] = strings.Trim(ln, "\r\n")
if len([]rune(ln)) > 3 && ln[:2] == "=>" {
isPreBlockDeclaration := strings.HasPrefix(ln, "```")
if isPreBlockDeclaration && !inPreBlock && (BlockBehavior == "both" || BlockBehavior == "alt") {
inPreBlock = !inPreBlock
alt := strings.TrimSpace(ln)
if len(alt) > 3 {
alt = strings.TrimSpace(alt[3:])
splitContent[outputIndex] = fmt.Sprintf("%s[ALT][ %s ]", spacer, alt)
outputIndex++
}
} else if isPreBlockDeclaration {
inPreBlock = !inPreBlock
} else if len([]rune(ln)) > 3 && ln[:2] == "=>" && !inPreBlock {
var link, decorator string
subLn := strings.Trim(ln[2:], "\r\n\t \a")
splitPoint := strings.IndexAny(subLn, " \t")
@ -355,33 +406,40 @@ func parseGemini(b, rootUrl, currentUrl string) (string, []string) {
}
if strings.Index(link, "://") < 0 {
link = handleRelativeUrl(link, rootUrl, currentUrl)
link, _ = HandleRelativeUrl(link, currentUrl)
}
links = append(links, link)
linknum := fmt.Sprintf("[%d]", len(links))
splitContent[i] = fmt.Sprintf("%-5s %s", linknum, decorator)
splitContent[outputIndex] = fmt.Sprintf("%-5s %s", linknum, decorator)
outputIndex++
} else {
if inPreBlock && (BlockBehavior == "alt" || BlockBehavior == "neither") {
continue
}
var leader, tail string = "", ""
if len(ln) > 0 && ln[0] == '#' {
leader = "\033[1m"
tail = "\033[0m"
}
splitContent[outputIndex] = fmt.Sprintf("%s%s%s%s", spacer, leader, ln, tail)
outputIndex++
}
}
return strings.Join(splitContent, "\n"), links
return strings.Join(splitContent[:outputIndex], "\n"), links
}
func handleRelativeUrl(u, root, current string) string {
if len(u) < 1 {
return u
// handleRelativeUrl provides link completion
func HandleRelativeUrl(relLink, current string) (string, error) {
base, err := url.Parse(current)
if err != nil {
return relLink, err
}
if u[0] == '/' {
return fmt.Sprintf("%s%s", root, u)
rel, err := url.Parse(relLink)
if err != nil {
return relLink, err
}
ind := strings.LastIndex(current, "/")
if ind < 10 {
return fmt.Sprintf("%s/%s", root, u)
}
current = current[:ind+1]
return fmt.Sprintf("%s%s", current, u)
return base.ResolveReference(rel).String(), nil
}
func hashCert(cert []byte) string {
@ -398,5 +456,5 @@ func MakeCapsule() Capsule {
}
func MakeTofuDigest() TofuDigest {
return TofuDigest{make(map[string]string), tls.Certificate{}}
return TofuDigest{make(map[string]string)}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2022 Brian Evans
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// Contains the building blocks of a gopher client: history, url, and view.
// History handles the browsing session and view represents individual
// text based resources, the url represents a parsed url.
@ -38,6 +54,8 @@ var types = map[string]string{
"T": "TEL",
}
var Timeout time.Duration = time.Duration(15) * time.Second
//------------------------------------------------\\
// + + + F U N C T I O N S + + + \\
//--------------------------------------------------\\
@ -49,7 +67,6 @@ var types = map[string]string{
// be better.
func Retrieve(host, port, resource string) ([]byte, error) {
nullRes := make([]byte, 0)
timeOut := time.Duration(5) * time.Second
if host == "" || port == "" {
return nullRes, errors.New("Incomplete request url")
@ -57,7 +74,7 @@ func Retrieve(host, port, resource string) ([]byte, error) {
addr := host + ":" + port
conn, err := net.DialTimeout("tcp", addr, timeOut)
conn, err := net.DialTimeout("tcp", addr, Timeout)
if err != nil {
return nullRes, err
}
@ -131,16 +148,20 @@ func parseMap(text string) (string, []string) {
if len(line[0]) > 1 {
title = line[0][1:]
} else if len(line[0]) == 1 {
title = ""
} else {
title = ""
line[0] = "i"
}
if len(line) > 1 && len(line[0]) > 0 && string(line[0][0]) == "i" {
if len(line) < 4 || strings.HasPrefix(line[0], "i") {
splitContent[i] = " " + string(title)
} else if len(line) >= 4 {
} else {
link := buildLink(line[2], line[3], string(line[0][0]), line[1])
links = append(links, link)
linktext := fmt.Sprintf("(%s) %2d %s", getType(string(line[0][0])), len(links), title)
linkNum := fmt.Sprintf("[%d]",len(links))
linktext := fmt.Sprintf("%s %5s %s", getType(string(line[0][0])), linkNum, title)
splitContent[i] = linktext
}
}

View File

@ -1,3 +1,18 @@
/*
* Copyright (C) 2022 Brian Evans
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package main
import (

46
help.go Normal file
View File

@ -0,0 +1,46 @@
/*
* Copyright (C) 2022 Brian Evans
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package main
// ERRS maps commands to their syntax error message
var ERRS = map[string]string{
"A": "`a [target] [name...]`",
"ADD": "`add [target] [name...]`",
"D": "`d [bookmark-id]`",
"DELETE": "`delete [bookmark-id]`",
"B": "`b [[bookmark-id]]`",
"BOOKMARKS": "`bookmarks [[bookmark-id]]`",
"C": "`c [link_id]` or `c [setting]`",
"CHECK": "`check [link_id]` or `check [setting]`",
"H": "`h`",
"HOME": "`home`",
"J": "`j [[history_position]]`",
"JUMP": "`jump [[history_position]]`",
"P": "`p [host]`",
"PURGE": "`purge [host]`",
"Q": "`q`",
"QUIT": "`quit`",
"R": "`r`",
"RELOAD": "`reload`",
"SEARCH": "`search [[keyword(s)...]]`",
"S": "`s [setting] [value]`",
"SET": "`set [setting] [value]`",
"W": "`w [target]`",
"WRITE": "`write [target]`",
"VERSION": "`version`",
"?": "`? [[command]]`",
"HELP": "`help [[command]]`",
}

View File

@ -1,3 +1,18 @@
/*
* Copyright (C) 2022 Brian Evans
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package http
import (
@ -33,7 +48,7 @@ func Visit(webmode, url string, width int) (Page, error) {
return Page{}, fmt.Errorf("Invalid webmode setting")
}
c, err := exec.Command(webmode, "-dump", w, fmt.Sprintf("%d", width), url).Output()
if err != nil {
if err != nil && c == nil {
return Page{}, err
}
return parseLinks(string(c)), nil

View File

@ -1,4 +1,20 @@
// +build darwin
/*
* Copyright (C) 2022 Brian Evans
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// This will build for osx without a build tag based on the filename
package http

View File

@ -1,30 +0,0 @@
// +build linux
package http
import (
"fmt"
"os"
"os/exec"
)
// OpenInBrowser checks for the presence of a display server
// and environment variables indicating a gui is present. If found
// then xdg-open is called on a url to open said url in the default
// gui web browser for the system
func OpenInBrowser(url string) (string, error) {
disp := os.Getenv("DISPLAY")
wayland := os.Getenv("WAYLAND_DISPLAY")
_, err := exec.LookPath("Xorg")
if disp == "" && wayland == "" && err != nil {
return "", fmt.Errorf("No gui is available, check 'webmode' setting")
}
// Use start rather than run or output in order
// to release the process and not block
err = exec.Command("xdg-open", url).Start()
if err != nil {
return "", err
}
return "Opened in system default web browser", nil
}

View File

@ -1,11 +1,46 @@
// +build !linux
// +build !darwin
// +build !windows
// +build !darwin,!windows
/*
* Copyright (C) 2022 Brian Evans
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package http
import "fmt"
import (
"fmt"
"os"
"os/exec"
)
// OpenInBrowser checks for the presence of a display server
// and environment variables indicating a gui is present. If found
// then xdg-open is called on a url to open said url in the default
// gui web browser for the system
func OpenInBrowser(url string) (string, error) {
return "", fmt.Errorf("Unsupported os for 'webmode' 'gui' setting")
disp := os.Getenv("DISPLAY")
wayland := os.Getenv("WAYLAND_DISPLAY")
_, err := exec.LookPath("Xorg")
if disp == "" && wayland == "" && err != nil {
return "", fmt.Errorf("No gui is available, check 'webmode' setting")
}
// Use start rather than run or output in order
// to release the process and not block
err = exec.Command("xdg-open", url).Start()
if err != nil {
return "", err
}
return "Opened in system default web browser", nil
}

View File

@ -1,5 +1,21 @@
// +build windows
/*
* Copyright (C) 2022 Brian Evans
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// This will only build for windows based on the filename
// no build tag required
package http
import "os/exec"

View File

@ -1,3 +1,18 @@
/*
* Copyright (C) 2022 Brian Evans
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package local
import (

74
main.go
View File

@ -3,21 +3,21 @@ package main
// Bombadillo is an internet client for the terminal of unix or
// unix-like systems.
//
// Copyright (C) 2019 Brian Evans
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
/*
* Copyright (C) 2022 Brian Evans
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"flag"
"fmt"
@ -25,16 +25,17 @@ import (
"os"
"os/signal"
"path/filepath"
"strconv"
"strings"
"syscall"
"time"
"tildegit.org/sloum/bombadillo/config"
"tildegit.org/sloum/bombadillo/cui"
_ "tildegit.org/sloum/bombadillo/gemini"
"tildegit.org/sloum/bombadillo/gemini"
)
var version string
var build string
var version string = "2.3.3"
var bombadillo *client
var helplocation string = "gopher://bombadillo.colorfield.space:70/1/user-guide.map"
@ -65,6 +66,8 @@ func validateOpt(opt, val string) bool {
"webmode": []string{"none", "gui", "lynx", "w3m", "elinks"},
"theme": []string{"normal", "inverse", "color"},
"defaultscheme": []string{"gopher", "gemini", "http", "https"},
"showimages": []string{"true", "false"},
"geminiblocks": []string{"block", "neither", "alt", "both"},
}
opt = strings.ToLower(opt)
@ -78,12 +81,20 @@ func validateOpt(opt, val string) bool {
}
return false
}
if opt == "timeout" {
_, err := strconv.Atoi(val)
if err != nil {
return false
}
}
return true
}
func lowerCaseOpt(opt, val string) string {
switch opt {
case "webmode", "theme", "defaultscheme":
case "webmode", "theme", "defaultscheme", "showimages", "geminiblocks":
return strings.ToLower(val)
default:
return val
@ -120,6 +131,11 @@ func loadConfig() {
if _, ok := bombadillo.Options[lowerkey]; ok {
if validateOpt(lowerkey, v.Value) {
bombadillo.Options[lowerkey] = v.Value
if lowerkey == "geminiblocks" {
gemini.BlockBehavior = v.Value
} else if lowerkey == "timeout" {
updateTimeouts(v.Value)
}
} else {
bombadillo.Options[lowerkey] = defaultOptions[lowerkey]
}
@ -131,16 +147,26 @@ func loadConfig() {
}
for _, v := range settings.Certs {
bombadillo.Certs.Add(v.Key, v.Value)
// Remove expired certs
vals := strings.SplitN(v.Value, "|", -1)
if len(vals) < 2 {
continue
}
now := time.Now()
ts, err := strconv.ParseInt(vals[1], 10, 64)
if err != nil || now.Unix() > ts {
continue
}
// Satisfied that the cert is not expired
// or malformed: add to the current client
// instance
bombadillo.Certs.Add(v.Key, vals[0], ts)
}
}
func initClient() {
bombadillo = MakeClient(" ((( Bombadillo ))) ")
loadConfig()
if bombadillo.Options["tlscertificate"] != "" && bombadillo.Options["tlskey"] != "" {
bombadillo.Certs.LoadCertificate(bombadillo.Options["tlscertificate"], bombadillo.Options["tlskey"])
}
}
// In the event of specific signals, ensure the display is shown correctly.
@ -184,7 +210,7 @@ func main() {
flag.Usage = printHelp
flag.Parse()
if *getVersion {
fmt.Printf("Bombadillo %s - build %s\n", version, build)
fmt.Printf("Bombadillo %s\n", version)
os.Exit(0)
}
args := flag.Args()

94
page.go
View File

@ -1,8 +1,25 @@
/*
* Copyright (C) 2022 Brian Evans
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package main
import (
"fmt"
"strings"
"tildegit.org/sloum/bombadillo/tdiv"
"unicode"
)
//------------------------------------------------\\
@ -21,6 +38,9 @@ type Page struct {
FoundLinkLines []int
SearchTerm string
SearchIndex int
FileType string
WrapWidth int
Color bool
}
//------------------------------------------------\\
@ -47,17 +67,41 @@ func (p *Page) ScrollPositionRange(termHeight int) (int, int) {
return p.ScrollPosition, end
}
func (p *Page) RenderImage(width int) {
w := (width - 5) * 2
if w > 300 {
w = 300
}
p.WrappedContent = tdiv.Render([]byte(p.RawContent), w)
p.WrapWidth = width
}
// WrapContent performs a hard wrap to the requested
// width and updates the WrappedContent
// of the Page struct width a string slice
// of the wrapped data
func (p *Page) WrapContent(width int, color bool) {
func (p *Page) WrapContent(width, maxWidth int, color bool) {
if p.FileType == "image" {
p.RenderImage(width)
return
}
width = min(width, maxWidth)
counter := 0
spacer := ""
var content strings.Builder
var esc strings.Builder
escape := false
content.Grow(len(p.RawContent))
for _, ch := range []rune(p.RawContent) {
if p.Location.Mime == "1" { // gopher document
spacer = " "
} else if strings.HasSuffix(p.Location.Mime, "gemini") { //gemini document
spacer = " "
}
runeArr := []rune(p.RawContent)
for i := 0; i < len(runeArr); i++ {
ch := runeArr[i]
if escape {
if color {
esc.WriteRune(ch)
@ -71,8 +115,8 @@ func (p *Page) WrapContent(width int, color bool) {
}
continue
}
if ch == '\n' {
content.WriteRune(ch)
if ch == '\n' || ch == '\u0085' || ch == '\u2028' || ch == '\u2029' {
content.WriteRune('\n')
counter = 0
} else if ch == '\t' {
if counter+4 < width {
@ -83,7 +127,7 @@ func (p *Page) WrapContent(width int, color bool) {
counter = 0
}
} else if ch == '\r' || ch == '\v' || ch == '\b' || ch == '\f' || ch == '\a' {
// Get rid of control characters we dont want
// Get rid of control characters we don't want
continue
} else if ch == 27 {
if p.Location.Scheme == "local" {
@ -99,23 +143,38 @@ func (p *Page) WrapContent(width int, color bool) {
}
continue
} else {
if counter <= width {
// peek forward to see if we can render the word without going over
j := i
for ; j < len(runeArr) && !unicode.IsSpace(runeArr[j]); j++ {
if counter+(j-i) > width+1 {
break
}
}
// if we can render the rest of the word, write the next letter. else, skip to the next line.
// TODO(raidancampbell): optimize this to write out the whole word, this will involve referencing the
// above special cases
if counter+(j-i) <= width+1 && !(j == i && counter == width+1) {
content.WriteRune(ch)
counter++
} else if ch == ' ' || ch == '\t' {
// we want to wrap and write this char, but it's a space. eat it to prevent the next line from
// having a leading whitespace because of our wrapping
counter++
} else {
content.WriteRune('\n')
counter = 0
if p.Location.Mime == "1" {
spacer := " "
content.WriteString(spacer)
counter += len(spacer)
}
content.WriteString(spacer)
counter += len(spacer)
content.WriteRune(ch)
counter++
}
}
}
p.WrappedContent = strings.Split(content.String(), "\n")
p.WrapWidth = width
p.Color = color
p.HighlightFoundText()
}
@ -132,7 +191,7 @@ func (p *Page) HighlightFoundText() {
if bombadillo.Options["theme"] == "inverse" {
format = "\033[27m%s\033[7m"
}
ln = strings.ReplaceAll(ln, p.SearchTerm, fmt.Sprintf(format, p.SearchTerm))
ln = strings.Replace(ln, p.SearchTerm, fmt.Sprintf(format, p.SearchTerm), -1)
p.WrappedContent[i] = ln
}
}
@ -153,7 +212,7 @@ func (p *Page) FindText() {
if found < 0 {
continue
}
ln = strings.ReplaceAll(ln, s, fmt.Sprintf(format, s))
ln = strings.Replace(ln, s, fmt.Sprintf(format, s), -1)
p.WrappedContent[i] = ln
p.FoundLinkLines = append(p.FoundLinkLines, i)
}
@ -165,6 +224,13 @@ func (p *Page) FindText() {
// MakePage returns a Page struct with default values
func MakePage(url Url, content string, links []string) Page {
p := Page{make([]string, 0), content, links, url, 0, make([]int, 0), "", 0}
p := Page{make([]string, 0), content, links, url, 0, make([]int, 0), "", 0, "", 40, false}
return p
}
func min(a, b int) int {
if a < b {
return a
}
return b
}

145
page_test.go Normal file
View File

@ -0,0 +1,145 @@
/*
* Copyright (C) 2022 Brian Evans
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package main
import (
"reflect"
"testing"
)
func Test_WrapContent_Wrapped_Line_Length(t *testing.T) {
type fields struct {
WrappedContent []string
RawContent string
Links []string
Location Url
ScrollPosition int
FoundLinkLines []int
SearchTerm string
SearchIndex int
FileType string
WrapWidth int
Color bool
}
type args struct {
width int
maxWidth int
color bool
}
// create a Url for use by the MakePage function
url, _ := MakeUrl("gemini://rawtext.club")
tests := []struct {
name string
input string
expects []string
args args
}{
{
"Short line that doesn't wrap",
"0123456789\n",
[]string{
"0123456789",
"",
},
args{
10,
10,
false,
},
},
{
"multiple words should wrap at the right point",
"01 345 789 123456789 123456789 123456789 123456789\n",
[]string{
"01 345 789",
"123456789 ",
"123456789 ",
"123456789 ",
"123456789",
"",
},
args{
10,
10,
false,
},
},
{
"Long line wrapped to 10 columns, leading spaces omitted when wrapping",
"0123456789 123456789 123456789 123456789 123456789\n",
[]string{
"0123456789",
"123456789 ",
"123456789 ",
"123456789 ",
"123456789",
"",
},
args{
10,
10,
false,
},
},
{
"Intentional leading spaces aren't trimmed",
"01 345\n 789 123456789\n",
[]string{
"01 345",
" 789 ",
"123456789",
"",
},
args{
10,
10,
false,
},
},
{
"Unicode line endings that should not wrap",
"LF\u000A" +
"CR+LF\u000D\u000A" +
"NEL\u0085" +
"LS\u2028" +
"PS\u2029",
[]string{
"LF",
"CR+LF",
"NEL",
"LS",
"PS",
"",
},
args{
10,
10,
false,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := MakePage(url, tt.input, []string{""})
p.WrapContent(tt.args.width-1, tt.args.maxWidth, tt.args.color)
if !reflect.DeepEqual(p.WrappedContent, tt.expects) {
t.Errorf("Test failed - %s\nexpects %s\nactual %s", tt.name, tt.expects, p.WrappedContent)
}
})
}
}

View File

@ -1,3 +1,18 @@
/*
* Copyright (C) 2022 Brian Evans
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package main
import (
@ -38,7 +53,7 @@ func (p *Pages) NavigateHistory(qty int) error {
}
// Add gets passed a Page, which gets added to the history
// arrayr. Add also updates the current length and position
// array. Add also updates the current length and position
// of the Pages struct to which it belongs. Add also shifts
// off array items if necessary.
func (p *Pages) Add(pg Page) {
@ -60,13 +75,17 @@ func (p *Pages) Add(pg Page) {
// Render wraps the content for the current page and returns
// the page content as a string slice
func (p *Pages) Render(termHeight, termWidth int, color bool) []string {
func (p *Pages) Render(termHeight, termWidth, maxWidth int, color bool) []string {
if p.Length < 1 {
return make([]string, 0)
}
pos := p.History[p.Position].ScrollPosition
prev := len(p.History[p.Position].WrappedContent)
p.History[p.Position].WrapContent(termWidth, color)
if termWidth != p.History[p.Position].WrapWidth || p.History[p.Position].Color != color {
p.History[p.Position].WrapContent(termWidth, maxWidth, color)
}
now := len(p.History[p.Position].WrappedContent)
if prev > now {
diff := prev - now
@ -88,6 +107,18 @@ func (p *Pages) Render(termHeight, termWidth int, color bool) []string {
return p.History[p.Position].WrappedContent[pos:]
}
func (p *Pages) CopyHistory(pos int) error {
if p.Length < 2 || pos > p.Position {
return fmt.Errorf("There are not enough history locations available")
}
if pos < 0 {
pos = p.Position-1
}
p.Add(p.History[pos])
return nil
}
//------------------------------------------------\\
// + + + F U N C T I O N S + + + \\
//--------------------------------------------------\\

305
tdiv/tdiv.go Normal file
View File

@ -0,0 +1,305 @@
/*
* Copyright (C) 2022 Brian Evans
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package tdiv
import (
"bytes"
"fmt"
"image"
"image/gif"
"image/jpeg"
"image/png"
"io"
"strings"
)
func getBraille(pattern string) (rune, error) {
switch pattern {
case "000000":
return ' ', nil
case "100000":
return '⠁', nil
case "001000":
return '⠂', nil
case "101000":
return '⠃', nil
case "000010":
return '⠄', nil
case "100010":
return '⠅', nil
case "001010":
return '⠆', nil
case "101010":
return '⠇', nil
case "010000":
return '⠈', nil
case "110000":
return '⠉', nil
case "011000":
return '⠊', nil
case "111000":
return '⠋', nil
case "010010":
return '⠌', nil
case "110010":
return '⠍', nil
case "011010":
return '⠎', nil
case "111010":
return '⠏', nil
case "000100":
return '⠐', nil
case "100100":
return '⠑', nil
case "001100":
return '⠒', nil
case "101100":
return '⠓', nil
case "000110":
return '⠔', nil
case "100110":
return '⠕', nil
case "001110":
return '⠖', nil
case "101110":
return '⠗', nil
case "010100":
return '⠘', nil
case "110100":
return '⠙', nil
case "011100":
return '⠚', nil
case "111100":
return '⠛', nil
case "010110":
return '⠜', nil
case "110110":
return '⠝', nil
case "011110":
return '⠞', nil
case "111110":
return '⠟', nil
case "000001":
return '⠠', nil
case "100001":
return '⠡', nil
case "001001":
return '⠢', nil
case "101001":
return '⠣', nil
case "000011":
return '⠤', nil
case "100011":
return '⠥', nil
case "001011":
return '⠦', nil
case "101011":
return '⠧', nil
case "010001":
return '⠨', nil
case "110001":
return '⠩', nil
case "011001":
return '⠪', nil
case "111001":
return '⠫', nil
case "010011":
return '⠬', nil
case "110011":
return '⠭', nil
case "011011":
return '⠮', nil
case "111011":
return '⠯', nil
case "000101":
return '⠰', nil
case "100101":
return '⠱', nil
case "001101":
return '⠲', nil
case "101101":
return '⠳', nil
case "000111":
return '⠴', nil
case "100111":
return '⠵', nil
case "001111":
return '⠶', nil
case "101111":
return '⠷', nil
case "010101":
return '⠸', nil
case "110101":
return '⠹', nil
case "011101":
return '⠺', nil
case "111101":
return '⠻', nil
case "010111":
return '⠼', nil
case "110111":
return '⠽', nil
case "011111":
return '⠾', nil
case "111111":
return '⠿', nil
default:
return '!', fmt.Errorf("Invalid character entry")
}
}
// scaleImage loads and scales an image and returns a 2d pixel-int slice
//
// Adapted from:
// http://tech-algorithm.com/articles/nearest-neighbor-image-scaling/
func scaleImage(file io.Reader, newWidth int) (int, int, [][]int, error) {
img, _, err := image.Decode(file)
if err != nil {
return 0, 0, nil, err
}
bounds := img.Bounds()
width, height := bounds.Max.X, bounds.Max.Y
newHeight := int(float64(newWidth) * (float64(height) / float64(width)))
out := make([][]int, newHeight)
for i := range out {
out[i] = make([]int, newWidth)
}
xRatio := float64(width) / float64(newWidth)
yRatio := float64(height) / float64(newHeight)
var px, py int
for i := 0; i < newHeight; i++ {
for j := 0; j < newWidth; j++ {
px = int(float64(j) * xRatio)
py = int(float64(i) * yRatio)
out[i][j] = rgbaToGray(img.At(px, py).RGBA())
}
}
return newWidth, newHeight, out, nil
}
// Get the bi-dimensional pixel array
func getPixels(file io.Reader) (int, int, [][]int, error) {
img, _, err := image.Decode(file)
if err != nil {
return 0, 0, nil, err
}
bounds := img.Bounds()
width, height := bounds.Max.X, bounds.Max.Y
var pixels [][]int
for y := 0; y < height; y++ {
var row []int
for x := 0; x < width; x++ {
row = append(row, rgbaToGray(img.At(x, y).RGBA()))
}
pixels = append(pixels, row)
}
return width, height, pixels, nil
}
func errorDither(w, h int, p [][]int) [][]int {
mv := [4][2]int{
[2]int{0, 1},
[2]int{1, 1},
[2]int{1, 0},
[2]int{1, -1},
}
per := [4]float64{0.4375, 0.0625, 0.3125, 0.1875}
var res, diff int
for y := 0; y < h; y++ {
for x := 0; x < w; x++ {
cur := p[y][x]
if cur > 128 {
res = 1
diff = -(255 - cur)
} else {
res = 0
diff = cur // TODO see why this was abs() in the py version
}
for i, v := range mv {
if y+v[0] >= h || x+v[1] >= w || x+v[1] <= 0 {
continue
}
px := p[y+v[0]][x+v[1]]
px = int(float64(diff)*per[i] + float64(px))
if px < 0 {
px = 0
} else if px > 255 {
px = 255
}
p[y+v[0]][x+v[1]] = px
p[y][x] = res
}
}
}
return p
}
func toBraille(p [][]int) []rune {
w := len(p[0]) // TODO this is unsafe
h := len(p)
rows := h / 3
cols := w / 2
out := make([]rune, rows*(cols+1))
counter := 0
for y := 0; y < h-3; y += 4 {
for x := 0; x < w-1; x += 2 {
str := fmt.Sprintf(
"%d%d%d%d%d%d",
p[y][x], p[y][x+1],
p[y+1][x], p[y+1][x+1],
p[y+2][x], p[y+2][x+1])
b, err := getBraille(str)
if err != nil {
out[counter] = ' '
} else {
out[counter] = b
}
counter++
}
out[counter] = '\n'
counter++
}
return out
}
func rgbaToGray(r uint32, g uint32, b uint32, a uint32) int {
rf := float64(r/257) * 0.92126
gf := float64(g/257) * 0.97152
bf := float64(b/257) * 0.90722
grey := int((rf + gf + bf) / 3)
return grey
}
func Render(in []byte, width int) []string {
image.RegisterFormat("jpeg", "jpeg", jpeg.Decode, jpeg.DecodeConfig)
image.RegisterFormat("png", "png", png.Decode, png.DecodeConfig)
image.RegisterFormat("gif", "gif", gif.Decode, gif.DecodeConfig)
w, h, p, err := scaleImage(bytes.NewReader(in), width)
if err != nil {
return []string{"Unable to render image.", "Please download using:", "", " :w ."}
}
px := errorDither(w, h, p)
b := toBraille(px)
out := strings.SplitN(string(b), "\n", -1)
return out
}

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2022 Brian Evans
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// Package telnet provides a function that starts a telnet session in a subprocess.
package telnet

26
termios/consts_linux.go Normal file
View File

@ -0,0 +1,26 @@
// +build linux
/*
* Copyright (C) 2022 Brian Evans
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package termios
import "syscall"
const (
getTermiosIoctl = syscall.TCGETS
setTermiosIoctl = syscall.TCSETS
)

View File

@ -0,0 +1,26 @@
// +build !linux
/*
* Copyright (C) 2022 Brian Evans
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package termios
import "syscall"
const (
getTermiosIoctl = syscall.TIOCGETA
setTermiosIoctl = syscall.TIOCSETAF
)

75
termios/termios.go Normal file
View File

@ -0,0 +1,75 @@
/*
* Copyright (C) 2022 Brian Evans
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package termios
import (
"os"
"runtime"
"syscall"
"unsafe"
)
type winsize struct {
Row uint16
Col uint16
Xpixel uint16
Ypixel uint16
}
var fd = os.Stdin.Fd()
func ioctl(fd, request, argp uintptr) error {
if _, _, e := syscall.Syscall(syscall.SYS_IOCTL, fd, request, argp); e != 0 {
return e
}
return nil
}
func GetWindowSize() (int, int) {
var value winsize
ioctl(fd, syscall.TIOCGWINSZ, uintptr(unsafe.Pointer(&value)))
return int(value.Col), int(value.Row)
}
func getTermios() syscall.Termios {
var value syscall.Termios
err := ioctl(fd, getTermiosIoctl, uintptr(unsafe.Pointer(&value)))
if err != nil {
panic(err)
}
return value
}
func setTermios(termios syscall.Termios) {
err := ioctl(fd, setTermiosIoctl, uintptr(unsafe.Pointer(&termios)))
if err != nil {
panic(err)
}
runtime.KeepAlive(termios)
}
func SetCharMode() {
t := getTermios()
t.Lflag = t.Lflag ^ syscall.ICANON
t.Lflag = t.Lflag ^ syscall.ECHO
setTermios(t)
}
func SetLineMode() {
var t = getTermios()
t.Lflag = t.Lflag | (syscall.ICANON | syscall.ECHO)
setTermios(t)
}

36
url.go
View File

@ -1,8 +1,24 @@
/*
* Copyright (C) 2022 Brian Evans
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package main
import (
"fmt"
"os/user"
"path"
"path/filepath"
"regexp"
"strings"
@ -130,7 +146,7 @@ func MakeUrl(u string) (Url, error) {
out.Mime = "1"
}
switch out.Mime {
case "1", "0", "h", "7":
case "1", "0", "h", "7", "I", "g":
out.DownloadOnly = false
default:
out.DownloadOnly = true
@ -145,6 +161,24 @@ func MakeUrl(u string) (Url, error) {
return out, nil
}
func UpOneDir(u string) string {
url, err := MakeUrl(u)
if len(url.Resource) < 1 || err != nil {
return u
}
if strings.HasSuffix(url.Resource, "/") {
url.Resource = url.Resource[:len(url.Resource)-1]
}
url.Resource, _ = path.Split(url.Resource)
if url.Scheme == "gopher" {
url.Mime = "1"
}
url.Full = url.Scheme + "://" + url.Host + ":" + url.Port + "/" + url.Mime + url.Resource
return url.Full
}
func parseFinger(u string) (Url, error) {
var out Url
out.Scheme = "finger"