Compare commits

...

418 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: sloum/bombadillo#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: sloum/bombadillo#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: sloum/bombadillo#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: sloum/bombadillo#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: sloum/bombadillo#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: sloum/bombadillo#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: sloum/bombadillo#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: sloum/bombadillo#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: sloum/bombadillo#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: sloum/bombadillo#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: sloum/bombadillo#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: sloum/bombadillo#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: sloum/bombadillo#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: sloum/bombadillo#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: sloum/bombadillo#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: sloum/bombadillo#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: sloum/bombadillo#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: sloum/bombadillo#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: sloum/bombadillo#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: sloum/bombadillo#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: sloum/bombadillo#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: sloum/bombadillo#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: sloum/bombadillo#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: sloum/bombadillo#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: sloum/bombadillo#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
Sloom Sloum Sluom IV b4e077869f 2.1.0 Release - Merge branch 'develop' of sloum/bombadillo into master 2020-01-04 14:37:05 -05:00
Sloom Sloum Sluom IV 7e6281d565 Merge branch 'version-update' of sloum/bombadillo into develop 2020-01-04 14:30:15 -05:00
sloumdrone 8d2ebd5c6d Updates version file 2020-01-04 11:28:07 -08:00
Sloom Sloum Sluom IV 6c634415d8 Merge branch 'window-title' of sloum/bombadillo into develop 2020-01-04 14:26:21 -05:00
Sloom Sloum Sluom IV b551138c75 Merge branch 'text-find' of sloum/bombadillo into develop 2020-01-04 14:20:28 -05:00
sloumdrone daacae276c Adds man page documentation for search 2020-01-04 11:19:01 -08:00
sloumdrone ef36895c5d Removes dead code 2020-01-04 10:58:56 -08:00
sloumdrone 71e21543f0 Fixes line wrapping to 80 columns rather than 79 2020-01-04 10:53:24 -08:00
sloumdrone 10f28027dc Working search 2019-12-18 22:17:52 -08:00
asdf 4bb0c84c56 Amended man page and usage details synopsis 2019-12-19 14:57:40 +11:00
sloumdrone 9e7ac3c866 Finally solved infuriating rendering issue 2019-12-17 22:32:45 -08:00
sloumdrone f68996decf Beginnings of text search... semi-working 2019-12-16 22:18:28 -08:00
sloumdrone 4182619a78 Adds flag for displaying title and adds documentation 2019-12-16 19:40:52 -08:00
sloumdrone aeee577e24 Adds title change escapes to cui 2019-12-15 11:00:39 -08:00
Sloom Sloum Sluom IV 8fa7c7a635 Merge branch 'add-color' of sloum/bombadillo into develop 2019-12-14 10:38:15 -05:00
sloumdrone 982fba3019 Updates man page 2019-12-14 07:37:56 -08:00
sloumdrone 9ae6c37707 Removes dead code 2019-12-14 07:26:11 -08:00
sloumdrone f3f406260e Updates escape code handling to be safer plus renders literally in local scheme 2019-12-13 20:19:50 -08:00
sloumdrone 021678d9da Adds support for a new color theme mode 2019-12-10 21:56:41 -08:00
Sloom Sloum Sluom IV ccc694fb2f Merge branch 'refresh-first-pg' of sloum/bombadillo into develop
Merges in a fix to using the _reload_ command when on the first history location. It was previously not working.
2019-12-08 23:21:42 -05:00
Sloom Sloum Sluom IV 786784bf4f Merge branch 'default-scheme' of sloum/bombadillo into develop 2019-12-08 01:23:29 -05:00
sloumdrone 275f7928ea Fixes logic to not be based on length, but on position 2019-12-07 22:20:33 -08:00
sloumdrone d5ff5694d4 Adds an option to set a default scheme 2019-12-06 22:19:25 -08:00
sloumdrone 2c9c00ab8d Makes the first history position reloadable 2019-12-06 21:53:21 -08:00
Sloom Sloum Sluom IV 3a226f3769 Merge branch 'panic-errors' of sloum/bombadillo into develop 2019-12-06 22:44:09 -05:00
asdf 817affcf14 Amended error message wording 2019-12-07 11:14:16 +11:00
sloumdrone fde463be0f Removes unnecessary variable from exit routine 2019-12-05 22:01:22 -08:00
sloumdrone 8b004df1d5 Better specific errors on bootup 2019-12-05 21:55:07 -08:00
sloumdrone 6f0be3b4e4 Adds visible error handling for bad exits and improves exit code use 2019-12-05 21:46:31 -08:00
jboverf c64d06610e Merge branch 'create-config-dir' of sloum/bombadillo into develop
Looks good to me! Thanks for jumping on this so quickly. I also am very confused as to why the panic does not display any output in this case...
2019-12-04 07:39:02 -05:00
asdf 637c23d4cc os.MkallDir now just makes directories 2019-12-04 19:18:01 +11:00
sloumdrone 217b069172 Moves folder creation and error handling to loadConfig 2019-12-03 19:58:18 -08:00
sloumdrone 2900702313 Updates the XDG path creation to create needed dirs and panic on failure 2019-12-03 19:22:15 -08:00
Sloom Sloum Sluom IV c6cab8220a Merge branch 'man-correction' of sloum/bombadillo into develop 2019-12-03 11:34:11 -05:00
asdf c756912388 Small man page corrections as per #110 2019-12-03 19:21:05 +11:00
Sloom Sloum Sluom IV ce3744a22b Merge branch 'develop' of sloum/bombadillo into master 2019-12-01 12:09:59 -05:00
sloumdrone abcd6416d7 Fixes merge conflict 2019-11-30 19:32:32 -08:00
Sloom Sloum Sluom IV 93d9a2c173 Merge branch 'manreview' of sloum/bombadillo into develop 2019-11-30 22:25:43 -05:00
asdf 16d6b8e243 Man page final review 2019-12-01 13:17:03 +11:00
asdf bc8240fdbb Merge branch 'readme-review' of sloum/bombadillo into develop 2019-11-28 21:19:38 -05:00
sloumdrone 0f670623bd Corrects a word in the readme 2019-11-28 12:17:22 -08:00
Sloom Sloum Sluom IV 9880fdffd0 Merge branch 'remove-getCurrentPage-' of sloum/bombadillo into develop 2019-11-28 14:54:19 -05:00
Sloom Sloum Sluom IV 1564a6038b Merge branch 'press-y-quickly' of sloum/bombadillo into develop 2019-11-28 01:03:42 -05:00
asdf 67293823e0 Change c.Visit(u) call to blocking, from goroutine 2019-11-27 18:37:39 +11:00
asdf ebd33c6dbd Remove unused functions getCurrentPageRawData & getCurrentPageUrl 2019-11-26 12:24:16 +11:00
Sloom Sloum Sluom IV 76cf3da4ae Merge branch 'desktop-file' of sloum/bombadillo into develop 2019-11-24 00:29:25 -05:00
asdf 0c0ce2ceb1 Use update-desktop-database instead of xdg-mime 2019-11-24 08:54:10 +11:00
sloumdrone 82b47d8c86 Removes a deprecated category from desktop file 2019-11-22 19:17:21 -08:00
asdf 76b58a2337 Merge branch 'config-loc' of sloum/bombadillo into develop
Tested, no issues. Wording of documentation is good.
2019-11-18 19:07:29 -05:00
Brian Evans 26c4b176c0 Updates the set command to disallow configlocation and updates associated man apge entry 2019-11-18 15:44:32 -08:00
sloumdrone 1509dbeba5 Solves merge conflict 2019-11-17 17:02:39 -08:00
sloumdrone c089041cf8 Makes xdg-mime fail without halting the makefile 2019-11-17 17:01:38 -08:00
Sloom Sloum Sluom IV c9c87f4129 Merge branch 'improved-local' of sloum/bombadillo into develop 2019-11-17 18:05:24 -05:00
sloumdrone 8e4091aac1 Simplifies .. navigation and passes actual errors 2019-11-17 14:58:43 -08:00
sloumdrone c948be18dc Adds better formatting/options for local protocol directory listings 2019-11-16 17:38:11 -08:00
Brian Evans 3ee7bd9c7b Makes local protocol navigable 2019-11-15 14:48:50 -08:00
Brian Evans 35c580c9d0 Removes protocol handler association for non-linux systems 2019-11-15 14:05:10 -08:00
sloumdrone 57ab1b085e Adds a default entry in the mime db for gopher, gemini, and finger 2019-11-14 22:20:12 -08:00
sloumdrone f49f8bebdd Adds desktop entry and icon, plus build target for makefile 2019-11-14 21:46:28 -08:00
Sloom Sloum Sluom IV bb4fed6d6b Merge branch 'web-backends' of sloum/bombadillo into develop 2019-11-15 00:01:19 -05:00
sloumdrone 915bf764aa Merge branch 'web-backends' of https://tildegit.org/sloum/Bombadillo into web-backends 2019-11-14 20:35:51 -08:00
sloumdrone aa46cc2600 Removed unneeded settings 2019-11-14 20:35:34 -08:00
sloumdrone 88f26ff92b Adds bombadillo desktop settings and icon 2019-11-14 20:28:48 -08:00
asdf 33a8c51de1 Further changes to the man page for webmode 2019-11-15 15:19:47 +11:00
sloumdrone ef27e54d0e Fixes errors introduced by a distracted commit earlier, also handles linting 2019-11-14 19:30:58 -08:00
Brian Evans e6f1ecd41c Fixes merge conflict 2019-11-14 10:12:29 -08:00
Brian Evans 24fd98fa9b Added manpage updates as well as an update to using the gui for opening web content 2019-11-14 10:10:05 -08:00
asdf 9d29acc8e8 Review of webmode, corrections to error messages and comments 2019-11-14 21:22:17 +11:00
asdf e04ea285fd Merge branch 'language-normalization' of sloum/bombadillo into develop
Merging in, this looks good. I made an additional change for the `printHelp()` function in main.go.
2019-11-14 04:24:56 -05:00
sloumdrone e4324c6863 Changes references to client to browser in manpage and readme 2019-11-14 20:21:00 +11:00
sloumdrone 28c2d6b277 Diversifies the web rendering backends and removes confusing configuration options 2019-11-13 22:23:57 -08:00
Sloom Sloum Sluom IV 0281458763 Merge branch 'manreview' of sloum/bombadillo into develop 2019-11-13 21:42:39 -05:00
asdf 002516bcd2 Final review of man page 2019-11-14 13:10:33 +11:00
asdf bf446289f1 Merge branch 'fix-footbar' of sloum/bombadillo into develop
Fixes #88
2019-11-13 20:07:12 -05:00
Brian Evans 0b63b85a1a HOT FIX: Solves issue where input bar disappears on empty search input 2019-11-13 11:25:13 -08:00
sloumdrone 4ae093d3e3 Adds in missing calls to draw the message 2019-11-12 21:16:19 -08:00
Sloom Sloum Sluom IV a06ad4ee8d Merge branch 'remove-escapes' of sloum/bombadillo into develop 2019-11-12 09:52:16 -05:00
asdf 45400a1935 Merge branch 'add-xdg-config' of sloum/bombadillo into develop
Fix #77
2019-11-12 01:58:09 -05:00
asdf 0239fdceba Add xdg config home functionality, documentation 2019-11-12 17:51:55 +11:00
asdf 198f357d1d Merge branch 'support-different-go-versions' of sloum/bombadillo into develop 2019-11-11 22:23:12 -05:00
asdf 494a70859e Incorporated feedback 2019-11-12 14:21:10 +11:00
Sloom Sloum Sluom IV c36ded496f Merge branch 'run-format' of sloum/bombadillo into develop 2019-11-11 21:42:29 -05:00
asdf 36e52477eb Further generalisation of development advice 2019-11-12 13:05:15 +11:00
asdf 522331da28 Rebasing to latest 2019-11-12 12:48:56 +11:00
asdf 45eabef945 Added developer documentation, testing to Makefile 2019-11-12 12:48:39 +11:00
Brian Evans 34e9e109e2 Correcting an error message 2019-11-11 16:07:50 -08:00
sloumdrone 6d9ae540e3 Responds to GoMetaLinter for more of the codebase. 2019-11-10 16:18:27 -08:00
sloumdrone 3a33320ec5 Removed dead code and empty file 2019-11-10 14:14:45 -08:00
sloumdrone f5c5dcd736 Handles a number of linter errors 2019-11-10 11:35:52 -08:00
sloumdrone e4f7147e4f Ran format on each file 2019-11-10 10:41:12 -08:00
sloumdrone 2038c9c4ac HOTFIX: Removes incorrect draw of error message 2019-11-09 19:27:15 -08:00
asdf bb74613323 Merge branch 'save-path-updates' of sloum/bombadillo into develop 2019-11-08 00:30:11 -05:00
asdf ecef63f099 Use word directory instead of folder, correct savepath info 2019-11-08 16:20:54 +11:00
sloumdrone ba9a016dfd Removes the WRITE AS command and updates the man page to reflect the change 2019-11-07 20:11:25 -08:00
Sloom Sloum Sluom IV 57c4e05e13 Merge branch 'prevent-telnet-breaking-terminal' of sloum/bombadillo into develop 2019-11-07 09:43:27 -05:00
asdf b35ceb14d6 Added developer documentation, testing to Makefile 2019-11-07 22:29:22 +11:00
asdf a8fc394a3d Prevent changes in telnet breaking terminal display 2019-11-07 21:17:50 +11:00
Sloom Sloum Sluom IV ed5c267847 Merge branch 'support-different-go-versions' of sloum/bombadillo into develop 2019-11-07 00:57:07 -05:00
asdf b240d8340e Makefile has GOCMD variable for supporting multiple versions of Go 2019-11-07 13:22:06 +11:00
asdf 5e8cebaaaa Merge branch 'extend-makefile' of sloum/bombadillo into develop 2019-11-04 22:32:04 -05:00
asdf 8e9f2f614f Amended README, corrected error in Makefile 2019-11-05 13:32:08 +11:00
Sloom Sloum Sluom IV 0d804893d1 Merge branch 'dont-overwrite-files' of sloum/bombadillo into develop 2019-11-04 20:26:44 -05:00
Sloom Sloum Sluom IV 4191918bb5 Merge branch 'remove-replace-all' of sloum/bombadillo into develop 2019-11-04 00:51:25 -05:00
asdf 23a78b45b3 Amended Makefile to include additional variables as per #66 2019-11-04 14:05:59 +11:00
asdf fcc58647b1 Merge branch 'update-readme' of sloum/bombadillo into develop
I'll merge this in now, and we can replace the image when something nicer comes up. asciinema would be a good choice, ideally something ambient.
2019-11-03 20:24:05 -05:00
sloumdrone aaa37cbb9f Removes usage of library methods that are only available in 1.12 2019-11-03 16:42:16 -08:00
sloumdrone eef9ba6e81 Removes debugging code 2019-11-02 22:15:31 -07:00
sloumdrone 3a96792cf3 Moved repeated logic into function 2019-11-02 22:14:10 -07:00
sloumdrone b998fa9e7c Adds suffixing for file writes 2019-11-02 20:18:21 -07:00
sloumdrone 65101563d9 Removes ascii escape codes from remote content 2019-11-02 19:45:55 -07:00
asdf 3b74654c3d Add image to README.md 2019-11-03 12:39:56 +11:00
asdf 20d2c706b4 Merge branch 'update-readme' of sloum/bombadillo into develop 2019-11-01 23:47:47 -04:00
asdf bd774ee7e1 Merge branch 'update-readme' of ttm.sh:sloum/bombadillo into update-readme 2019-11-02 14:37:56 +11:00
asdf 743d1cec44 Further changes from review feedback. 2019-11-02 14:18:41 +11:00
asdf 56ec4962ac Expanded the contributing section 2019-11-02 14:18:41 +11:00
asdf 24deae900e Initial review 2019-11-02 14:18:41 +11:00
Brian Evans 61907aed1e Updates readme to be more current with the develop branch 2019-11-02 14:18:41 +11:00
sloumdrone 867db2bbe9 Updates readme to better fit the 2.0.0 build methodology 2019-11-02 14:18:41 +11:00
asdf a9654a6804 Expanded the contributing section 2019-11-01 13:32:59 +11:00
asdf 0bd731c870 Merge branch 'minor-lynxmode-fix' of sloum/bombadillo into develop 2019-10-30 23:31:56 -04:00
asdf ab6037d39d Initial review 2019-10-29 21:19:14 +11:00
asdf 25d1e13bba Merge branch 'fix-path-issues' of sloum/bombadillo into develop 2019-10-29 06:03:51 -04:00
sloumdrone 3efde7d061 Fixes a glitch that was preventing redirects from getting shown in lynxmode 2019-10-28 22:14:11 -07:00
Brian Evans f4e5209f24 Updates readme to be more current with the develop branch 2019-10-28 11:48:16 -07:00
asdf 5779338ac5 Merge branch 'develop' into fix-path-issues 2019-10-28 22:04:35 +11:00
asdf 8b0d87f30c Reverting changes to client.go 2019-10-28 22:03:04 +11:00
Sloom Sloum Sluom IV 86265454d3 Merge branch 'remove-mailcap' of sloum/bombadillo into develop 2019-10-28 00:41:22 -04:00
asdf b441ac4624 go.mod, go.sum mailcap cleanup, version decrement 2019-10-28 11:21:07 +11:00
asdf 73c77fd625 Corrected spelling error 2019-10-28 11:10:03 +11:00
sloumdrone 9c2caf4a6f Updates man page 2019-10-27 11:09:45 -07:00
Sloom Sloum Sluom IV e81a494523 Merge branch 'lynx-web-mode' of sloum/bombadillo into develop 2019-10-27 10:15:55 -04:00
asdf be4741895e Found and addressed some possible path issues 2019-10-27 23:24:52 +11:00
asdf 9d77c3cea0 Removed empty line at end of man page 2019-10-27 11:48:26 +11:00
asdf ceeaa2d0f0 Spelling/syntax errors, suggested rewrite of web section 2019-10-27 11:31:06 +11:00
sloumdrone 67d7df6804 Reworks the protocol handling and makes html downloads work 2019-10-25 20:06:13 -07:00
asdf d727f22fe7 Merge branch 'handle-signals' of sloum/bombadillo into develop
Merging in after review completed.
2019-10-24 22:01:41 -04:00
sloumdrone e5c0bd7443 Updated man page 2019-10-23 19:52:22 -07:00
asdf e32ba04a1a Missed the for loop...plus some documentation 2019-10-24 13:42:38 +11:00
asdf a393b00ad1 Handle more signals without breaking terminal 2019-10-24 13:25:31 +11:00
sloumdrone 224b595a6f Resolves conflict in defaults.go with upstream branch 2019-10-23 19:00:12 -07:00
Sloom Sloum Sluom IV 24e2509831 Merge branch 'command-line-cleanup' of asdf/bombadillo into develop 2019-10-23 21:57:23 -04:00
asdf b18cc8cfac Merge branch 'command-line-cleanup' of ttm.sh:asdf/bombadillo into command-line-cleanup 2019-10-24 12:20:26 +11:00
asdf 3f0bf1ec41 Unified wording for -v option 2019-10-24 12:19:16 +11:00
asdf 5b1f8984b7 Unified wording, updated man page 2019-10-24 12:19:16 +11:00
asdf 2c6a0a85ee Added printHelp, provide more info on -h and -v 2019-10-24 12:19:16 +11:00
Sloom Sloum Sluom IV 18c2fc1322 Merge branch 'makefile' of sloum/bombadillo into develop 2019-10-23 09:50:06 -04:00
sloumdrone 231ccc36d3 Ran go fmt on a few files 2019-10-22 22:13:08 -07:00
sloumdrone 552e211139 Adds web support in lynx mode 2019-10-22 22:02:32 -07:00
asdf 2b6a738d04 Added uninstall information 2019-10-23 13:54:24 +11:00
asdf 1bc6e75ada Unified wording for -v option 2019-10-22 15:07:37 +11:00
asdf 17b1e6eff2 Unified wording, updated man page 2019-10-22 15:00:01 +11:00
asdf 95837e108a Added printHelp, provide more info on -h and -v 2019-10-22 14:45:39 +11:00
asdf d5246a7d3e Corrected troubleshooting information 2019-10-22 14:02:26 +11:00
asdf 68a91c58f2 Updated README.md to reflect new build options 2019-10-22 13:55:33 +11:00
asdf 1031805bfc Ammended variables to include prefix and destdir 2019-10-21 13:23:58 +11:00
sloumdrone d22d8bf406 Updated uninstall logic to work better, particularly with non-gopath install points 2019-10-16 19:23:39 -07:00
sloumdrone 1c0295e32e Updates readme to better fit the 2.0.0 build methodology 2019-10-15 20:07:54 -07:00
sloumdrone cc27c82703 HOT FIX: Removes defaults from man file since they can be set at compile time. Users should use CHECK to see settings. 2019-10-15 19:36:50 -07:00
Sloom Sloum Sluom IV 899702ad9c Merge branch 'man-page' of sloum/bombadillo into develop
Merges in more complete documentation, removes dead files,etc
2019-10-15 22:30:44 -04:00
Brian Evans 0fc0dddb97 Adds some a bit about config location being variable 2019-10-15 13:26:05 -07:00
Brian Evans 33bca45b98 Makes makefile structure more modular and fixes clean to not remove installed binary 2019-10-15 09:31:48 -07:00
Brian Evans 32e769c9da Moves custom config path into defaults.go from makefile 2019-10-15 08:53:47 -07:00
sloumdrone 6609cca70e Removed a v from the sprintf for version output. 2019-10-14 20:34:20 -07:00
sloumdrone 3e6c61dcdc Remaps help to the user guide at bombadillo.colorfield.space 2019-10-14 20:27:40 -07:00
sloumdrone 59aa8a1ef2 Removes old files, updates default homepage, and more makefile stuff 2019-10-14 20:25:32 -07:00
Brian Evans 7ed2b46b32 First go at a makefile, woo-hoo! 2019-10-14 16:29:40 -07:00
sloumdrone 2dc64684bb More descriptive error msg 2019-10-13 22:23:04 -07:00
sloumdrone e44666cd05 Updates man file 2019-10-12 14:52:51 -07:00
Sloom Sloum Sluom IV 9e8e7ee5b1 Merge branch 'improved-cert-error-messaging' of sloum/bombadillo into develop 2019-10-12 14:49:40 -04:00
sloumdrone 3537880242 Corrected lexer commands 2019-10-12 11:48:16 -07:00
sloumdrone 3af224056a Improves certificate error messaging 2019-10-11 21:33:57 -07:00
sloumdrone 74ada2b8ed HOT FIX: Returned url regex to rightful state and fixed numerical issue with check command 2019-10-10 22:07:32 -07:00
sloumdrone 207a45d678 HOT FIX: Removes underwrite for lines when bookmarks is closed 2019-10-10 21:28:51 -07:00
Sloom Sloum Sluom IV 93ec221af9 Merge branch 'handle-ctrlz' of asdf/bombadillo into develop
Closes sloum/bombadillo#48
2019-10-10 22:49:06 -04:00
asdf 6faf4e5205 fixes screen display properly, better start location 2019-10-11 12:04:19 +11:00
asdf 86485154c9 Inital try at handling SIGCONT 2019-10-10 17:09:36 +11:00
Sloom Sloum Sluom IV 282bd9d246 Merge branch 'file-mode' of sloum/bombadillo into develop 2019-10-09 21:23:22 -04:00
sloumdrone 4a8c9cd074 Merges updates from develop into file-mode so that merge into develop can happen 2019-10-09 18:20:01 -07:00
Sloom Sloum Sluom IV b48ad3168e Merge branch 'client-certs' of sloum/bombadillo into develop 2019-10-09 11:45:47 -04:00
Sloom Sloum Sluom IV c9036fac68 Merge branch 'fix-write-files' of asdf/bombadillo into develop 2019-10-09 11:44:38 -04:00
asdf 60dafcceba Fix using path/filepath Join function 2019-10-09 17:36:41 +11:00
Sloom Sloum Sluom IV ff948b829d Merge branch 'fix-render-bug' of sloum/bombadillo into develop 2019-10-08 22:53:54 -04:00
sloumdrone d862b16863 Fixes bug where screan flashes clear when bookmarks are toggled closed 2019-10-08 19:52:06 -07:00
Sloom Sloum Sluom IV ef82f45772 Merge branch 'finger-support' of sloum/bombadillo into develop 2019-10-08 22:19:33 -04:00
asdf 1045829076 Simple fix applied, expanded error message, gofmt 2019-10-08 18:13:01 +11:00
sloumdrone b2a8e7dba5 Adds basic finger protocol support to Bombadillo 2019-10-06 19:59:46 -07:00
sloumdrone b5fc017978 Added refresh feature 2019-10-05 17:55:15 -07:00
sloumdrone ee9fc8332c Adds support for listing directories as well as getting file contents 2019-10-05 13:10:09 -07:00
sloumdrone 484fb77aa3 Adds support for local files, making bombadillo a functional pager 2019-10-04 22:50:06 -07:00
sloumdrone 3b1065f384 Updating go module declaration to require go 1.12+ 2019-10-02 19:37:39 -07:00
sloumdrone c12bc16015 Sets certificate to update as SET is called 2019-10-02 19:25:29 -07:00
Sloom Sloum Sluom IV 66acf6102f Merge branch 'tofu' of sloum/bombadillo into develop
Adds the following:
- Storing/retrieving of TLS certs from servers in `.bombadillo.ini`
- `purge` command is added to clear out stale certificates
- Bombadillo now validates that certs match the ones on file
- Bombadillo now validates certificates are within valid window
- Bombadillo now validates that certificate hostnames match the host that is requested
- Logic to get a new certificate (if one is presented) when an expired certificate is still present
- Logic to get a certificate if none is present
2019-10-02 15:49:35 -04:00
Brian Evans 5539f6c2c6 Switches the way client certs are provided 2019-10-02 09:24:01 -07:00
sloumdrone df793c78f2 Adds basic functioning client cert, but always sends. Would prefer to only send on ask. 2019-10-01 21:38:13 -07:00
sloumdrone 8edf886488 Fixes a logical isssue where I was checking an errors value, but the error may have been nil and therefore had no value 2019-09-28 16:58:42 -07:00
sloumdrone 2fd2d6c722 Added command for user purging of certificates, and fixed multiword search 2019-09-28 16:29:42 -07:00
sloumdrone bd004d74c2 Refactors TOFU approach 2019-09-28 10:20:23 -07:00
sloumdrone 6c52299c7a Fixes a few logical issues and order of op 2019-09-27 19:19:23 -07:00
sloumdrone bef32b7ff5 Adds basic tofu certificate pinning 2019-09-26 22:08:57 -07:00
sloumdrone 4c92870790 Cleans up url struct creation a bit 2019-09-26 18:40:14 -07:00
sloumdrone 7896858fac Updated url regex to allow for any scheme 2019-09-26 18:30:44 -07:00
sloumdrone 5e68d81a63 Starts building out a tofu system for certificate pinning 2019-09-24 21:23:44 -07:00
sloumdrone df2a7fbd05 Adds validation to setting and loading configuration options. 2019-09-23 21:18:46 -07:00
sloumdrone b219e659ab Updated readme contributors language 2019-09-23 09:11:17 -07:00
sloumdrone dd4849e6b3 Updated readme with release information and manpage information 2019-09-23 09:09:47 -07:00
sloumdrone e68dc3b414 Fixes merge conflicts and removes dead files 2019-09-23 09:02:48 -07:00
sloumdrone 74473ff309 Returns license to gpl3 2019-09-23 08:49:33 -07:00
sloumdrone b66dd3baa9 Fixes relative linking for gemini 2019-09-22 15:08:15 -07:00
sloumdrone ff209c4ae3 Adds status 1 support to gemini, fixes bug in url where gophertypes were getting thrown out of nongopher addresses, and fixes up relative linking in gemini maps 2019-09-21 22:02:20 -07:00
sloumdrone 2f14011a48 Adds temporary ability to not add invalid value for theme. Also adds a temporary first draft of a manpage for bombadillo. 2019-09-21 14:12:18 -07:00
sloumdrone 1050b858dd Finished working through file saving for gopher and gemini, and added mailcap functionality to gemini 2019-09-20 18:24:12 -07:00
sloumdrone db1cf75d2e Cleans up some display issues 2019-09-20 16:15:53 -07:00
sloumdrone 19f210f243 Renames a cui function to be more appropriate and adds command line flag for version number 2019-09-20 09:18:16 -07:00
sloumdrone 8c42748432 Fixes issue where percent read was incorrect when moving through history 2019-09-19 21:29:52 -07:00
sloumdrone fdaf6312ab Adds a terminal mode change to disallow line wrapping by the terminal, also fixes a resize scroll issue and disallows escape characters in text files 2019-09-19 20:29:17 -07:00
sloumdrone 0879592015 Adds search with terms inline, also gemini file rendering 2019-09-19 15:32:26 -07:00
sloumdrone 5114ac1a15 Updates gophermap rendering to support gemini urls as h URL:... style links 2019-09-18 22:03:19 -07:00
sloumdrone 7e4a32c67a Adds buggy but present gemini support 2019-09-18 20:27:56 -07:00
sloumdrone 8a3ddad58e Added ability to view a link's url with the check command 2019-09-17 21:57:21 -07:00
sloumdrone 21fe5714a3 Bookmarks can be scrolled when focused 2019-09-16 19:38:07 -07:00
sloumdrone 7e53ce6aea Fixed broken simple command: b 2019-09-16 09:53:13 -07:00
sloumdrone 1af11f1b8f Removes duplicated code in messaging system 2019-09-15 21:26:32 -07:00
sloumdrone e7a1b4e348 Fixes order of opperations issue when drawing messages 2019-09-15 21:24:45 -07:00
Sloom Sloum Sluom IV 4ea753561f Merge branch 'incorrect_line_wrapping_endash' of asdf/bombadillo into master 2019-09-14 20:57:30 -04:00
sloumdrone cb3bcc9465 Relicenses bobmadillo 2019-09-14 15:45:23 -07:00
asdf a5c14b9b7c Finalising change 2019-09-15 08:42:58 +10:00
sloumdrone f2f730f3c5 Vast improvements, still squashing bugs like crazy. 2019-09-13 22:56:38 -07:00
sloumdrone 98e34576ca Worked out resizing and wrapping bugs. Now hard wraps, rather than soft. 2019-09-12 20:57:48 -07:00
sloumdrone bccca61ec2 Some level of screen draw now works 2019-09-11 22:53:36 -07:00
asdf 45596d4e8d Using slice of runes for calculating lengths 2019-09-12 12:49:51 +10:00
asdf 38c9721817 this is a test only for wraplines rune tests 2019-09-11 15:36:56 +10:00
sloumdrone b7d7d021ed Begun work on gopher module 2019-09-10 22:30:29 -07:00
sloumdrone a6d1f45be8 Added clarifying comments 2019-09-10 20:23:44 -07:00
sloumdrone 84631a38da Adds telnet and http modules, updates visit method on client 2019-09-10 20:13:30 -07:00
Sloom Sloum Sluom IV 2641b03295 Merge branch 'line_wrapping_alignment' of asdf/bombadillo into master
This keeps proper spaces, including multiple spaces in a row, when wrapping text. This is a big improvement.

TODO in a future PR:
- Wrap long lines with no spaces properly
- Handle multispace characters (unicode) properly
- Handle tabs
- Handle extra indent of wrapped link lines on gophermaps
2019-09-10 11:16:11 -04:00
asdf 98cfd120ca corrected test description 2019-09-10 21:10:52 +10:00
asdf 864bb7c2c0 added unit test 2019-09-10 21:06:14 +10:00
asdf 0894827563 simple line wrapping and associated unit tests 2019-09-10 20:24:16 +10:00
asdf ea096af511 simple line wrapping that does not mangle spaces 2019-09-10 20:19:45 +10:00
sloumdrone da45f627e0 Initial v2 commit, deep in restructuring... maybe not for the better? 2019-09-09 19:35:16 -07:00
asdf 3e8587f039 using strings.SplitAfter in wrapLines 2019-09-07 23:04:20 +10:00
Sloom Sloum Sluom IV d1360fa0db Merge branch 'msg-bar-tab-fix' of sloum/bombadillo into master
Closes #32
2019-09-06 12:14:46 -04:00
asdf a9d5651e3c added test for a specific line wrapping issue 2019-09-06 20:18:03 +10:00
asdf be34a9a809 identified more issues, added tests 2019-09-05 21:52:12 +10:00
asdf 319138b189 Addtional test for benchmarking the wrapLines() function 2019-09-05 10:48:47 +10:00
asdf fb2130518c first attempt on line wrapping indents issue 2019-09-04 22:57:26 +10:00
sloumdrone adf29bda02 Should solve tab in url issue re msg bar overflowing onto second line 2019-09-03 20:18:02 -07:00
53 changed files with 4773 additions and 1724 deletions

2
.gitignore vendored
View File

@ -1 +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).

53
LICENSE
View File

@ -619,56 +619,3 @@ Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

71
Makefile Normal file
View File

@ -0,0 +1,71 @@
GOCMD := go
BINARY := bombadillo
PREFIX := /usr/local
EXEC_PREFIX := ${PREFIX}
BINDIR := ${EXEC_PREFIX}/bin
DATAROOTDIR := ${PREFIX}/share
MANDIR := ${DATAROOTDIR}/man
MAN1DIR := ${MANDIR}/man1
test : GOCMD := go1.11.13
# Use a dateformat rather than -I flag since OSX
# does not support -I. It also doesn't support
# %:z - so settle for %z.
BUILD_TIME := ${shell date "+%Y-%m-%dT%H:%M%z"}
.PHONY: build
build:
${GOCMD} build -o ${BINARY}
.PHONY: install
install: install-bin install-man install-desktop clean
.PHONY: install-man
install-man: bombadillo.1
gzip -c ./bombadillo.1 > ./bombadillo.1.gz
install -d ${DESTDIR}${MAN1DIR}
install -m 0644 ./bombadillo.1.gz ${DESTDIR}${MAN1DIR}
.PHONY: install-desktop
install-desktop:
ifeq ($(shell uname), Linux)
# These steps will not work on Darwin, Plan9, or Windows
# They would likely work on BSD systems
install -d ${DESTDIR}${DATAROOTDIR}/applications
install -m 0644 ./bombadillo.desktop ${DESTDIR}${DATAROOTDIR}/applications
install -d ${DESTDIR}${DATAROOTDIR}/pixmaps
install -m 0644 ./bombadillo-icon.png ${DESTDIR}${DATAROOTDIR}/pixmaps
-update-desktop-database 2> /dev/null
else
@echo "* Skipping protocol handler associations and desktop file creation for non-linux system *"
endif
.PHONY: install-bin
install-bin: build
install -d ${DESTDIR}${BINDIR}
install -m 0755 ./${BINARY} ${DESTDIR}${BINDIR}
.PHONY: clean
clean:
${GOCMD} clean
rm -f ./bombadillo.1.gz 2> /dev/null
rm -f ./${BINARY}_* 2> /dev/null
.PHONY: uninstall
uninstall: clean
rm -f ${DESTDIR}${MAN1DIR}/bombadillo.1.gz
rm -f ${DESTDIR}${BINDIR}/${BINARY}
rm -f ${DESTDIR}${DATAROOTDIR}/applications/bombadillo.desktop
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

126
README.md
View File

@ -1,57 +1,139 @@
# Bombadillo
# Bombadillo - a non-web browser
Bombadillo is a modern [Gopher](https://en.wikipedia.org/wiki/Gopher_(protocol)) client for the terminal, and functions as a pager/terminal UI. Bombadillo features vim-like keybindings, configurable settings, and a robust command selection. Bombadillo is under active development.
Bombadillo is a non-web browser for the terminal.
![a screenshot of the bombadillo browser](bombadillo-screenshot.png)
Bombadillo features a full terminal user interface, vim-like keybindings, document pager, configurable settings, and a robust command selection.
Currently, Bombadillo supports the following protocols as first class citizens:
* gopher
* gemini
* finger
* local (a user's file system)
Support for the following protocols is also available via integration with 3rd party applications:
* telnet
* Links are opened in a telnet application run as a subprocess.
* http/https
* Web support is opt-in (turned off by default).
* Links can be opened in a user's default web browser when in a graphical environment.
* Web pages can be rendered directly in Bombadillo if [Lynx](https://lynx.invisible-island.net/), [w3m](http://w3m.sourceforge.net/), or [elinks](http://elinks.or.cz/) are installed on the system to handle the document parsing.
## Getting Started
These instructions will get a copy of the project up and running on your local machine.
These instructions will get a copy of the project up and running on your local machine. The following only applies if you are building from source (rather than using a precompiled binary).
### Prerequisites
If building from source, you will need to have [Go](https://golang.org/) version >= 1.11. Bombadillo uses the module system, so if using 1.11 you will need to have that feature enabled. If using a version > 1.11, you already have modules enabled.
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)
Bombadillo does not use any outside dependencies beyond the Go standard library.
### Building, Installing, Uninstalling
### Installing
Bombadillo installation uses `make`. It is also possible to use Go to build and install (i.e `go build`, `go install`), but this is not the recommended approach.
Assuming you have `go` installed, run the following:
Running `make` from the source code directory will build Bombadillo in the local directory. This is fine for testing or trying things out. For usage system-wide, and easy access to documentation, follow the installation instructions below.
```
#### Basic Installation
Most users will want to install using the following commands:
```shell
git clone https://tildegit.org/sloum/bombadillo.git
cd bombadillo
go install
sudo make install
```
*Note: the usage of `sudo` here will be system dependent. Most systems will require it for installation to `/usr/local/bin`.*
Once you have done that you should, assuming `go install` is set up to install to a place on your path, you should be able to run the following from anywhere on your system to use Bombadillo:
```
You can then start Bombadillo by running the command:
```shell
bombadillo
```
To familiarize yourself with the application, documentation is available by running the command:
```shell
man bombadillo
```
#### Custom Installation
##### Configuration Options
There are a number of default configuration options in the file `defaults.go`, allowing customisation of the default settings for users of Bombadillo.
To use this feature, amend the `defaults.go` file as appropriate, then follow the standard install instructions.
Full documentation for these options is contained within the `defaults.go` file.
An administrator might use this to feature to set a default for all users of a system. Typically though, these options should not need changing, and a user may change most of these settings themselves once they start Bombadillo. The one option that can only be configured in `defaults.go` is `configlocation` which controls where `.bombadillo.ini` is stored.
##### Override Install Location
The installation location can be overridden at compile time, which can be very useful for administrators wanting to set up Bombadillo on a multi-user machine.
```shell
git clone https://tildegit.org/sloum/bombadillo.git
cd bombadillo
sudo make install PREFIX=/some/directory
```
There are two things to know about when using the above format:
1. The above would install Bombadillo to `/some/directory/bin`, _not_ to `/some/directory`. So you will want to make sure your `$PATH` is set accordingly.
2. Using the above will install the man page to `/some/directory/share/man/man1`, rather than its usual location. You will want to update your `manpath` accordingly.
There are other overrides available - please review the [Makefile](Makefile) for more information.
#### Uninstall
If you used the Makefile to install Bombadillo then uninstalling is very simple. From the Bombadillo source folder run:
```shell
sudo make uninstall
```
If you used a custom `PREFIX` value during install, you will need to supply it when uninstalling:
```shell
sudo make uninstall PREFIX=/some/directory
```
Uninstall will clean up any build files, remove the installed binary, and remove the man page from the system. It will _not_ remove any directories created as a part of the installation, nor will it remove any Bombadillo user configuration files.
#### Troubleshooting
If you run `bombadillo` and get `bombadillo: command not found`, try running `go build` from within the cloned repo. Then try: `./bombadillo`. If that works it means that Go does not install to your path. `go build` added an executable file to the repo directory. Move that file to somewhere on your path. I suggest `/usr/local/bin` on most systems, but that may be a matter of personal preference.
If you run `bombadillo` and get `bombadillo: command not found`, try running `make` from within the cloned repo. Then try: `./bombadillo`. If that works it means that the application is getting built correctly and the issue is likely in your path settings. Any errors during `make install` should be visible, and you will be able to see what command it failed on.
### Downloading
If you would prefer to download a binary for your system, rather than build from source, please visit the [Bombadillo downloads](https://rawtext.club/~sloum/bombadillo.html#downloads) page. Don't see your OS/architecture? Bombadillo can be built for use with any POSIX compliant system that is supported as a target for the Go compiler (Linux, BSD, OS X, Plan 9). No testing has been done for Windows. The program will build, but will likely not work properly outside of the Linux subsystem. If you are a Windows user and would like to do some testing or get involved in development please reach out or open an issue.
If you would prefer to download a binary for your system, rather than build from source, please visit the [Bombadillo releases](http://bombadillo.colorfield.space/releases) page. Don't see your OS/architecture? Bombadillo can be built for use with any system that is supported as a target for the Go compiler (Linux, BSD, OS X, Plan 9). There is no explicit support for, or testing done for, Windows or Plan 9. The program should build on those systems, but you may encounter unexpected behaviors or incompatibilities.
### Documentation
Bombadillo has documentation available in three places currently. The first is the [Bombadillo homepage](https://rawtext.club/~sloum/bombadillo.html#docs), which has lots of information about the program, links to places around Gopher, and documentation of the commands and configuration options.
Bombadillo's primary documentation can be found in the man entry that installs with Bombadillo. To access it run `man bombadillo` after first installing Bombadillo. If for some reason that does not work, the document can be accessed directly from the source folder with `man ./bombadillo.1`.
Secondly, and possibly more importantly, documentation is available via Gopher from within Bombadillo. When a user launches Bombadillo for the first time, their `homeurl` is set to the help file. As such they will have access to all of the key bindings, commands, and configuration from the first run. A user can also type `:?` or `:help` at any time to return to the documentation. Remember that Bombadillo uses vim-like key bindings, so scroll with `j` and `k` to view the docs file.
Lastly, this repo contains a file `bombadillo-info`. This is a duplicate of the help file that is hosted over gopher mentioned above. Per user request it has been added to the repo so that pull requests can be created with updates to the documentation.
The longterm hope is to create an installer of some sort that will move bombadillo onto a users path (compiling if need be) and installing a man file (yet to be created) onto their system. There is also talk about being able to open local files and use bombadillo as a pager, which would enable linking in the included help file.
In addition to the man page, users can get information on Bombadillo on the web @ [http://bombadillo.colorfield.space](http://bombadillo.colorfield.space). Running the command `help` inside Bombadillo will navigate a user to the gopher server hosted at [bombadillo.colorfield.space](gopher://bombadillo.colorfield.space); specifically the user guide.
## Contributing
Bombadillo development is largely handled by Sloum, with help from jboverf and some community input. If you would like to get involved, please reach out or submit an issue. At present the developers use the tildegit issues system to discuss new features, track bugs, and communicate with users about hopes and/or issues for/with the software.
Bombadillo development is largely handled by Sloum, with help from asdf, jboverf, and community input.
There are many ways to contribute to Bombadillo, including a fair few that don't require knowledge of programming:
- Try out the browser and let us know if you have a suggestion for improvement, or if you find a bug.
- Read the documentation and let us know if something isn't well explained, or needs correction.
- Maybe you have a cool logo or some art that you think would look nice.
If you have something in mind, please reach out or [open an issue](https://tildegit.org/sloum/bombadillo/issues).
We aim for simplicity and quality, and do our best to make Bombadillo useful to its users. Any proposals for change are reviewed by the maintainers with this in mind, and not every request will be accepted. Furthermore, this software is developed in our spare time for the good of all, and help is provided as best efforts. In general, we want to help!
The maintainers use the [tildegit](https://tildegit.org) issues system to discuss new features, track bugs, and communicate with users regarding issues and suggestions. Pull requests should typically have an associated issue, and should target the `develop` branch.
## Development
See [DEVELOPING.md](DEVELOPING.md) for information on how changes to Bombadillo are made, along with other technical information for developers.
## License
This project is licensed under the GNU GPL version 3- see the [LICENSE](LICENSE) file for details.
This project is licensed under the GNU GPL version 3. See the [LICENSE](LICENSE) file for details.
## Releases
Starting with version 2.0.0 releases into `master` will be version-tagged. Work done toward the next release will be created on work branches named for what they are doing and then merged into `develop` to be combined with other ongoing efforts before a release is merged into `master`. At present there is no specific release schedule. It will depend on the urgency of the work that makes its way into develop and will be up to the project maintainers' judgement when to release from `develop`.

1
VERSION Normal file
View File

@ -0,0 +1 @@
2.3.3

BIN
bombadillo-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -1,162 +0,0 @@
i false null.host 70
i false null.host 70
i **** bombadillo **** false null.host 70
i false null.host 70
ibombadillo is a gopher client for the terminal. it functions as a pager false null.host 70
iwith a "full screen" terminal user interface. keys are mapped similarly false null.host 70
ito vim (as detailed below). source code can be downloaded from the link false null.host 70
iand is written in golang. linux and osx are fully supported for both arm false null.host 70
iand x86_64. false null.host 70
i false null.host 70
iin bombadillo, scroll down with 'j' and up with 'k'. this is being false null.host 70
ilisted here to facilitate easier viewing of the rest of this doc false null.host 70
ifor first time users. false null.host 70
i false null.host 70
i false null.host 70
isource code is available here: false null.host 70
hhttp://tildegit.org/sloum/bombadillo url:http://tildegit.org/sloum/bombadillo colorfield.space 70
i false null.host 70
iweb based documentation/links available here: false null.host 70
hhttps://rawtext.club/~sloum/bombadillo.html url:https://rawtext.club/~sloum/bombadillo.html colorfield.space 70
i false null.host 70
i false null.host 70
ito open the above link in bombadillo a user must enable the feature. to do so false null.host 70
ia user would ":set openhttp true". this will open the http based web link in false null.host 70
itheir default web browser. a user can change back to false at any time if false null.host 70
iprefer to not open non-gopher links. if a default web browser is not set, the false null.host 70
itrue value will still result in failure :( unfortunately, if you are in a false null.host 70
inon-graphical environment entiely (such as in a remote shell) you will likely false null.host 70
inot be able to open a web browser as lynx (or the like) are not generally false null.host 70
iset up to work as a system default browser.
i false null.host 70
ibombadillo uses, if it is available, the alternate terminal buffer. this will false null.host 70
ihelp keep your terminal clean when you exit as well as create a better full false null.host 70
iscreen experience in a terminal. a configuration flag to toggle this feature false null.host 70
iis in the works.
i false null.host 70
i false null.host 70
i** quick start ** false null.host 70
i false null.host 70
iupon opening bombadillo for the first time a user will be presented with this false null.host 70
iscreen and a top bar with the application title. to visit a page a user can false null.host 70
ienter a colon followed by a gopher url or a link number (shown on the active false null.host 70
ipage to the left of link text and to the right of the gopher type). For false null.host 70
iexample: false null.host 70
i false null.host 70
i:colorfield.space false null.host 70
i false null.host 70
iupon doing so the user will see the colorfield.space gopher page false null.host 70
iyou will see the ':' key come up a lot as it leads into many commands. false null.host 70
i false null.host 70
iyou can pass a url to bombadillo when opening it from the terminal. false null.host 70
idoing so will open the client directly to that url. for example: false null.host 70
i false null.host 70
i bombadillo gopher://colorfield.space false null.host 70
i false null.host 70
i false null.host 70
iA note on window resizing: false null.host 70
i false null.host 70
iIf you resize your terminal window the screen will wrap text in weird/wild false null.host 70
iways. Pressing any key when the screen is in this state will redraw the false null.host 70
iscreen and realign the text. false null.host 70
i false null.host 70
i false null.host 70
i false null.host 70
i** hot keys ** false null.host 70
i false null.host 70
isome keys function as "hot keys". when pressed, they will initite an false null.host 70
iaction immediately. the following keys work as hot keys: false null.host 70
i false null.host 70
ikey behavior false null.host 70
i-------- -------------------------------------------------------------- false null.host 70
i q quit bombadillo false null.host 70
i b back (go back a place in browsing history if available) false null.host 70
i f forward (go forward a place in browsing history if available) false null.host 70
i j scroll down (if there is room to do so) false null.host 70
i k scroll up (if there is room to do so) false null.host 70
i G scroll to bottom (end) false null.host 70
i g scroll to top (home) false null.host 70
i d page down false null.host 70
i u page up false null.host 70
i B toggle bookmarks sidebar into or out of view false null.host 70
i : enter command mode false null.host 70
i SPC enter command mode false null.host 70
i TAB cycle window focus false null.host 70
i false null.host 70
i *window focus changes only have an effect if the bookmark window is open. false null.host 70
i Changing focus will allow the focused window to be scrolled while both false null.host 70
i windows are visible on screen. false null.host 70
i false null.host 70
i false null.host 70
i false null.host 70
i** commands ** false null.host 70
i false null.host 70
ionce in command mode, the following commands are available: false null.host 70
i (most can function using just their first letter... ex: false null.host 70
i :q will quit false null.host 70
i :w . somefile.txt will save the current file as somefile.txt false null.host 70
i all current commands work this way in addition to their long form) false null.host 70
i false null.host 70
i false null.host 70
iaction syntax notes false null.host 70
i----------------- -------------------------------- --------------------- false null.host 70
iquit bombadillo :quit same as 'q' hot key false null.host 70
ivisit homepage :home set by homeurl option false null.host 70
ivisit help :help short version :? false null.host 70
isearch :search will ask for kwds false null.host 70
ivisit url :[url] valid gopher url false null.host 70
ivisit link :[number] link # on active page false null.host 70
iview bookmarks :bookmarks same as 'B' hot key false null.host 70
ivisit bookmark :bookmarks [number] valid bookmark # false null.host 70
iadd bookmark :add [url] [bookmark title] valid gopher url false null.host 70
iadd bookmark :add [link #] [bookmark title] link # on active page false null.host 70
iadd bookmark :add . [bookmark title] adds active page false null.host 70
idelete bookmark :delete [number] valid bookmark # false null.host 70
iset an option :set [option] [value] used for configuration false null.host 70
icheck an option :check [option name] used to check config false null.host 70
iwrite to file :write [url] [file name] valid gopher url false null.host 70
iwrite to file :write [link #] [file name] link # on active page false null.host 70
iwrite to file :write . [file name] saves active page false null.host 70
i false null.host 70
i *: navigating to a non-text gophertype will automaticall save false null.host 70
i files save to the path set by the 'saveurl' option (defaults false null.host 70
i to a user's Downloads folder in their home directory). false null.host 70
i **: search is entered on its own and will query the user for keywords false null.host 70
i and will then query the search service set as 'searchengine' false null.host 70
i (defaults to veronica2) false null.host 70
i false null.host 70
i false null.host 70
i false null.host 70
i false null.host 70
i** configuartion ** false null.host 70
i false null.host 70
ivarious configuartion options can be set via the 'set' command. false null.host 70
ithe following are the currently avialable options, more will be false null.host 70
icoming in the near future (including color theme options) false null.host 70
i false null.host 70
ionce a user sets an option or adds a bookmark a config file will be false null.host 70
icreated in their home directory. the file will be named '.bombadillo.ini' false null.host 70
iwhile it can be edited directly, it is recommended to use bombadillo false null.host 70
ito interact with said file.
i false null.host 70
i false null.host 70
i option value type default false null.host 70
i--------------- ------------------------- ------------------------ false null.host 70
i false null.host 70
ihomeurl valid gopher url this document false null.host 70
isavelocation an non-relative filepath /[home path]/Downloads/ false null.host 70
isearchengine a type 7 gopher url gopher.floodgap.com:70/7/v2/vs false null.host 70
iopenhttp true/false false false null.host 70
i false null.host 70
i false null.host 70
i false null.host 70
i false null.host 70
i false null.host 70
i false null.host 70
ifor more information please contact the following: false null.host 70
1colorfield space / colorfield.space 70
isloum@sdf.org false null.host 70
htildegit url:http://tildergit.org/sloum colorfield.space 70
i false null.host 70
i false null.host 70
i false null.host 70

BIN
bombadillo-screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

304
bombadillo.1 Normal file
View File

@ -0,0 +1,304 @@
.TH "bombadillo" 1 "27 OCT 2019" "" "General Operation Manual"
.SH NAME
\fBbombadillo \fP- a non-web browser
.SH SYNOPSIS
.nf
.fam C
\fBbombadillo\fP [\fIoptions\fP] [\fIurl\fP]
.fam T
.fi
.SH DESCRIPTION
\fBbombadillo\fP is a non-web browser for the terminal. It features a full terminal user interface, vim-like keybindings, document pager, configurable settings, and a robust command selection.
.TP
\fBbombadillo\fP supports the following protocols as first class citizens: gopher, gemini, finger, and local (a users file system). Support for telnet, http and https is also available via integration with third party applications.
.SH OPTIONS
.TP
.B
\fB-h\fP
Display usage help and exit. Provides a list of all command line options with a short description and exits.
.TP
.B
\fB-t\fP
Set the window title to 'Bombadillo'. Can be used in a GUI environment, however not all terminals support this feature.
.TP
.B
\fB-v\fP
Display version information and exit.
.SH PROTOCOL SUPPORT
All of the below protocols are supported. With the exception of gopher, the protocol name must be present as the scheme component of a url in the form of \fI[protocol]://[the rest of the url]\fP.
.TP
.B
gopher
Gopher is the default protocol for \fBbombadillo\fP. Any textual item types will be visited and shown to the user and any non-text types will be downloaded. Type 7 (querying) is fully supported. As the default protocol, any url that is not prefixed with the scheme section of a url (\fIgopher://\fP for example) will be treated as gopher urls.
.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). Gemini maps and other text types are rendered in the browser and non-text types will be downloaded.
.TP
.B
finger
Basic support is provided for the finger protocol. The format is: \fIfinger://[[username@]][hostname]\fP. Many servers still support finger and it can be fun to see if friends are online or read about the users whose phlogs you follow.
.TP
.B
local
Local is similar to the \fIfile\fP protocol used in web browsers or the like, with a smaller set of features. Users can use the local scheme to view files on their local system. Directories are supported as viewable text object as well as any files. Wildcards and globbing are not supported. Using \fI~\fP to represent a user's home directory, as well as relative paths, are supported. The \fIcolor\fP theme has no effect on this protocol and all terminal escape sequences will be rendered to the screen literally.
.TP
.B
telnet
Telnet is not supported directly, but addresses will be followed and opened as a subprocess by whatever telnet client a user sets in their settings (defaulting to \fItelnet\fP). In some cases this behavior may be buggy.
.TP
.B
http, https
Neither of the world wide web protocols are supported directly. \fBbombadillo\fP can be configured to open web links in a user's default graphical web browser. It is also possible to display web content directly in \fBbombadillo\fP using lynx, w3m, or elinks terminal web browsers to render pages. Opening http/https links is opt-in only, controlled by the \fIwebmode\fP setting.
.IP
Opening links in a default graphical web browser will only work in a GUI environment.
.IP
Displaying web content directly in \fBbombadillo\fP requires lynx, w3m or elinks terminal web browsers are installed on the system.
.SH COMMANDS
.SS KEY COMMANDS
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, h
Navigate back one place in your document history.
.TP
.B
B
Toggle the bookmarks panel open/closed.
.TP
.B
d
Scroll down an amount corresponding to 75% of your terminal window height in the current document.
.TP
.B
f, l
Navigate forward one place in your document history.
.TP
.B
g
Scroll to the top of the current document.
.TP
.B
G
Scroll to the bottom of the current document.
.TP
.B
j
Scroll down a single line in the current document.
.TP
.B
k
Scroll up a single line.
.TP
.B
n
Jump to next found text item.
.TP
.B
N
Jump to previous found text item.
.TP
.B
q
Quit \fBbombadillo\fP.
.TP
.B
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
.B
/
Search for text within current document. / followed by a text query will highlight and allow navigation of found text. / with an empty query will clear the current query.
.TP
.B
<tab>
Toggle the scroll focus between the bookmarks panel and the document panel. Only has an effect if the bookmarks panel is open.
.TP
.B
<spc>
Enter line command mode. Once a line command is input, the mode will automatically revert to key command mode.
.TP
.B
:
Alias for <spc>. Enter line command mode.
.SS LINE COMMANDS
These commands are typed in by the user to perform an action of some sort. As listed in KEY COMMANDS, this mode is initiated by pressing : or <space>. The command names themselves are not case sensitive, though the arguments supplied to them may be.
.TP
.B
[url]
Navigates to the requested url.
.TP
.B
[link id]
Follows a link on the current document with the given number.
.TP
.B
add [url] [name\.\.\.]
Adds the url as a bookmarks labeled by name. \fIa\fP can be used instead of the full \fIadd\fP.
.TP
.B
add [link id] [name\.\.\.]
Adds the url represented by the link id within the current document as a bookmark labeled by name. \fIa\fP can be used instead of the full \fIadd\fP.
.TP
.B
add . [name\.\.\.]
Adds the current document's url as a bookmark labeled by name. \fIa\fP can be used instead of the full \fIadd\fP.
.TP
.B
bookmarks
Toggles the bookmarks panel open/closed. Alias for KEY COMMAND \fIB\fP. \fIb\fP can be used instead of the full \fIbookmarks\fP.
.TP
.B
bookmarks [bookmark id]
Navigates to the url represented by the bookmark matching bookmark id. \fIb\fP can be entered, rather than the full \fIbookmarks\fP.
.TP
.B
check [link id]
Displays the url corresponding to a given link id for the current document. \fIc\fP can be used instead of the full \fIcheck\fP.
.TP
.B
check [setting name]
Displays the current value for a given configuration setting. \fIc\fP can be used instead of the full \fIcheck\fP.
.TP
.B
delete [bookmark id]
Deletes the bookmark matching the bookmark id. \fId\fP can be used instead of the full \fIdelete\fP.
.TP
.B
help
Navigates to the gopher based help page for \fBbombadillo\fP. \fI?\fP can be used instead of the full \fIhelp\fP.
.TP
.B
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
.B
purge [host name]
Deletes the pinned gemini server certificate for the given hostname. \fIp\fP can be used instead of the full \fIpurge\fP.
.TP
.B
quit
Quits \fBbombadillo\fP. Alias for KEY COMMAND \fIq\fP. \fIq\fP can be used instead of the full \fIquit\fP.
.TP
.B
reload
Requests the current document from the server again. This does not break forward history the way entering the url again would. \fIr\fP can be used instead of the full \fIreload\fP.
.TP
.B
search
Queries the user for search terms and submits a search to the search engine set by the \fIsearchengine\fP setting.
.TP
.B
search [keywords\.\.\.]
Submits a search to the search engine set by the \fIsearchengine\fP setting, with the query being the provided keyword(s).
.TP
.B
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
.B
write [url]
Writes data from a given url 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
.B
write [link id]
Writes data from a given link id in 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.
.SH FILES
\fBbombadillo\fP keeps a hidden configuration file in a user's XDG configuration directory. The file is a simplified ini file titled \fI.bombadillo.ini\fP. It is generated when a user first loads \fBbombadillo\fP and is updated with bookmarks and settings as a user adds them. The file can be directly edited, but it is best to use the SET command to update settings whenever possible. To return to the state of a fresh install, simply remove the file and a new one will be generated with the \fBbombadillo\fP defaults. On some systems an administrator may set the configuration file location to somewhere other than the default setting. If you do not see the file where you expect it, or if your settings are not being read, try \fI:check configlocation\fP to see where the file should be, or contact your system administrator for more information.
.SH SETTINGS
The following is a list of the settings that \fBbombadillo\fP recognizes, as well as a description of their valid values.
.TP
.B
configlocation
The path to the directory that the \fI.bombadillo.ini\fP configuration file is stored in. This is a \fBread only\fP setting and cannot be changed with the \fIset\fP command, but it can be read with the \fIcheck\fP command.
.TP
.B
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 COMMAND \fIsearch\fP. Should be a valid search path that terms may be appended to.
.TP
.B
telnetcommand
Tells the browser what command to use to start a telnet session. Should be a valid command, including any flags. The address being navigated to will be added to the end of the command.
.TP
.B
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
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
Controls behavior when following web links. The following values are valid: \fInone\fP will disable following web links, \fIgui\fP will have the browser attempt to open web links in a user's default graphical web browser; \fIlynx\fP, \fIw3m\fP, and \fIelinks\fP will have the browser attempt to use the selected terminal web browser to handle the rendering of web pages and will display the pages directly in Bombadillo.
.SH BUGS
There are very likely bugs. Many known bugs can be found in the issues section of \fBbombadillo\fP's source code repository (see \fIlinks\fP).
.SH LINKS
\fBbombadillo\fP maintains a presence in the following locations:
.TP
.B
Source Code Repository
https://tildegit.org/sloum/bombadillo
.TP
.B
Web Homepage
http://bombadillo.colorfield.space
.TP
.B
Gopher Homepage
gopher://bombadillo.colorfield.space
.SH AUTHORS
\fBbombadillo\fP was primarily developed by sloum, with kind and patient assistance from ~asdf and jboverf.

10
bombadillo.desktop Normal file
View File

@ -0,0 +1,10 @@
[Desktop Entry]
Type=Application
Name=Bombadillo
GenericName=Non-Web Browser
Comment=View gopher, gemini, finger, telnet, http(s) sites over the internet
Terminal=true
Categories=Network;WebBrowser;ConsoleOnly;
Exec=bombadillo -t %u
Icon=bombadillo-icon
MimeType=x-scheme-handler/gopher;x-scheme-handler/gemini;x-scheme-handler/finger;

164
bookmarks.go Normal file
View File

@ -0,0 +1,164 @@
/*
* 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/cui"
)
//------------------------------------------------\\
// + + + T Y P E S + + + \\
//--------------------------------------------------\\
// Bookmarks represents the contents of the bookmarks
// bar, as well as its visibility, focus, and scroll
// state.
type Bookmarks struct {
IsOpen bool
IsFocused bool
Position int
Length int
Titles []string
Links []string
}
//------------------------------------------------\\
// + + + R E C E I V E R S + + + \\
//--------------------------------------------------\\
// Add a bookmark to the bookmarks struct
func (b *Bookmarks) Add(v []string) (string, error) {
if len(v) < 2 {
return "", fmt.Errorf("Received %d arguments, expected 2+", len(v))
}
b.Titles = append(b.Titles, strings.Join(v[1:], " "))
b.Links = append(b.Links, v[0])
b.Length = len(b.Titles)
return "Bookmark added successfully", nil
}
// Delete a bookmark from the bookmarks struct
func (b *Bookmarks) Delete(i int) (string, error) {
if i < len(b.Titles) && len(b.Titles) == len(b.Links) {
b.Titles = append(b.Titles[:i], b.Titles[i+1:]...)
b.Links = append(b.Links[:i], b.Links[i+1:]...)
b.Length = len(b.Titles)
return "Bookmark deleted successfully", nil
}
return "", fmt.Errorf("Bookmark %d does not exist", i)
}
// ToggleOpen toggles visibility state of the bookmarks bar
func (b *Bookmarks) ToggleOpen() {
b.IsOpen = !b.IsOpen
if b.IsOpen {
b.IsFocused = true
} else {
b.IsFocused = false
}
}
// ToggleFocused toggles the focal state of the bookmarks bar
func (b *Bookmarks) ToggleFocused() {
if b.IsOpen {
b.IsFocused = !b.IsFocused
}
}
// IniDump returns a string representing the current bookmarks
// in the format that .bombadillo.ini uses
func (b Bookmarks) IniDump() string {
if len(b.Titles) < 1 {
return ""
}
out := "[BOOKMARKS]\n"
for i := 0; i < len(b.Titles); i++ {
out += b.Titles[i]
out += "="
out += b.Links[i]
out += "\n"
}
return out
}
// List returns a list, including link nums, of bookmarks
// as a string slice
func (b Bookmarks) List() []string {
var out []string
for i, t := range b.Titles {
out = append(out, fmt.Sprintf("[%d] %s", i, t))
}
return out
}
// Render returns a string slice with the contents of each
// visual row of the bookmark bar.
func (b Bookmarks) Render(termwidth, termheight int) []string {
width := 40
termheight -= 3
var walll, wallr, floor, ceil, tr, tl, br, bl string
if termwidth < 40 {
width = termwidth
}
if b.IsFocused {
walll = cui.Shapes["awalll"]
wallr = cui.Shapes["awallr"]
ceil = cui.Shapes["aceiling"]
floor = cui.Shapes["afloor"]
tr = cui.Shapes["atr"]
br = cui.Shapes["abr"]
tl = cui.Shapes["atl"]
bl = cui.Shapes["abl"]
} else {
walll = cui.Shapes["walll"]
wallr = cui.Shapes["wallr"]
ceil = cui.Shapes["ceiling"]
floor = cui.Shapes["floor"]
tr = cui.Shapes["tr"]
br = cui.Shapes["br"]
tl = cui.Shapes["tl"]
bl = cui.Shapes["bl"]
}
out := make([]string, 0, 5)
contentWidth := width - 2
top := fmt.Sprintf("%s%s%s", tl, strings.Repeat(ceil, contentWidth), tr)
out = append(out, top)
marks := b.List()
for i := 0; i < termheight-2; i++ {
if i+b.Position >= len(b.Titles) {
out = append(out, fmt.Sprintf("%s%-*.*s%s", walll, contentWidth, contentWidth, "", wallr))
} else {
out = append(out, fmt.Sprintf("%s%-*.*s%s", walll, contentWidth, contentWidth, marks[i+b.Position], wallr))
}
}
bottom := fmt.Sprintf("%s%s%s", bl, strings.Repeat(floor, contentWidth), br)
out = append(out, bottom)
return out
}
//------------------------------------------------\\
// + + + F U N C T I O N S + + + \\
//--------------------------------------------------\\
// MakeBookmarks creates a Bookmark struct with default values
func MakeBookmarks() Bookmarks {
return Bookmarks{false, false, 0, 0, make([]string, 0), make([]string, 0)}
}

1294
client.go Normal file

File diff suppressed because it is too large Load Diff

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 (
@ -68,9 +83,11 @@ func (s *scanner) scanText() Token {
capInput := strings.ToUpper(buf.String())
switch capInput {
case "DELETE", "ADD", "WRITE", "SET", "RECALL", "R", "SEARCH",
"W", "A", "D", "S", "Q", "QUIT", "B", "BOOKMARKS", "H",
"HOME", "?", "HELP", "C", "CHECK":
case "D", "DELETE", "A", "ADD", "W", "WRITE",
"S", "SET", "R", "RELOAD", "SEARCH",
"Q", "QUIT", "B", "BOOKMARKS", "H",
"HOME", "?", "HELP", "C", "CHECK",
"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,10 +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 config
import (
"fmt"
"io"
"strings"
"tildegit.org/sloum/bombadillo/gopher"
)
//------------------------------------------------\\
@ -21,9 +35,12 @@ type Parser struct {
}
type Config struct {
Bookmarks gopher.Bookmarks
Colors []KeyValue
Settings []KeyValue
// Bookmarks gopher.Bookmarks
Bookmarks struct {
Titles, Links []string
}
Settings []KeyValue
Certs []KeyValue
}
type KeyValue struct {
@ -86,12 +103,10 @@ func (p *Parser) Parse() (Config, error) {
}
switch section {
case "BOOKMARKS":
err := c.Bookmarks.Add([]string{keyval.Value, keyval.Key})
if err != nil {
return c, err
}
case "COLORS":
c.Colors = append(c.Colors, keyval)
c.Bookmarks.Titles = append(c.Bookmarks.Titles, keyval.Value)
c.Bookmarks.Links = append(c.Bookmarks.Links, keyval.Key)
case "CERTS":
c.Certs = append(c.Certs, keyval)
case "SETTINGS":
c.Settings = append(c.Settings, keyval)
}

View File

@ -1,40 +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 cui
import (
"bufio"
"bytes"
"fmt"
"os"
"os/exec"
"strings"
"tildegit.org/sloum/bombadillo/termios"
)
var shapes = map[string]string{
"wall": "╵",
"ceiling": "╴",
"tl": "┌",
"tr": "┐",
"bl": "└",
"br": "┘",
"awall": "║",
"aceiling": "═",
"atl": "╔",
"atr": "╗",
"abl": "╚",
"abr": "╝",
}
func drawShape(shape string) {
if val, ok := shapes[shape]; ok {
fmt.Printf("%s", val)
} else {
fmt.Print("x")
}
}
func moveThenDrawShape(r, c int, s string) {
MoveCursorTo(r, c)
drawShape(s)
var Shapes = map[string]string{
"walll": "╎",
"wallr": " ",
"ceiling": " ",
"floor": " ",
"tl": "╎",
"tr": " ",
"bl": "╎",
"br": " ",
"awalll": "▌",
"awallr": "▐",
"aceiling": "▀",
"afloor": "▄",
"atl": "▞",
"atr": "▜",
"abl": "▚",
"abr": "▟",
}
func MoveCursorTo(row, col int) {
@ -54,15 +60,34 @@ func moveCursorToward(dir string, amount int) {
}
}
func Exit() {
// Exit performs cleanup operations before exiting the application
func Exit(exitCode int, msg string) {
CleanupTerm()
if msg != "" {
fmt.Print(msg, "\n")
}
fmt.Print("\033[23;0t") // Restore window title from terminal stack
os.Exit(exitCode)
}
// InitTerm sets the terminal modes appropriate for Bombadillo
func InitTerm() {
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")
HandleAlternateScreen("rmcup")
os.Exit(0)
fmt.Print("\033[?25h") // reenables cursor blinking
Tput("smam") // turn on line wrap
Tput("rmcup") // stop using alternate screen
}
func Clear(dir string) {
@ -81,39 +106,6 @@ func Clear(dir string) {
}
func wrapLines(s []string, length int) []string {
out := []string{}
for _, ln := range s {
if len(ln) <= length {
out = append(out, ln)
} else {
words := strings.Split(ln, " ")
var subout bytes.Buffer
for i, wd := range words {
sublen := subout.Len()
if sublen+len(wd)+1 <= length {
if sublen > 0 {
subout.WriteString(" ")
}
subout.WriteString(wd)
if i == len(words)-1 {
out = append(out, subout.String())
}
} else {
out = append(out, subout.String())
subout.Reset()
subout.WriteString(wd)
if i == len(words)-1 {
out = append(out, subout.String())
subout.Reset()
}
}
}
}
}
return out
}
func Getch() rune {
reader := bufio.NewReader(os.Stdin)
char, _, err := reader.ReadRune()
@ -123,43 +115,21 @@ func Getch() rune {
return char
}
func GetLine() (string, error) {
SetLineMode()
func GetLine(prefix string) (string, error) {
termios.SetLineMode()
defer termios.SetCharMode()
reader := bufio.NewReader(os.Stdin)
fmt.Print(": ")
fmt.Print(prefix)
text, err := reader.ReadString('\n')
if err != nil {
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 HandleAlternateScreen(opt string) {
func Tput(opt string) {
cmd := exec.Command("tput", opt)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout

View File

@ -1,32 +0,0 @@
package cui
// MsgBar is a struct to represent a single row horizontal
// bar on the screen.
type MsgBar struct {
row int
title string
message string
showTitle bool
}
// SetTitle sets the title for the MsgBar in question
func (m *MsgBar) SetTitle(s string) {
m.title = s
}
// SetMessage sets the message for the MsgBar in question
func (m *MsgBar) SetMessage(s string) {
m.message = s
}
// ClearAll clears all text from the message bar (title and message)
func (m MsgBar) ClearAll() {
MoveCursorTo(m.row, 1)
Clear("line")
}
// ClearMessage clears all message text while leaving the title in place
func (m *MsgBar) ClearMessage() {
MoveCursorTo(m.row, len(m.title)+1)
Clear("right")
}

View File

@ -1,144 +0,0 @@
package cui
import (
"bytes"
"fmt"
"os"
"os/exec"
"strings"
)
// screenInit records whether or not the screen has been initialized
// this is used to prevent more than one screen from being used
var screenInit bool = false
// Screen represent the top level abstraction for a cui application.
// It takes up the full width and height of the terminal window and
// holds the various Windows and MsgBars for the application as well
// as a record of which window is active for control purposes.
type Screen struct {
Height int
Width int
Windows []*Window
Activewindow int
Bars []*MsgBar
}
// AddWindow adds a new window to the Screen struct in question
func (s *Screen) AddWindow(r1, c1, r2, c2 int, scroll, border, show bool) {
w := Window{box{r1, c1, r2, c2}, scroll, 0, []string{}, border, false, show, 1}
s.Windows = append(s.Windows, &w)
}
// AddMsgBar adds a new MsgBar to the Screen struct in question
func (s *Screen) AddMsgBar(row int, title, msg string, showTitle bool) {
b := MsgBar{row, title, msg, showTitle}
s.Bars = append(s.Bars, &b)
}
// DrawAllWindows loops over every window in the Screen struct and
// draws it to screen in index order (smallest to largest)
func (s Screen) DrawAllWindows() {
for _, w := range s.Windows {
if w.Show {
w.DrawWindow()
}
}
MoveCursorTo(s.Height-1, 1)
}
// Clear removes all content from the interior of the screen
func (s Screen) Clear() {
for i := 0; i <= s.Height; i++ {
MoveCursorTo(i, 0)
Clear("line")
}
}
// Clears message/error/command area
func (s *Screen) ClearCommandArea() {
MoveCursorTo(s.Height-1, 1)
Clear("line")
MoveCursorTo(s.Height, 1)
Clear("line")
MoveCursorTo(s.Height-1, 1)
}
// ReflashScreen checks for a screen resize and resizes windows if
// needed then redraws the screen. It takes a bool to decide whether
// to redraw the full screen or just the content. On a resize
// event, the full screen will always be redrawn.
func (s *Screen) ReflashScreen(clearScreen bool) {
s.DrawAllWindows()
if clearScreen {
s.DrawMsgBars()
s.ClearCommandArea()
}
}
// DrawMsgBars draws all MsgBars present in the Screen struct.
// All MsgBars are looped over and drawn in index order (sm - lg).
func (s *Screen) DrawMsgBars() {
for _, bar := range s.Bars {
fmt.Print("\033[7m")
var buf bytes.Buffer
title := bar.title
if len(bar.title) > s.Width {
title = string(bar.title[:s.Width-3]) + "..."
}
_, _ = buf.WriteString(title)
msg := bar.message
if len(bar.message) > s.Width-len(title) {
msg = string(bar.message[:s.Width-len(title)-3]) + "..."
}
_, _ = buf.WriteString(msg)
if buf.Len() < s.Width {
wsLength := s.Width - buf.Len()
_,_ = buf.WriteString(strings.Repeat(" ", wsLength))
}
MoveCursorTo(bar.row, 1)
fmt.Print(buf.String())
fmt.Print("\033[0m")
}
}
// GetSize retrieves the terminal size and sets the Screen
// width and height to that size
func (s *Screen) GetSize() {
cmd := exec.Command("stty", "size")
cmd.Stdin = os.Stdin
out, err := cmd.Output()
if err != nil {
fmt.Println("Fatal error: Unable to retrieve terminal size")
os.Exit(1)
}
var h, w int
fmt.Sscan(string(out), &h, &w)
s.Height = h
s.Width = w
}
// - - - - - - - - - - - - - - - - - - - - - - - - - -
// NewScreen is a constructor function that returns a pointer
// to a Screen struct
func NewScreen() *Screen {
if screenInit {
fmt.Println("Fatal error: Cannot create multiple screens")
os.Exit(1)
}
var s Screen
s.GetSize()
for i := 0; i < s.Height; i++ {
fmt.Println()
}
SetCharMode()
Clear("screen")
screenInit = true
return &s
}

View File

@ -1,187 +0,0 @@
package cui
import (
"fmt"
"strings"
)
type box struct {
Row1 int
Col1 int
Row2 int
Col2 int
}
// TODO add coloring
type Window struct {
Box box
Scrollbar bool
Scrollposition int
Content []string
drawBox bool
Active bool
Show bool
tempContentLen int
}
func (w *Window) DrawWindow() {
w.DrawContent()
if w.drawBox {
w.DrawBox()
}
}
func (w *Window) DrawBox() {
lead := ""
if w.Active {
lead = "a"
}
moveThenDrawShape(w.Box.Row1, w.Box.Col1, lead+"tl")
moveThenDrawShape(w.Box.Row1, w.Box.Col2, lead+"tr")
moveThenDrawShape(w.Box.Row2, w.Box.Col1, lead+"bl")
moveThenDrawShape(w.Box.Row2, w.Box.Col2, lead+"br")
for i := w.Box.Col1 + 1; i < w.Box.Col2; i++ {
moveThenDrawShape(w.Box.Row1, i, lead+"ceiling")
moveThenDrawShape(w.Box.Row2, i, lead+"ceiling")
}
for i := w.Box.Row1 + 1; i < w.Box.Row2; i++ {
moveThenDrawShape(i, w.Box.Col1, lead+"wall")
moveThenDrawShape(i, w.Box.Col2, lead+"wall")
}
}
func (w *Window) DrawContent() {
var maxlines, borderThickness, contenth int
var short_content bool = false
if w.drawBox {
borderThickness, contenth = -1, 1
} else {
borderThickness, contenth = 1, 0
}
height := w.Box.Row2 - w.Box.Row1 + borderThickness
width := w.Box.Col2 - w.Box.Col1 + borderThickness
content := wrapLines(w.Content, width)
w.tempContentLen = len(content)
if w.Scrollposition > w.tempContentLen-height {
w.Scrollposition = w.tempContentLen - height
if w.Scrollposition < 0 {
w.Scrollposition = 0
}
}
if len(content) < w.Scrollposition+height {
maxlines = len(content)
short_content = true
} else {
maxlines = w.Scrollposition + height
}
for i := w.Scrollposition; i < maxlines; i++ {
MoveCursorTo(w.Box.Row1+contenth+i-w.Scrollposition, w.Box.Col1+contenth)
fmt.Print(strings.Repeat(" ", width))
MoveCursorTo(w.Box.Row1+contenth+i-w.Scrollposition, w.Box.Col1+contenth)
fmt.Print(content[i])
}
if short_content {
for i := len(content); i <= height; i++ {
MoveCursorTo(w.Box.Row1+contenth+i-w.Scrollposition, w.Box.Col1+contenth)
fmt.Print(strings.Repeat(" ", width))
}
}
}
func (w *Window) ScrollDown() {
var borderThickness int
if w.drawBox {
borderThickness = -1
} else {
borderThickness = 1
}
height := w.Box.Row2 - w.Box.Row1 + borderThickness
if w.Scrollposition < w.tempContentLen-height {
w.Scrollposition++
} else {
fmt.Print("\a")
}
}
func (w *Window) ScrollUp() {
if w.Scrollposition > 0 {
w.Scrollposition--
} else {
fmt.Print("\a")
}
}
func (w *Window) PageDown() {
var borderThickness int
if w.drawBox {
borderThickness = -1
} else {
borderThickness = 1
}
height := w.Box.Row2 - w.Box.Row1 + borderThickness
if w.Scrollposition < w.tempContentLen-height {
w.Scrollposition += height
if w.Scrollposition > w.tempContentLen-height {
w.Scrollposition = w.tempContentLen - height
}
} else {
fmt.Print("\a")
}
}
func (w *Window) PageUp() {
var borderThickness int
if w.drawBox {
borderThickness = -1
} else {
borderThickness = 1
}
height := w.Box.Row2 - w.Box.Row1 + borderThickness
contentLength := len(w.Content)
if w.Scrollposition > 0 && height < contentLength {
w.Scrollposition -= height
if w.Scrollposition < 0 {
w.Scrollposition = 0
}
} else {
fmt.Print("\a")
}
}
func (w *Window) ScrollHome() {
if w.Scrollposition > 0 {
w.Scrollposition = 0
} else {
fmt.Print("\a")
}
}
func (w *Window) ScrollEnd() {
var borderThickness int
if w.drawBox {
borderThickness = -1
} else {
borderThickness = 1
}
height := w.Box.Row2 - w.Box.Row1 + borderThickness
if w.Scrollposition < w.tempContentLen-height {
w.Scrollposition = w.tempContentLen - height
} else {
fmt.Print("\a")
}
}

95
defaults.go Normal file
View File

@ -0,0 +1,95 @@
/*
* 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 (
"os"
"os/user"
"path/filepath"
)
var defaultOptions = map[string]string{
// The configuration options below control the default settings for
// users of Bombadillo.
//
// Changes take effect when Bombadillo is built. Follow the standard
// install instructions after making a change.
//
// Most options can be changed by a user in the Bombadillo client, and
// changes made here will not overwrite an existing user's settings.
// The exception to both cases is "configlocation" which controls where
// .bombadillo.ini is stored. If you make changes to this setting,
// consider moving bombadillo.ini to the new location as well, so you
// (or your users) do not loose bookmarks or other preferences.
//
// Further explanation of each option is available in the man page.
// Basic Usage
//
// Any option can be defined as a string, like this:
// "option": "value"
//
// Options can also have values calculated on startup. There are two
// functions below that do just this: homePath() and xdgConfigPath()
// You can set any value to use these functions like this:
// "option": homePath()
// "option": xdgConfigPath()
// See the comments for these functions for more information on what
// they do.
//
// You can also use `filepath.Join()` if you want to build a file path.
// For example, specify "~/bombadillo" like so:
// "option": filepath.Join(homePath(), bombadillo)
// Moving .bombadillo.ini out of your home directory
//
// To ensure .bombadillo.ini is saved as per XDG config spec, change
// 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",
"theme": "normal", // "normal", "inverted", "color"
"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
// Usage:
// "configlocation": homeConfigPath()
func homePath() string {
var userinfo, _ = user.Current()
return userinfo.HomeDir
}
// xdgConfigPath returns the path to your XDG base directory for configuration
// i.e the contents of environment variable XDG_CONFIG_HOME, or ~/.config/
// Usage:
// "configlocation": xdgConfigPath()
func xdgConfigPath() string {
configPath := os.Getenv("XDG_CONFIG_HOME")
if configPath == "" {
return filepath.Join(homePath(), ".config")
}
return configPath
}

46
finger/finger.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 finger
import (
"fmt"
"io/ioutil"
"net"
"time"
)
func Finger(host, port, resource string) (string, error) {
addr := fmt.Sprintf("%s:%s", host, port)
timeOut := time.Duration(3) * time.Second
conn, err := net.DialTimeout("tcp", addr, timeOut)
if err != nil {
return "", err
}
defer conn.Close()
_, err = conn.Write([]byte(resource + "\r\n"))
if err != nil {
return "", err
}
result, err := ioutil.ReadAll(conn)
if err != nil {
return "", err
}
return string(result), nil
}

73
footbar.go Normal file
View File

@ -0,0 +1,73 @@
/*
* 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"
"strconv"
)
//------------------------------------------------\\
// + + + T Y P E S + + + \\
//--------------------------------------------------\\
// Footbar deals with the values present in the
// client's footbar
type Footbar struct {
PercentRead string
PageType string
}
//------------------------------------------------\\
// + + + R E C E I V E R S + + + \\
//--------------------------------------------------\\
// SetPercentRead sets the percentage of the current
// document the user has read
func (f *Footbar) SetPercentRead(p int) {
if p > 100 {
p = 100
} else if p < 0 {
p = 0
}
f.PercentRead = strconv.Itoa(p) + "%"
}
// SetPageType sets the current page's type
// NOTE: This is not currently in use
func (f *Footbar) SetPageType(t string) {
f.PageType = t
}
// Render returns a string representing the visual display
// of the bookmarks bar
func (f *Footbar) Render(termWidth, position int, theme string) string {
pre := fmt.Sprintf("HST: (%2.2d) - - - %4s Read ", position+1, f.PercentRead)
out := "\033[0m%*.*s "
if theme == "inverse" {
out = "\033[7m%*.*s \033[0m"
}
return fmt.Sprintf(out, termWidth-1, termWidth-1, pre)
}
//------------------------------------------------\\
// + + + F U N C T I O N S + + + \\
//--------------------------------------------------\\
// MakeFootbar returns a footbar with default values
func MakeFootbar() Footbar {
return Footbar{"---", "N/A"}
}

460
gemini/gemini.go Normal file
View File

@ -0,0 +1,460 @@
/*
* 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 (
"bytes"
"crypto/sha1"
"crypto/tls"
"fmt"
"io/ioutil"
"net"
"net/url"
"strconv"
"strings"
"time"
)
type Capsule struct {
MimeMaj string
MimeMin string
Status int
Content string
Links []string
}
type TofuDigest struct {
certs map[string]string
}
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) Purge(host string) error {
host = strings.ToLower(host)
if host == "*" {
t.certs = make(map[string]string)
return nil
} else if _, ok := t.certs[strings.ToLower(host)]; ok {
delete(t.certs, host)
return nil
}
return fmt.Errorf("Invalid host %q", host)
}
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 {
if _, ok := t.certs[strings.ToLower(host)]; ok {
return true
}
return false
}
func (t *TofuDigest) Find(host string) (string, error) {
if hash, ok := t.certs[strings.ToLower(host)]; ok {
return hash, nil
}
return "", fmt.Errorf("Invalid hostname, no key saved")
}
func (t *TofuDigest) Match(host, localCert string, cState *tls.ConnectionState) error {
now := time.Now()
for _, cert := range cState.PeerCertificates {
if localCert != hashCert(cert.Raw) {
continue
}
if now.Before(cert.NotBefore) {
return fmt.Errorf("Certificate is not valid yet")
}
if now.After(cert.NotAfter) {
return fmt.Errorf("EXP")
}
if err := cert.VerifyHostname(host); err != nil && cert.Subject.CommonName != host {
return fmt.Errorf("Certificate error: %s", err)
}
return nil
}
return fmt.Errorf("No matching certificate was found for host %q", host)
}
func (t *TofuDigest) newCert(host string, cState *tls.ConnectionState) error {
host = strings.ToLower(host)
now := time.Now()
var reasons strings.Builder
for index, cert := range cState.PeerCertificates {
if index > 0 {
reasons.WriteString("; ")
}
if now.Before(cert.NotBefore) {
reasons.WriteString(fmt.Sprintf("Cert [%d] is not valid yet", index+1))
continue
}
if now.After(cert.NotAfter) {
reasons.WriteString(fmt.Sprintf("Cert [%d] is expired", index+1))
continue
}
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), 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 ""
}
var out strings.Builder
out.WriteString("[CERTS]\n")
for k, v := range t.certs {
out.WriteString(k)
out.WriteString("=")
out.WriteString(v)
out.WriteString("\n")
}
return out.String()
}
//------------------------------------------------\\
// + + + F U N C T I O N S + + + \\
//--------------------------------------------------\\
func Retrieve(host, port, resource string, td *TofuDigest) (string, error) {
if host == "" || port == "" {
return "", fmt.Errorf("Incomplete request url")
}
addr := host + ":" + port
conf := &tls.Config{
MinVersion: tls.VersionTLS12,
InsecureSkipVerify: true,
}
conn, err := tls.DialWithDialer(&net.Dialer{Timeout: TlsTimeout}, "tcp", addr, conf)
if err != nil {
return "", fmt.Errorf("TLS Dial Error: %s", err.Error())
}
defer conn.Close()
connState := conn.ConnectionState()
// Begin TOFU screening...
// If no certificates are offered, bail out
if len(connState.PeerCertificates) < 1 {
return "", fmt.Errorf("Insecure, no certificates offered by server")
}
localCert, localTs, err := td.GetCertAndTimestamp(host)
if localTs > 0 {
// See if we have a matching cert
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
return "", err
} else if err != nil {
// The cert expired, see if they are offering one that is valid...
err := td.newCert(host, &connState)
if err != nil {
// If there are no valid certs to offer, let the client know
return "", err
}
}
} else {
err = td.newCert(host, &connState)
if err != nil {
// If there are no valid certs to offer, let the client know
return "", err
}
}
send := "gemini://" + addr + "/" + resource + "\r\n"
_, err = conn.Write([]byte(send))
if err != nil {
return "", err
}
result, err := ioutil.ReadAll(conn)
if err != nil {
return "", err
}
return string(result), nil
}
func Fetch(host, port, resource string, td *TofuDigest) ([]byte, error) {
rawResp, err := Retrieve(host, port, resource, td)
if err != nil {
return make([]byte, 0), err
}
resp := strings.SplitN(rawResp, "\r\n", 2)
if len(resp) != 2 {
if err != nil {
return make([]byte, 0), fmt.Errorf("Invalid response from server")
}
}
header := strings.SplitN(resp[0], " ", 2)
if len([]rune(header[0])) != 2 {
header = strings.SplitN(resp[0], "\t", 2)
if len([]rune(header[0])) != 2 {
return make([]byte, 0), fmt.Errorf("Invalid response format from server")
}
}
// Get status code single digit form
status, err := strconv.Atoi(string(header[0][0]))
if err != nil {
return make([]byte, 0), fmt.Errorf("Invalid status response from server")
}
if status != 2 {
switch status {
case 1:
return make([]byte, 0), fmt.Errorf("[1] Queries cannot be saved.")
case 3:
return make([]byte, 0), fmt.Errorf("[3] Redirects cannot be saved.")
case 4:
return make([]byte, 0), fmt.Errorf("[4] Temporary Failure.")
case 5:
return make([]byte, 0), fmt.Errorf("[5] Permanent Failure.")
case 6:
return make([]byte, 0), fmt.Errorf("[6] Client Certificate Required (Unsupported)")
default:
return make([]byte, 0), fmt.Errorf("Invalid response status from server")
}
}
return []byte(resp[1]), nil
}
func Visit(host, port, resource string, td *TofuDigest) (Capsule, error) {
capsule := MakeCapsule()
rawResp, err := Retrieve(host, port, resource, td)
if err != nil {
return capsule, err
}
resp := strings.SplitN(rawResp, "\r\n", 2)
if len(resp) != 2 {
if err != nil {
return capsule, fmt.Errorf("Invalid response from server")
}
}
header := strings.SplitN(resp[0], " ", 2)
if len([]rune(header[0])) != 2 {
header = strings.SplitN(resp[0], "\t", 2)
if len([]rune(header[0])) != 2 {
return capsule, fmt.Errorf("Invalid response format from server")
}
}
body := resp[1]
// Get status code single digit form
capsule.Status, err = strconv.Atoi(string(header[0][0]))
if err != nil {
return capsule, fmt.Errorf("Invalid status response from server")
}
// Parse the meta as needed
var meta string
switch capsule.Status {
case 1:
capsule.Content = header[1]
return capsule, nil
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")
}
capsule.MimeMaj = minMajMime[0]
capsule.MimeMin = minMajMime[1]
if capsule.MimeMaj == "text" && capsule.MimeMin == "gemini" {
if len(resource) > 0 && resource[0] != '/' {
resource = fmt.Sprintf("/%s", resource)
} else if resource == "" {
resource = "/"
}
currentUrl := fmt.Sprintf("gemini://%s:%s%s", host, port, resource)
capsule.Content, capsule.Links = parseGemini(body, currentUrl)
} else {
capsule.Content = body
}
return capsule, nil
case 3:
// The client will handle informing the user of a redirect
// and then request the new url
capsule.Content = header[1]
return capsule, nil
case 4:
return capsule, fmt.Errorf("[4] Temporary Failure. %s", header[1])
case 5:
return capsule, fmt.Errorf("[5] Permanent Failure. %s", header[1])
case 6:
return capsule, fmt.Errorf("[6] Client Certificate Required (Unsupported)")
default:
return capsule, fmt.Errorf("Invalid response status from server")
}
}
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")
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")
if splitPoint < 0 || len([]rune(subLn))-1 <= splitPoint {
link = subLn
decorator = subLn
} else {
link = strings.Trim(subLn[:splitPoint], "\t\n\r \a")
decorator = strings.Trim(subLn[splitPoint:], "\t\n\r \a")
}
if strings.Index(link, "://") < 0 {
link, _ = HandleRelativeUrl(link, currentUrl)
}
links = append(links, link)
linknum := fmt.Sprintf("[%d]", len(links))
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[:outputIndex], "\n"), links
}
// handleRelativeUrl provides link completion
func HandleRelativeUrl(relLink, current string) (string, error) {
base, err := url.Parse(current)
if err != nil {
return relLink, err
}
rel, err := url.Parse(relLink)
if err != nil {
return relLink, err
}
return base.ResolveReference(rel).String(), nil
}
func hashCert(cert []byte) string {
hash := sha1.Sum(cert)
hex := make([][]byte, len(hash))
for i, data := range hash {
hex[i] = []byte(fmt.Sprintf("%02X", data))
}
return fmt.Sprintf("%s", string(bytes.Join(hex, []byte(":"))))
}
func MakeCapsule() Capsule {
return Capsule{"", "", 0, "", make([]string, 0, 5)}
}
func MakeTofuDigest() TofuDigest {
return TofuDigest{make(map[string]string)}
}

2
go.mod
View File

@ -1,3 +1,3 @@
module tildegit.org/sloum/bombadillo
go 1.10
go 1.11

View File

@ -1,65 +0,0 @@
package gopher
import (
"fmt"
"strings"
)
//------------------------------------------------\\
// + + + T Y P E S + + + \\
//--------------------------------------------------\\
//Bookmarks is a holder for titles and links that
//can be retrieved by index
type Bookmarks struct {
Titles []string
Links []string
}
//------------------------------------------------\\
// + + + R E C E I V E R S + + + \\
//--------------------------------------------------\\
// Add adds a new title and link combination to the bookmarks
// struct. It takes as input a string slice in which the first
// element represents the link and all following items represent
// the title of the bookmark (they will be joined with spaces).
func (b *Bookmarks) Add(v []string) error {
if len(v) < 2 {
return fmt.Errorf("Received %d arguments, expected 2 or more", len(v))
}
b.Titles = append(b.Titles, strings.Join(v[1:], " "))
b.Links = append(b.Links, v[0])
return nil
}
func (b *Bookmarks) Del(i int) error {
if i < len(b.Titles) && i < len(b.Links) {
b.Titles = append(b.Titles[:i], b.Titles[i+1:]...)
b.Links = append(b.Links[:i], b.Links[i+1:]...)
return nil
}
return fmt.Errorf("Bookmark %d does not exist", i)
}
func (b Bookmarks) List() []string {
var out []string
for i, t := range b.Titles {
out = append(out, fmt.Sprintf("[%d] %s", i, t))
}
return out
}
func (b Bookmarks) IniDump() string {
if len(b.Titles) < 0 {
return ""
}
out := "[BOOKMARKS]\n"
for i := 0; i < len(b.Titles); i++ {
out += b.Titles[i]
out += "="
out += b.Links[i]
out += "\n"
}
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/>.
*/
// 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.
@ -21,19 +37,25 @@ import (
var types = map[string]string{
"0": "TXT",
"1": "MAP",
"h": "HTM",
"3": "ERR",
"4": "BIN",
"5": "DOS",
"s": "SND",
"g": "GIF",
"I": "IMG",
"9": "BIN",
"7": "FTS",
"6": "UUE",
"7": "FTS",
"8": "TEL",
"9": "BIN",
"g": "GIF",
"G": "GEM",
"h": "HTM",
"I": "IMG",
"p": "PNG",
"s": "SND",
"S": "SSH",
"T": "TEL",
}
var Timeout time.Duration = time.Duration(15) * time.Second
//------------------------------------------------\\
// + + + F U N C T I O N S + + + \\
//--------------------------------------------------\\
@ -43,25 +65,21 @@ var types = map[string]string{
// available to use directly, but in most implementations
// using the "Visit" receiver of the History struct will
// be better.
func Retrieve(u Url) ([]byte, error) {
func Retrieve(host, port, resource string) ([]byte, error) {
nullRes := make([]byte, 0)
timeOut := time.Duration(5) * time.Second
if u.Host == "" || u.Port == "" {
if host == "" || port == "" {
return nullRes, errors.New("Incomplete request url")
}
addr := u.Host + ":" + u.Port
addr := host + ":" + port
conn, err := net.DialTimeout("tcp", addr, timeOut)
conn, err := net.DialTimeout("tcp", addr, Timeout)
if err != nil {
return nullRes, err
}
send := u.Resource + "\n"
if u.Scheme == "http" || u.Scheme == "https" {
send = u.Gophertype
}
send := resource + "\n"
_, err = conn.Write([]byte(send))
if err != nil {
@ -73,43 +91,29 @@ func Retrieve(u Url) ([]byte, error) {
return nullRes, err
}
return result, err
return result, nil
}
// Visit is a high level combination of a few different
// types that makes it easy to create a Url, make a request
// to that Url, and add the response and Url to a View.
// Returns a copy of the view and an error (or nil).
func Visit(addr, openhttp string) (View, error) {
u, err := MakeUrl(addr)
// Visit handles the making of the request, parsing of maps, and returning
// the correct information to the client
func Visit(gophertype, host, port, resource string) (string, []string, error) {
resp, err := Retrieve(host, port, resource)
if err != nil {
return View{}, err
return "", []string{}, err
}
if u.Gophertype == "h" {
if res, tf := isWebLink(u.Resource); tf && strings.ToUpper(openhttp) == "TRUE" {
err := openBrowser(res)
if err != nil {
return View{}, err
}
text := string(resp)
links := []string{}
return View{}, fmt.Errorf("")
}
}
text, err := Retrieve(u)
if err != nil {
return View{}, err
if IsDownloadOnly(gophertype) {
return text, []string{}, nil
}
var pageContent []string
if u.IsBinary && u.Gophertype != "7" {
pageContent = []string{string(text)}
} else {
pageContent = strings.Split(string(text), "\n")
if gophertype == "1" {
text, links = parseMap(text)
}
return MakeView(u, pageContent), nil
return text, links, nil
}
func getType(t string) string {
@ -127,3 +131,74 @@ func isWebLink(resource string) (string, bool) {
}
return "", false
}
func parseMap(text string) (string, []string) {
splitContent := strings.Split(text, "\n")
links := make([]string, 0, 10)
for i, e := range splitContent {
e = strings.Trim(e, "\r\n")
if e == "." {
splitContent[i] = ""
continue
}
line := strings.Split(e, "\t")
var title 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) < 4 || strings.HasPrefix(line[0], "i") {
splitContent[i] = " " + string(title)
} else {
link := buildLink(line[2], line[3], string(line[0][0]), line[1])
links = append(links, link)
linkNum := fmt.Sprintf("[%d]",len(links))
linktext := fmt.Sprintf("%s %5s %s", getType(string(line[0][0])), linkNum, title)
splitContent[i] = linktext
}
}
return strings.Join(splitContent, "\n"), links
}
// Returns false for all text formats (including html
// even though it may link out. Things like telnet
// should never make it into the retrieve call for
// this module, having been handled in the client
// based on their protocol.
func IsDownloadOnly(gophertype string) bool {
switch gophertype {
case "0", "1", "3", "7", "h":
return false
default:
return true
}
}
func buildLink(host, port, gtype, resource string) string {
switch gtype {
case "8", "T":
return fmt.Sprintf("telnet://%s:%s", host, port)
case "G":
return fmt.Sprintf("gemini://%s:%s%s", host, port, resource)
case "h":
u, tf := isWebLink(resource)
if tf {
if strings.Index(u, "://") > 0 {
return u
} else {
return fmt.Sprintf("http://%s", u)
}
}
return fmt.Sprintf("gopher://%s:%s/h%s", host, port, resource)
default:
return fmt.Sprintf("gopher://%s:%s/%s%s", host, port, gtype, resource)
}
}

View File

@ -1,112 +0,0 @@
package gopher
import (
"errors"
"fmt"
)
//------------------------------------------------\\
// + + + T Y P E S + + + \\
//--------------------------------------------------\\
// The history struct represents the history of the browsing
// session. It contains the current history position, the
// length of the active history space (this can be different
// from the available capacity in the Collection), and a
// collection array containing View structs representing
// each page in the current history. In general usage this
// struct should be initialized via the MakeHistory function.
type History struct {
Position int
Length int
Collection [20]View
}
//------------------------------------------------\\
// + + + R E C E I V E R S + + + \\
//--------------------------------------------------\\
// The "Add" receiver takes a view and adds it to
// the history struct that called it. "Add" returns
// nothing. "Add" will shift history down if the max
// history length would be exceeded, and will reset
// history length if something is added in the middle.
func (h *History) Add(v View) {
v.ParseMap()
if h.Position == h.Length-1 && h.Length < len(h.Collection) {
h.Collection[h.Length] = v
h.Length++
h.Position++
} else if h.Position == h.Length-1 && h.Length == 20 {
for x := 1; x < len(h.Collection); x++ {
h.Collection[x-1] = h.Collection[x]
}
h.Collection[len(h.Collection)-1] = v
} else {
h.Position += 1
h.Length = h.Position + 1
h.Collection[h.Position] = v
}
}
// The "Get" receiver is called by a history struct
// and returns a View from the current position, will
// return an error if history is empty and there is
// nothing to get.
func (h History) Get() (*View, error) {
if h.Position < 0 {
return nil, errors.New("History is empty, cannot get item from empty history.")
}
return &h.Collection[h.Position], nil
}
// The "GoBack" receiver is called by a history struct.
// When called it decrements the current position and
// displays the content for the View in that position.
// If history is at position 0, no action is taken.
func (h *History) GoBack() bool {
if h.Position > 0 {
h.Position--
return true
}
fmt.Print("\a")
return false
}
// The "GoForward" receiver is called by a history struct.
// When called it increments the current position and
// displays the content for the View in that position.
// If history is at position len - 1, no action is taken.
func (h *History) GoForward() bool {
if h.Position+1 < h.Length {
h.Position++
return true
}
fmt.Print("\a")
return false
}
// The "DisplayCurrentView" receiver is called by a history
// struct. It calls the Display receiver for th view struct
// at the current history position. "DisplayCurrentView" does
// not return anything, and does nothing if position is less
// that 0.
func (h *History) DisplayCurrentView() {
h.Collection[h.Position].Display()
}
//------------------------------------------------\\
// + + + F U N C T I O N S + + + \\
//--------------------------------------------------\\
// Constructor function for History struct.
// This is used to initialize history position
// as -1, which is needed. Returns a copy of
// initialized History struct (does NOT return
// a pointer to the struct).
func MakeHistory() History {
return History{-1, 0, [20]View{}}
}

View File

@ -1,9 +0,0 @@
// +build darwin
package gopher
import "os/exec"
func openBrowser(url string) error {
return exec.Command("open", url).Start()
}

View File

@ -1,9 +0,0 @@
// +build linux
package gopher
import "os/exec"
func openBrowser(url string) error {
return exec.Command("xdg-open", url).Start()
}

View File

@ -1,11 +0,0 @@
// +build !linux
// +build !darwin
// +build !windows
package gopher
import "fmt"
func openBrowser(url string) error {
return fmt.Errorf("Unsupported os for browser detection")
}

View File

@ -1,9 +0,0 @@
// +build windows
package gopher
import "os/exec"
func openBrowser(url string) error {
return exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
}

View File

@ -1,94 +0,0 @@
package gopher
import (
"errors"
"regexp"
"strings"
)
//------------------------------------------------\\
// + + + T Y P E S + + + \\
//--------------------------------------------------\\
// The url struct represents a URL for the rest of the system.
// It includes component parts as well as a full URL string.
type Url struct {
Scheme string
Host string
Port string
Gophertype string
Resource string
Full string
IsBinary bool
}
//------------------------------------------------\\
// + + + F U N C T I O N S + + + \\
//--------------------------------------------------\\
// MakeUrl is a Url constructor that takes in a string
// representation of a url and returns a Url struct and
// an error (or nil).
func MakeUrl(u string) (Url, error) {
var out Url
re := regexp.MustCompile(`^((?P<scheme>gopher|http|https|ftp|telnet):\/\/)?(?P<host>[\w\-\.\d]+)(?::(?P<port>\d+)?)?(?:/(?P<type>[01345679gIhisp])?)?(?P<resource>.*)?$`)
match := re.FindStringSubmatch(u)
if valid := re.MatchString(u); !valid {
return out, errors.New("Invalid URL or command character")
}
for i, name := range re.SubexpNames() {
switch name {
case "scheme":
out.Scheme = match[i]
case "host":
out.Host = match[i]
case "port":
out.Port = match[i]
case "type":
out.Gophertype = match[i]
case "resource":
out.Resource = match[i]
}
}
if out.Scheme == "" {
out.Scheme = "gopher"
}
if out.Host == "" {
return out, errors.New("no host")
}
if out.Scheme == "gopher" && out.Port == "" {
out.Port = "70"
} else if out.Scheme == "http" && out.Port == "" {
out.Port = "80"
} else if out.Scheme == "https" && out.Port == "" {
out.Port = "443"
}
if out.Gophertype == "" && (out.Resource == "" || out.Resource == "/") {
out.Gophertype = "1"
}
if out.Scheme == "gopher" && out.Gophertype == "" {
out.Gophertype = "0"
}
if out.Gophertype == "7" && strings.Contains(out.Resource, "\t") {
out.Gophertype = "1"
}
switch out.Gophertype {
case "1", "0", "h", "7":
out.IsBinary = false
default:
out.IsBinary = true
}
out.Full = out.Scheme + "://" + out.Host + ":" + out.Port + "/" + out.Gophertype + out.Resource
return out, nil
}

View File

@ -1,83 +0,0 @@
package gopher
import (
"fmt"
"strings"
)
//------------------------------------------------\\
// + + + T Y P E S + + + \\
//--------------------------------------------------\\
// View is a struct representing a gopher page. It contains
// the page content as a string slice, a list of link URLs
// as string slices, and the Url struct representing the page.
type View struct {
Content []string
Links []string
Address Url
}
//------------------------------------------------\\
// + + + R E C E I V E R S + + + \\
//--------------------------------------------------\\
// ParseMap is called by a view struct to parse a gophermap.
// It checks if the view is for a gophermap. If not,it does
// nothing. If so, it parses the gophermap into comment lines
// and link lines. For link lines it adds a link to the links
// slice and changes the content value to just the printable
// string plus a gophertype indicator and a link number that
// relates to the link position in the links slice. This
// receiver does not return anything.
func (v *View) ParseMap() {
if v.Address.Gophertype == "1" || v.Address.Gophertype == "7" {
for i, e := range v.Content {
e = strings.Trim(e, "\r\n")
if e == "." {
v.Content[i] = " "
continue
}
line := strings.Split(e, "\t")
var title string
if len(line[0]) > 1 {
title = line[0][1:]
} else {
title = ""
}
if len(line) > 1 && len(line[0]) > 0 && string(line[0][0]) == "i" {
v.Content[i] = " " + string(title)
} else if len(line) >= 4 {
fulllink := fmt.Sprintf("%s:%s/%s%s", line[2], line[3], string(line[0][0]), line[1])
v.Links = append(v.Links, fulllink)
linktext := fmt.Sprintf("(%s) %2d %s", getType(string(line[0][0])), len(v.Links), title)
v.Content[i] = linktext
}
}
}
}
// Display is called on a view struct to print the contents of the view.
// This receiver does not return anything.
func (v View) Display() {
fmt.Println()
for _, el := range v.Content {
fmt.Println(el)
}
}
//------------------------------------------------\\
// + + + F U N C T I O N S + + + \\
//--------------------------------------------------\\
// MakeView creates and returns a new View struct from
// a Url and a string splice of content. This is used to
// initialize a View with a Url struct, links, and content.
// It takes a Url struct and a content []string and returns
// a View (NOT a pointer to a View).
func MakeView(url Url, content []string) View {
v := View{content, make([]string, 0), url}
v.ParseMap()
return v
}

54
headbar.go Normal file
View File

@ -0,0 +1,54 @@
/*
* 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"
)
//------------------------------------------------\\
// + + + T Y P E S + + + \\
//--------------------------------------------------\\
// Headbar represents the contents of the top bar of
// the client and contains the client name and the
// current URL
type Headbar struct {
title string
url string
}
//------------------------------------------------\\
// + + + R E C E I V E R S + + + \\
//--------------------------------------------------\\
// Render returns a string with the contents of theHeadbar
func (h *Headbar) Render(width int, theme string) string {
maxMsgWidth := width - len([]rune(h.title)) - 2
if theme == "inverse" {
return fmt.Sprintf("\033[7m%s▟\033[27m %-*.*s\033[0m", h.title, maxMsgWidth, maxMsgWidth, h.url)
}
return fmt.Sprintf("%s▟\033[7m %-*.*s\033[0m", h.title, maxMsgWidth, maxMsgWidth, h.url)
}
//------------------------------------------------\\
// + + + F U N C T I O N S + + + \\
//--------------------------------------------------\\
// MakeHeadbar returns a Headbar with default values
func MakeHeadbar(title string) Headbar {
return Headbar{title, ""}
}

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]]`",
}

113
http/http_render.go Normal file
View File

@ -0,0 +1,113 @@
/*
* 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"
"io/ioutil"
"net/http"
"os/exec"
"strings"
)
// Page represents the contents and links or an http/https document
type Page struct {
Content string
Links []string
}
// Visit is the main entry to viewing a web document in bombadillo.
// It takes a url, a terminal width, and which web backend the user
// currently has set. Visit returns a Page and an error
func Visit(webmode, url string, width int) (Page, error) {
if width > 80 {
width = 80
}
var w string
switch webmode {
case "lynx":
w = "-width"
case "w3m":
w = "-cols"
case "elinks":
w = "-dump-width"
default:
return Page{}, fmt.Errorf("Invalid webmode setting")
}
c, err := exec.Command(webmode, "-dump", w, fmt.Sprintf("%d", width), url).Output()
if err != nil && c == nil {
return Page{}, err
}
return parseLinks(string(c)), nil
}
// IsTextFile makes an http(s) head request to a given URL
// and determines if the content-type is text based. It then
// returns a bool
func IsTextFile(url string) bool {
resp, err := http.Head(url)
if err != nil {
return false
}
ctype := resp.Header.Get("content-type")
if strings.Contains(ctype, "text") || ctype == "" {
return true
}
return false
}
func parseLinks(c string) Page {
var out Page
contentUntil := strings.LastIndex(c, "References")
if contentUntil >= 1 {
out.Content = c[:contentUntil]
} else {
out.Content = c
out.Links = make([]string, 0)
return out
}
links := c[contentUntil+11:]
links = strings.TrimSpace(links)
linkSlice := strings.Split(links, "\n")
out.Links = make([]string, 0, len(linkSlice))
for _, link := range linkSlice {
ls := strings.SplitN(link, ".", 2)
if len(ls) < 2 {
continue
}
out.Links = append(out.Links, strings.TrimSpace(ls[1]))
}
return out
}
// Fetch makes an http(s) request and returns the []bytes
// for the response and an error. Fetch is used for saving
// the source file of an http(s) document
func Fetch(url string) ([]byte, error) {
resp, err := http.Get(url)
if err != nil {
return []byte{}, err
}
defer resp.Body.Close()
bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return []byte{}, err
}
return bodyBytes, nil
}

View File

@ -0,0 +1,29 @@
/*
* 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
import "os/exec"
func OpenInBrowser(url string) (string, error) {
err := exec.Command("open", url).Start()
if err != nil {
return "", err
}
return "Opened in system default web browser", nil
}

View File

@ -0,0 +1,46 @@
// +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"
"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

@ -0,0 +1,29 @@
/*
* 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"
func OpenInBrowser(url string) (string, error) {
err := exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
if err != nil {
return "", err
}
return "Opened in system default web browser", nil
}

105
local/local.go Normal file
View File

@ -0,0 +1,105 @@
/*
* 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 (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
)
func Open(address string) (string, []string, error) {
links := make([]string, 0, 10)
if !pathExists(address) {
return "", links, fmt.Errorf("Invalid system path: %s", address)
}
file, err := os.Open(address)
if err != nil {
return "", links, err
}
defer file.Close()
if pathIsDir(address) {
offset := 1
fileList, err := file.Readdir(0)
if err != nil {
return "", links, err
}
var out strings.Builder
out.WriteString(fmt.Sprintf("Current directory: %s\n\n", address))
// Handle 'addres/..' display
offset = 2
upFp := filepath.Join(address, "..")
upOneLevel, _ := filepath.Abs(upFp)
info, err := os.Stat(upOneLevel)
if err == nil {
out.WriteString("[1] ")
out.WriteString(fmt.Sprintf("%-12s ", info.Mode().String()))
out.WriteString("../\n")
links = append(links, upOneLevel)
}
// Sort the directory contents alphabetically
sort.Slice(fileList, func(i, j int) bool {
return fileList[i].Name() < fileList[j].Name()
})
// Handle each item in the directory
for i, obj := range fileList {
linkNum := fmt.Sprintf("[%d]", i+offset)
out.WriteString(fmt.Sprintf("%-5s ", linkNum))
out.WriteString(fmt.Sprintf("%-12s ", obj.Mode().String()))
out.WriteString(obj.Name())
if obj.IsDir() {
out.WriteString("/")
}
out.WriteString("\n")
fp := filepath.Join(address, obj.Name())
links = append(links, fp)
}
return out.String(), links, nil
}
bytes, err := ioutil.ReadAll(file)
if err != nil {
return "", links, err
}
return string(bytes), links, nil
}
func pathExists(p string) bool {
exists := true
if _, err := os.Stat(p); os.IsNotExist(err) {
exists = false
}
return exists
}
func pathIsDir(p string) bool {
info, err := os.Stat(p)
if err != nil {
return false
}
return info.IsDir()
}

724
main.go
View File

@ -1,570 +1,252 @@
package main
// Bombadillo is an internet client for the terminal of unix or
// unix-like systems.
//
/*
* 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"
"io/ioutil"
"os"
"os/user"
"regexp"
"os/signal"
"path/filepath"
"strconv"
"strings"
"syscall"
"time"
"tildegit.org/sloum/bombadillo/cmdparse"
"tildegit.org/sloum/bombadillo/config"
"tildegit.org/sloum/bombadillo/cui"
"tildegit.org/sloum/bombadillo/gopher"
"tildegit.org/sloum/bombadillo/gemini"
)
var helplocation string = "gopher://colorfield.space:70/1/bombadillo-info"
var history gopher.History = gopher.MakeHistory()
var screen *cui.Screen
var userinfo, _ = user.Current()
var version string = "2.3.3"
var bombadillo *client
var helplocation string = "gopher://bombadillo.colorfield.space:70/1/user-guide.map"
var settings config.Config
var options = map[string]string{
"homeurl": "gopher://colorfield.space:70/1/bombadillo-info",
"savelocation": userinfo.HomeDir,
"searchengine": "gopher://gopher.floodgap.com:70/7/v2/vs",
"openhttp": "false",
"httpbrowser": "lynx",
}
func saveFile(address, name string) error {
quickMessage("Saving file...", false)
url, err := gopher.MakeUrl(address)
if err != nil {
quickMessage("Saving file...", true)
return err
}
data, err := gopher.Retrieve(url)
if err != nil {
quickMessage("Saving file...", true)
return err
}
err = ioutil.WriteFile(options["savelocation"]+name, data, 0644)
if err != nil {
quickMessage("Saving file...", true)
return err
}
quickMessage(fmt.Sprintf("Saved file to %s%s", options["savelocation"], name), false)
return nil
}
func saveFileFromData(v gopher.View) error {
quickMessage("Saving file...", false)
urlsplit := strings.Split(v.Address.Full, "/")
filename := urlsplit[len(urlsplit)-1]
saveMsg := fmt.Sprintf("Saved file as %q", options["savelocation"]+filename)
err := ioutil.WriteFile(options["savelocation"]+filename, []byte(strings.Join(v.Content, "")), 0644)
if err != nil {
quickMessage("Saving file...", true)
return err
}
quickMessage(saveMsg, false)
return nil
}
func search(u string) error {
cui.MoveCursorTo(screen.Height-1, 0)
cui.Clear("line")
fmt.Print("Enter form input: ")
cui.MoveCursorTo(screen.Height-1, 17)
entry, err := cui.GetLine()
if err != nil {
return err
}
quickMessage("Searching...", false)
searchurl := fmt.Sprintf("%s\t%s", u, entry)
sv, err := gopher.Visit(searchurl, options["openhttp"])
if err != nil {
quickMessage("Searching...", true)
return err
}
history.Add(sv)
quickMessage("Searching...", true)
updateMainContent()
screen.Windows[0].Scrollposition = 0
screen.ReflashScreen(true)
return nil
}
func routeInput(com *cmdparse.Command) error {
var err error
switch com.Type {
case cmdparse.SIMPLE:
err = simpleCommand(com.Action)
case cmdparse.GOURL:
err = goToURL(com.Target)
case cmdparse.GOLINK:
err = goToLink(com.Target)
case cmdparse.DO:
err = doCommand(com.Action, com.Value)
case cmdparse.DOLINK:
err = doLinkCommand(com.Action, com.Target)
case cmdparse.DOAS:
err = doCommandAs(com.Action, com.Value)
case cmdparse.DOLINKAS:
err = doLinkCommandAs(com.Action, com.Target, com.Value)
default:
return fmt.Errorf("Unknown command entry!")
}
return err
}
func toggleBookmarks() {
bookmarks := screen.Windows[1]
main := screen.Windows[0]
if bookmarks.Show {
bookmarks.Show = false
screen.Activewindow = 0
main.Active = true
bookmarks.Active = false
} else {
bookmarks.Show = true
screen.Activewindow = 1
main.Active = false
bookmarks.Active = true
}
screen.ReflashScreen(false)
}
func simpleCommand(a string) error {
a = strings.ToUpper(a)
switch a {
case "Q", "QUIT":
cui.Exit()
case "H", "HOME":
return goHome()
case "B", "BOOKMARKS":
toggleBookmarks()
case "SEARCH":
return search(options["searchengine"])
case "HELP", "?":
return goToURL(helplocation)
default:
return fmt.Errorf("Unknown action %q", a)
}
return nil
}
func goToURL(u string) error {
if num, _ := regexp.MatchString(`^-?\d+.?\d*$`, u); num {
return goToLink(u)
}
quickMessage("Loading...", false)
v, err := gopher.Visit(u, options["openhttp"])
if err != nil {
quickMessage("Loading...", true)
return err
}
quickMessage("Loading...", true)
if v.Address.Gophertype == "7" {
err := search(v.Address.Full)
if err != nil {
return err
}
} else if v.Address.IsBinary {
return saveFileFromData(v)
} else {
history.Add(v)
}
updateMainContent()
screen.Windows[0].Scrollposition = 0
screen.ReflashScreen(true)
return nil
}
func goToLink(l string) error {
if num, _ := regexp.MatchString(`^-?\d+$`, l); num && history.Length > 0 {
linkcount := len(history.Collection[history.Position].Links)
item, _ := strconv.Atoi(l)
if item <= linkcount && item > 0 {
linkurl := history.Collection[history.Position].Links[item-1]
quickMessage("Loading...", false)
v, err := gopher.Visit(linkurl, options["openhttp"])
if err != nil {
quickMessage("Loading...", true)
return err
}
quickMessage("Loading...", true)
if v.Address.Gophertype == "7" {
err := search(linkurl)
if err != nil {
return err
}
} else if v.Address.IsBinary {
return saveFileFromData(v)
} else {
history.Add(v)
}
} else {
return fmt.Errorf("Invalid link id: %s", l)
}
} else {
return fmt.Errorf("Invalid link id: %s", l)
}
updateMainContent()
screen.Windows[0].Scrollposition = 0
screen.ReflashScreen(true)
return nil
}
func goHome() error {
if options["homeurl"] != "unset" {
return goToURL(options["homeurl"])
}
return fmt.Errorf("No home address has been set")
}
func doLinkCommand(action, target string) error {
num, err := strconv.Atoi(target)
if err != nil {
return fmt.Errorf("Expected number, got %q", target)
}
switch action {
case "DELETE", "D":
err := settings.Bookmarks.Del(num)
if err != nil {
return err
}
screen.Windows[1].Content = settings.Bookmarks.List()
err = saveConfig()
if err != nil {
return err
}
screen.ReflashScreen(false)
return nil
case "BOOKMARKS", "B":
if num > len(settings.Bookmarks.Links)-1 {
return fmt.Errorf("There is no bookmark with ID %d", num)
}
err := goToURL(settings.Bookmarks.Links[num])
return err
}
return fmt.Errorf("This method has not been built")
}
func doCommandAs(action string, values []string) error {
if len(values) < 2 {
return fmt.Errorf("%q", values)
}
if values[0] == "." {
values[0] = history.Collection[history.Position].Address.Full
}
switch action {
case "ADD", "A":
err := settings.Bookmarks.Add(values)
if err != nil {
return err
}
screen.Windows[1].Content = settings.Bookmarks.List()
err = saveConfig()
if err != nil {
return err
}
screen.ReflashScreen(false)
return nil
case "WRITE", "W":
return saveFile(values[0], strings.Join(values[1:], " "))
case "SET", "S":
if _, ok := options[values[0]]; ok {
options[values[0]] = strings.Join(values[1:], " ")
return saveConfig()
}
return fmt.Errorf("Unable to set %s, it does not exist", values[0])
}
return fmt.Errorf("Unknown command structure")
}
func doCommand(action string, values []string) error {
if length := len(values); length != 1 {
return fmt.Errorf("Expected 1 argument, received %d", length)
}
switch action {
case "CHECK", "C":
err := checkConfigValue(values[0])
if err != nil {
return err
}
return nil
}
return fmt.Errorf("Unknown command structure")
}
func checkConfigValue(setting string) error {
if val, ok := options[setting]; ok {
quickMessage(fmt.Sprintf("%s is set to: %q", setting, val), false)
return nil
}
return fmt.Errorf("Unable to check %q, it does not exist", setting)
}
func doLinkCommandAs(action, target string, values []string) error {
num, err := strconv.Atoi(target)
if err != nil {
return fmt.Errorf("Expected number, got %q", target)
}
links := history.Collection[history.Position].Links
if num >= len(links) {
return fmt.Errorf("Invalid link id: %s", target)
}
switch action {
case "ADD", "A":
newBookmark := append([]string{links[num-1]}, values...)
err := settings.Bookmarks.Add(newBookmark)
if err != nil {
return err
}
screen.Windows[1].Content = settings.Bookmarks.List()
err = saveConfig()
if err != nil {
return err
}
screen.ReflashScreen(false)
return nil
case "WRITE", "W":
return saveFile(links[num-1], strings.Join(values, " "))
}
return fmt.Errorf("This method has not been built")
}
func updateMainContent() {
screen.Windows[0].Content = history.Collection[history.Position].Content
screen.Bars[0].SetMessage(history.Collection[history.Position].Address.Full)
}
func clearInput(incError bool) {
cui.MoveCursorTo(screen.Height-1, 0)
cui.Clear("line")
if incError {
cui.MoveCursorTo(screen.Height, 0)
cui.Clear("line")
}
}
func quickMessage(msg string, clearMsg bool) {
xPos := screen.Width - 2 - len(msg)
if xPos < 2 {
xPos = 2
}
cui.MoveCursorTo(screen.Height, xPos)
if clearMsg {
cui.Clear("right")
} else {
fmt.Print("\033[48;5;21m\033[38;5;15m", msg, "\033[0m")
}
}
func saveConfig() error {
bkmrks := settings.Bookmarks.IniDump()
opts := "\n[SETTINGS]\n"
for k, v := range options {
opts += k
opts += "="
opts += v
opts += "\n"
var opts strings.Builder
bkmrks := bombadillo.BookMarks.IniDump()
certs := bombadillo.Certs.IniDump()
opts.WriteString("\n[SETTINGS]\n")
for k, v := range bombadillo.Options {
opts.WriteString(k)
opts.WriteRune('=')
opts.WriteString(v)
opts.WriteRune('\n')
}
return ioutil.WriteFile(userinfo.HomeDir+"/.bombadillo.ini", []byte(bkmrks+opts), 0644)
opts.WriteString(bkmrks)
opts.WriteString(certs)
return ioutil.WriteFile(filepath.Join(bombadillo.Options["configlocation"], ".bombadillo.ini"), []byte(opts.String()), 0644)
}
func loadConfig() error {
file, err := os.Open(userinfo.HomeDir + "/.bombadillo.ini")
func validateOpt(opt, val string) bool {
var validOpts = map[string][]string{
"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)
val = strings.ToLower(val)
if _, ok := validOpts[opt]; ok {
for _, item := range validOpts[opt] {
if item == val {
return true
}
}
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", "showimages", "geminiblocks":
return strings.ToLower(val)
default:
return val
}
}
func loadConfig() {
err := os.MkdirAll(bombadillo.Options["configlocation"], 0755)
if err != nil {
exitMsg := fmt.Sprintf("Error creating 'configlocation' directory: %s", err.Error())
cui.Exit(3, exitMsg)
}
fp := filepath.Join(bombadillo.Options["configlocation"], ".bombadillo.ini")
file, err := os.Open(fp)
if err != nil {
err = saveConfig()
if err != nil {
return err
exitMsg := fmt.Sprintf("Error writing config file during bootup: %s", err.Error())
cui.Exit(4, exitMsg)
}
}
confparser := config.NewParser(file)
settings, _ = confparser.Parse()
file.Close()
screen.Windows[1].Content = settings.Bookmarks.List()
_ = file.Close()
for _, v := range settings.Settings {
lowerkey := strings.ToLower(v.Key)
if _, ok := options[lowerkey]; ok {
options[lowerkey] = v.Value
if lowerkey == "configlocation" {
// Read only
continue
}
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]
}
}
}
return nil
}
for i, v := range settings.Bookmarks.Titles {
_, _ = bombadillo.BookMarks.Add([]string{v, settings.Bookmarks.Links[i]})
}
func toggleActiveWindow() {
if screen.Windows[1].Show {
if screen.Windows[0].Active {
screen.Windows[0].Active = false
screen.Windows[1].Active = true
screen.Activewindow = 1
} else {
screen.Windows[0].Active = true
screen.Windows[1].Active = false
screen.Activewindow = 0
for _, v := range settings.Certs {
// Remove expired certs
vals := strings.SplitN(v.Value, "|", -1)
if len(vals) < 2 {
continue
}
screen.Windows[1].DrawWindow()
}
}
func displayError(err error) {
cui.MoveCursorTo(screen.Height, 0)
fmt.Print("\033[41m\033[37m", err, "\033[0m")
}
func initClient() error {
history.Position = -1
screen = cui.NewScreen()
cui.SetCharMode()
screen.AddWindow(2, 1, screen.Height-2, screen.Width, false, false, true)
screen.Windows[0].Active = true
screen.AddMsgBar(1, " ((( Bombadillo ))) ", " A fun gopher client!", true)
bookmarksWidth := 40
if screen.Width < 40 {
bookmarksWidth = screen.Width
}
screen.AddWindow(2, screen.Width-bookmarksWidth, screen.Height-2, screen.Width, false, true, false)
return loadConfig()
}
func handleResize() {
oldh, oldw := screen.Height, screen.Width
screen.GetSize()
if screen.Height != oldh || screen.Width != oldw {
screen.Windows[0].Box.Row2 = screen.Height - 2
screen.Windows[0].Box.Col2 = screen.Width
bookmarksWidth := 40
if screen.Width < 40 {
bookmarksWidth = screen.Width
now := time.Now()
ts, err := strconv.ParseInt(vals[1], 10, 64)
if err != nil || now.Unix() > ts {
continue
}
screen.Windows[1].Box.Row2 = screen.Height - 2
screen.Windows[1].Box.Col1 = screen.Width - bookmarksWidth
screen.Windows[1].Box.Col2 = screen.Width
screen.DrawAllWindows()
screen.DrawMsgBars()
screen.ClearCommandArea()
// 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()
}
// In the event of specific signals, ensure the display is shown correctly.
// Accepts a signal, blocking until it is received. Once not blocked, corrects
// terminal display settings as appropriate for that signal. Loops
// indefinitely, does not return.
func handleSignals(c <-chan os.Signal) {
for {
switch <-c {
case syscall.SIGTSTP:
cui.CleanupTerm()
_ = syscall.Kill(syscall.Getpid(), syscall.SIGSTOP)
case syscall.SIGCONT:
cui.InitTerm()
bombadillo.Draw()
case syscall.SIGINT:
cui.Exit(130, "")
}
}
}
//printHelp produces a nice display message when the --help flag is used
func printHelp() {
art := `Bombadillo - a non-web browser
Syntax: bombadillo [options] [url]
Examples: bombadillo gopher://bombadillo.colorfield.space
bombadillo -t
bombadillo -v
Options:
`
_, _ = fmt.Fprint(os.Stdout, art)
flag.PrintDefaults()
}
func main() {
cui.HandleAlternateScreen("smcup")
defer cui.Exit()
err := initClient()
if err != nil {
// if we can't initialize the window,
// we can't do anything!
panic(err)
getVersion := flag.Bool("v", false, "Display version information and exit")
addTitleToXWindow := flag.Bool("t", false, "Set the window title to 'Bombadillo'. Can be used in a GUI environment, however not all terminals support this feature.")
flag.Usage = printHelp
flag.Parse()
if *getVersion {
fmt.Printf("Bombadillo %s\n", version)
os.Exit(0)
}
args := flag.Args()
cui.InitTerm()
if *addTitleToXWindow {
fmt.Print("\033[22;0t") // Store window title on terminal stack
fmt.Print("\033]0;Bombadillo\007") // Update window title
}
mainWindow := screen.Windows[0]
defer cui.Exit(0, "")
initClient()
if len(os.Args) > 1 {
err = goToURL(os.Args[1])
// watch for signals, send them to be handled
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGTSTP, syscall.SIGCONT, syscall.SIGINT)
go handleSignals(c)
// Start polling for terminal size changes
go bombadillo.GetSize()
if len(args) > 0 {
// If a url was passed, move it down the line
// Goroutine so keypresses can be made during
// page load
bombadillo.Visit(args[0])
} else {
err = goHome()
}
if err != nil {
displayError(err)
} else {
updateMainContent()
// Otherwise, load the homeurl
// Goroutine so keypresses can be made during
// page load
bombadillo.Visit(bombadillo.Options["homeurl"])
}
// Loop indefinitely on user input
for {
c := cui.Getch()
handleResize()
switch c {
case 'j', 'J':
screen.Windows[screen.Activewindow].ScrollDown()
screen.ReflashScreen(false)
case 'k', 'K':
screen.Windows[screen.Activewindow].ScrollUp()
screen.ReflashScreen(false)
case 'q', 'Q':
cui.Exit()
case 'g':
screen.Windows[screen.Activewindow].ScrollHome()
screen.ReflashScreen(false)
case 'G':
screen.Windows[screen.Activewindow].ScrollEnd()
screen.ReflashScreen(false)
case 'd':
screen.Windows[screen.Activewindow].PageDown()
screen.ReflashScreen(false)
case 'u':
screen.Windows[screen.Activewindow].PageUp()
screen.ReflashScreen(false)
case 'b':
success := history.GoBack()
if success {
mainWindow.Scrollposition = 0
updateMainContent()
screen.ReflashScreen(true)
}
case 'B':
toggleBookmarks()
case 'f', 'F':
success := history.GoForward()
if success {
mainWindow.Scrollposition = 0
updateMainContent()
screen.ReflashScreen(true)
}
case '\t':
toggleActiveWindow()
case ':', ' ':
cui.MoveCursorTo(screen.Height-1, 0)
entry, err := cui.GetLine()
if err != nil {
displayError(err)
}
// Clear entry line and error line
clearInput(true)
if entry == "" {
continue
}
parser := cmdparse.NewParser(strings.NewReader(entry))
p, err := parser.Parse()
if err != nil {
displayError(err)
} else {
err := routeInput(p)
if err != nil {
displayError(err)
}
}
}
bombadillo.TakeControlInput()
}
}

View File

@ -1,60 +0,0 @@
TODO
- Add styles/color support
- Add code comments/documentation for all items
- Make sure html links using the URL convention work correctly
Control keys/input:
q quit
j scrolldown
k scrollup
f toggle showing favorites as subwindow
TODO - r refresh current page data (re-request)
GO
:# go to link num
:url go to url
SIMPLE
:quit quit
:home visit home
:bookmarks toogle bookmarks window
:search
:help
DOLINK
:delete # delete bookmark with num
:bookmarks # visit bookmark with num
DOLINKAS
:write # name write linknum to file
:add # name add link num as favorite
DOAS
:write url name write url to file
:add url name add link url as favorite
:set something something set a system variable
value, action, word
- - - - - - - - - - - - - - - - - -
Config format:
[favorites]
colorfield.space ++ gopher://colorfield.space:70/
My phlog ++ gopher://circumlunar.space/1/~sloum/
[options]
home ++ gopher://sdf.org
searchengine ++ gopher://floodgap.place/v2/veronicasomething
savelocation ++ ~/Downloads/
httpbrowser ++ lynx
openhttp ++ true

236
page.go Normal file
View File

@ -0,0 +1,236 @@
/*
* 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"
)
//------------------------------------------------\\
// + + + T Y P E S + + + \\
//--------------------------------------------------\\
// Page represents a visited URL's contents; including
// the raw content, wrapped content, link slice, URL,
// and the current scroll position
type Page struct {
WrappedContent []string
RawContent string
Links []string
Location Url
ScrollPosition int
FoundLinkLines []int
SearchTerm string
SearchIndex int
FileType string
WrapWidth int
Color bool
}
//------------------------------------------------\\
// + + + R E C E I V E R S + + + \\
//--------------------------------------------------\\
// ScrollPositionRange may not be in actual usage....
// TODO: find where this is being used
func (p *Page) ScrollPositionRange(termHeight int) (int, int) {
termHeight -= 3
if len(p.WrappedContent)-p.ScrollPosition < termHeight {
p.ScrollPosition = len(p.WrappedContent) - termHeight
}
if p.ScrollPosition < 0 {
p.ScrollPosition = 0
}
var end int
if len(p.WrappedContent) < termHeight {
end = len(p.WrappedContent)
} else {
end = p.ScrollPosition + termHeight
}
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, 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))
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)
}
if (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') {
escape = false
if ch == 'm' {
content.WriteString(esc.String())
esc.Reset()
}
}
continue
}
if ch == '\n' || ch == '\u0085' || ch == '\u2028' || ch == '\u2029' {
content.WriteRune('\n')
counter = 0
} else if ch == '\t' {
if counter+4 < width {
content.WriteString(" ")
counter += 4
} else {
content.WriteRune('\n')
counter = 0
}
} else if ch == '\r' || ch == '\v' || ch == '\b' || ch == '\f' || ch == '\a' {
// Get rid of control characters we don't want
continue
} else if ch == 27 {
if p.Location.Scheme == "local" {
if counter+4 >= width {
content.WriteRune('\n')
}
content.WriteString("\\033")
continue
}
escape = true
if color {
esc.WriteRune(ch)
}
continue
} else {
// 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
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()
}
func (p *Page) HighlightFoundText() {
if p.SearchTerm == "" {
return
}
for i, ln := range p.WrappedContent {
found := strings.Index(ln, p.SearchTerm)
if found < 0 {
continue
}
format := "\033[7m%s\033[27m"
if bombadillo.Options["theme"] == "inverse" {
format = "\033[27m%s\033[7m"
}
ln = strings.Replace(ln, p.SearchTerm, fmt.Sprintf(format, p.SearchTerm), -1)
p.WrappedContent[i] = ln
}
}
func (p *Page) FindText() {
p.FoundLinkLines = make([]int, 0, 10)
s := p.SearchTerm
p.SearchIndex = 0
if s == "" {
return
}
format := "\033[7m%s\033[27m"
if bombadillo.Options["theme"] == "inverse" {
format = "\033[27m%s\033[7m"
}
for i, ln := range p.WrappedContent {
found := strings.Index(ln, s)
if found < 0 {
continue
}
ln = strings.Replace(ln, s, fmt.Sprintf(format, s), -1)
p.WrappedContent[i] = ln
p.FoundLinkLines = append(p.FoundLinkLines, i)
}
}
//------------------------------------------------\\
// + + + F U N C T I O N S + + + \\
//--------------------------------------------------\\
// 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, "", 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)
}
})
}
}

129
pages.go Normal file
View File

@ -0,0 +1,129 @@
/*
* 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"
)
//------------------------------------------------\\
// + + + T Y P E S + + + \\
//--------------------------------------------------\\
// Pages is a struct that represents the history of the client.
// It functions as a container for the pages (history array) and
// tracks the current history length and location.
type Pages struct {
Position int
Length int
History [20]Page
}
//------------------------------------------------\\
// + + + R E C E I V E R S + + + \\
//--------------------------------------------------\\
// NavigateHistory takes a positive or negative integer
// and updates the current history position. Checks are done
// to make sure that the position moved to is a valid history
// location. Returns an error or nil.
func (p *Pages) NavigateHistory(qty int) error {
newPosition := p.Position + qty
if newPosition < 0 {
return fmt.Errorf("You are already at the beginning of history")
} else if newPosition > p.Length-1 {
return fmt.Errorf("Your way is blocked by void, there is nothing forward")
}
p.Position = newPosition
return nil
}
// Add gets passed a Page, which gets added to the history
// 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) {
if p.Position == p.Length-1 && p.Length < len(p.History) {
p.History[p.Length] = pg
p.Length++
p.Position++
} else if p.Position == p.Length-1 && p.Length == 20 {
for x := 1; x < len(p.History); x++ {
p.History[x-1] = p.History[x]
}
p.History[len(p.History)-1] = pg
} else {
p.Position++
p.Length = p.Position + 1
p.History[p.Position] = pg
}
}
// Render wraps the content for the current page and returns
// the page content as a string slice
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)
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
pos = pos - diff
} else if prev < now {
diff := now - prev
pos = pos + diff
if pos > now-termHeight {
pos = now - termHeight
}
}
if pos < 0 || now < termHeight-3 {
pos = 0
}
p.History[p.Position].ScrollPosition = pos
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 + + + \\
//--------------------------------------------------\\
// MakePages returns a Pages struct with default values
func MakePages() Pages {
return Pages{-1, 0, [20]Page{}}
}

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
}

52
telnet/telnet.go Normal file
View File

@ -0,0 +1,52 @@
/*
* 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
import (
"fmt"
"os"
"os/exec"
"tildegit.org/sloum/bombadillo/cui"
)
// StartSession starts a telnet session as a subprocess, connecting to the host
// and port specified. Telnet is run interactively as a subprocess until the
// process ends. It returns any errors from the telnet session.
func StartSession(host string, port string) (string, error) {
c := exec.Command("telnet", host, port)
c.Stdin = os.Stdin
c.Stdout = os.Stdout
c.Stderr = os.Stderr
// Clear the screen and position the cursor at the top left
fmt.Print("\033[2J\033[0;0H")
// Defer reset and reinit of the terminal to prevent any changes from
// telnet carrying over to the client (or beyond...)
defer func() {
cui.Tput("reset")
cui.InitTerm()
}()
err := c.Run()
if err != nil {
return "", fmt.Errorf("Telnet error response: %s", err.Error())
}
return "Telnet session terminated", nil
}

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)
}

207
url.go Normal file
View File

@ -0,0 +1,207 @@
/*
* 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"
)
//------------------------------------------------\\
// + + + T Y P E S + + + \\
//--------------------------------------------------\\
// Url is a struct representing the different pieces
// of a url. This custom struct is used rather than the
// built-in url library so-as to support gopher URLs, as
// well as track mime-type and renderability (can the
// response to the url be rendered as text in the client).
type Url struct {
Scheme string
Host string
Port string
Resource string
Full string
Mime string
DownloadOnly bool
}
//------------------------------------------------\\
// + + + R E C E I V E R S + + + \\
//--------------------------------------------------\\
// There are currently no receivers for the Url struct
//------------------------------------------------\\
// + + + F U N C T I O N S + + + \\
//--------------------------------------------------\\
// MakeUrl is a Url constructor that takes in a string
// representation of a url and returns a Url struct and
// an error (or nil).
func MakeUrl(u string) (Url, error) {
if len(u) < 1 {
return Url{}, fmt.Errorf("Invalid url, unable to parse")
}
if strings.HasPrefix(u, "finger://") {
return parseFinger(u)
}
var out Url
if local := strings.HasPrefix(u, "local://"); u[0] == '/' || u[0] == '.' || u[0] == '~' || local {
if local && len(u) > 8 {
u = u[8:]
}
var home string
userinfo, err := user.Current()
if err != nil {
home = ""
} else {
home = userinfo.HomeDir
}
u = strings.Replace(u, "~", home, 1)
res, err := filepath.Abs(u)
if err != nil {
return out, fmt.Errorf("Invalid path, unable to parse")
}
out.Scheme = "local"
out.Host = ""
out.Port = ""
out.Mime = ""
out.Resource = res
out.Full = out.Scheme + "://" + out.Resource
return out, nil
}
re := regexp.MustCompile(`^((?P<scheme>[a-zA-Z]+):\/\/)?(?P<host>[\w\-\.\d]+)(?::(?P<port>\d+)?)?(?:/(?P<type>[01345679gIhisp])?)?(?P<resource>.*)?$`)
match := re.FindStringSubmatch(u)
if valid := re.MatchString(u); !valid {
return out, fmt.Errorf("Invalid url, unable to parse")
}
for i, name := range re.SubexpNames() {
switch name {
case "scheme":
out.Scheme = match[i]
case "host":
out.Host = match[i]
case "port":
out.Port = match[i]
case "type":
out.Mime = match[i]
case "resource":
out.Resource = match[i]
}
}
if out.Host == "" {
return out, fmt.Errorf("no host")
}
out.Scheme = strings.ToLower(out.Scheme)
if out.Scheme == "" {
out.Scheme = bombadillo.Options["defaultscheme"]
}
if out.Scheme == "gopher" && out.Port == "" {
out.Port = "70"
} else if out.Scheme == "http" && out.Port == "" {
out.Port = "80"
} else if out.Scheme == "https" && out.Port == "" {
out.Port = "443"
} else if out.Scheme == "gemini" && out.Port == "" {
out.Port = "1965"
} else if out.Scheme == "telnet" && out.Port == "" {
out.Port = "23"
}
if out.Scheme == "gopher" {
if out.Mime == "" {
out.Mime = "1"
}
if out.Resource == "" || out.Resource == "/" {
out.Mime = "1"
}
if out.Mime == "7" && strings.Contains(out.Resource, "\t") {
out.Mime = "1"
}
switch out.Mime {
case "1", "0", "h", "7", "I", "g":
out.DownloadOnly = false
default:
out.DownloadOnly = true
}
} else {
out.Resource = fmt.Sprintf("%s%s", out.Mime, out.Resource)
out.Mime = ""
}
out.Full = out.Scheme + "://" + out.Host + ":" + out.Port + "/" + out.Mime + out.Resource
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"
if len(u) < 10 {
return out, fmt.Errorf("Invalid finger address")
}
u = u[9:]
userPlusAddress := strings.Split(u, "@")
if len(userPlusAddress) > 1 {
out.Resource = userPlusAddress[0]
u = userPlusAddress[1]
}
hostPort := strings.Split(u, ":")
if len(hostPort) < 2 {
out.Port = "79"
} else {
out.Port = hostPort[1]
}
out.Host = hostPort[0]
resource := ""
if out.Resource != "" {
resource = out.Resource + "@"
}
out.Full = fmt.Sprintf("%s://%s%s:%s", out.Scheme, resource, out.Host, out.Port)
return out, nil
}