From 6b0eda97246eec0e5fdd42879fa68921301643d0 Mon Sep 17 00:00:00 2001 From: Steven Stallion Date: Sun, 18 Apr 2021 16:15:17 -0500 Subject: [PATCH] Initial commit --- .gitignore | 8 + .travis.yml | 41 ++ CHANGELOG.md | 10 + CONTRIBUTING.md | 71 +++ LICENSE | 23 + MANIFEST.in | 3 + README.md | 117 +++++ docs/demo.cast | 982 +++++++++++++++++++++++++++++++++++++++ pyproject.toml | 2 + scripts/pre-commit.sh | 81 ++++ scripts/release_notes.py | 86 ++++ setup.cfg | 27 ++ setup.py | 60 +++ tests/conftest.py | 49 ++ tests/test_cli.py | 271 +++++++++++ tests/test_editor.py | 188 ++++++++ tests/test_wiki.py | 144 ++++++ vimwiki_cli/__init__.py | 25 + vimwiki_cli/__main__.py | 139 ++++++ vimwiki_cli/context.py | 53 +++ vimwiki_cli/diary.py | 73 +++ vimwiki_cli/editor.py | 132 ++++++ vimwiki_cli/tags.py | 62 +++ vimwiki_cli/wiki.py | 135 ++++++ 24 files changed, 2782 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 MANIFEST.in create mode 100644 README.md create mode 100644 docs/demo.cast create mode 100644 pyproject.toml create mode 100755 scripts/pre-commit.sh create mode 100755 scripts/release_notes.py create mode 100644 setup.cfg create mode 100644 setup.py create mode 100644 tests/conftest.py create mode 100644 tests/test_cli.py create mode 100644 tests/test_editor.py create mode 100644 tests/test_wiki.py create mode 100644 vimwiki_cli/__init__.py create mode 100644 vimwiki_cli/__main__.py create mode 100644 vimwiki_cli/context.py create mode 100644 vimwiki_cli/diary.py create mode 100644 vimwiki_cli/editor.py create mode 100644 vimwiki_cli/tags.py create mode 100644 vimwiki_cli/wiki.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2d33879 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +*.egg-info/ +*.py[co] +__pycache__/ +/.coverage +/.pytest_cache +/build +/dist +/venv diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..cf12a44 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,41 @@ +language: python + +python: + - 2.7 + - 3.6 + - 3.7 + - 3.8 + - 3.9 + +install: + - pip install -e .[test] + +script: + - pycodestyle + - pytest + +jobs: + include: + - stage: deploy + if: tag ~= /^v(\d+\.){2}\d+(-\S+)?$/ + python: 3.6 + install: + - pip install .[deploy] + script: + - export RELEASE_NOTES=$(scripts/release_notes.py $TRAVIS_TAG) + deploy: + - provider: pypi + edge: true + username: $PYPI_USERNAME + password: $PYPI_PASSWORD + distributions: sdist bdist_wheel + remove_build_dir: false + on: + tags: true + - provider: releases + edge: true + token: $GITHUB_TOKEN + file: dist/* + release_notes: $RELEASE_NOTES + on: + tags: true diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..5dcb29b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,10 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +[Unreleased]: https://github.com/sstallion/vimwiki-cli/compare/HEAD diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..5f5e022 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,71 @@ +# Contributing + +If you have an idea or feature request please open an [issue], even if you don't +have time to contribute! + +## Making Changes + +> **Note**: This guide assumes you have a working Python installation (most +> versions are supported) and have the Python Package Installer available in +> your PATH. + +To get started, [fork] this repository on GitHub and clone a working copy for +development: + + $ git clone git@github.com:YOUR-USERNAME/vimwiki-cli.git + +Once cloned, change the directory to your working copy, create a new virtualenv, +and activate the environment: + + $ virtualenv venv + $ source venv/bin/activate + +> **Note**: If the `virtualenv` command is not available, it can be installed +> locally by issuing `pip install --user virtualenv`. + +Dependencies are managed via `setuptools`; to set up the environment for +development, issue: + + $ pip install -e ".[test]" + +Once you are finished making changes, be sure to check the output of +`pycodestyle` and `pytest`. At a minimum, there should be no test regressions +and additional tests should be added for new functionality. If user-facing +changes are introduced, be sure add an entry to the `Unreleased` section in +[CHANGELOG.md] as well. + +With that out of the way, you can now commit your changes and create a [pull +request] against the `master` branch for review! + +## Making New Releases + +Making new releases is automated by the Travis CI deployment pipeline. Releases +should only be created from the `master` branch; as such `master` should be +passing tests at all times. + +To make a new release, follow these steps: + +1. Install dependencies by issuing `pip install -e ".[deploy]"` in an existing + virtualenv (see above). +2. Verify `pycodestyle` and `pytest` are passing locally. +3. Increment the version number by issuing `bump2version `. +4. Create a new section in [CHANGELOG.md] for the new version, and move items + from `Unreleased` to this section. Links should also be updated to point to + the correct tags for comparisons. +5. Verify release notes by issuing `scripts/release_notes.py v` and + make adjustments as needed. +6. Commit changes by issuing `git commit -a -m "Release v"`. +7. Create a release tag by issuing `git tag -a -m "Release v" + v`. +8. Push changes to `master` by issuing `git push origin master --tags`. +9. Verify Travis CI deploys successfully to PyPI and GitHub. + +## License + +By contributing to this repository, you agree that your contributions will be +licensed under its Simplified BSD License. + +[CHANGELOG.md]: https://github.com/sstallion/vimwiki-cli/blob/master/CHANGELOG.md +[fork]: https://docs.github.com/en/github/getting-started-with-github/fork-a-repo +[issue]: https://github.com/sstallion/vimwiki-cli/issues +[pull request]: https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8eb914b --- /dev/null +++ b/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2021 Steven Stallion +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..6de42a7 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,3 @@ +include CHANGELOG.md +include CONTRIBUTING.md +include LICENSE diff --git a/README.md b/README.md new file mode 100644 index 0000000..52e2896 --- /dev/null +++ b/README.md @@ -0,0 +1,117 @@ +# Vimwiki Command-Line Interface + +[![](https://img.shields.io/travis/com/sstallion/vimwiki-cli)][Travis] +[![](https://img.shields.io/pypi/v/vimwiki-cli)][PyPI] +[![](https://img.shields.io/pypi/pyversions/vimwiki-cli)][PyPI] +[![](https://img.shields.io/github/license/sstallion/vimwiki-cli)][LICENSE] + +`vimwiki-cli` is a command-line interface to [Vimwiki], a plugin for the [Vim] +text editor. It provides a front-end for interactive editor commands and can be +used to automate repetitive tasks such as rebuilding tag metadata and generating +links, all from the command line. + +[![](https://asciinema.org/a/411893.svg)](https://asciinema.org/a/411893?autoplay=1) + +## Installation + +`vimwiki-cli` is available on [PyPI] and can be installed locally using the +Python Package Installer: + + $ pip install --user vimwiki-cli + +Once installed, issue `vimwiki --help` to see usage. + +## Configuration + +For most installations no configuration is needed, however several environment +variables are available to modify default behavior without the need to pass +global options on the command line: + +| Environment Variable | Global Option | Description | +|------------------------|------------------|---------------------------------------------------| +| `VIMWIKI_EDITOR` | `--editor` | Editor to launch, defaults to `$EDITOR` or `vim`. | +| `VIMWIKI_COUNT` | `--count` | Index of wiki to open. | +| `VIMWIKI_SELECT` | `--select` | Select wiki from interactive list. | +| `VIMWIKI_OPEN_MATCHES` | `--open-matches` | Open search results by default. | +| `VIMWIKI_OPEN_TABS` | `--open-tabs` | Open pages in a new tab by default. | + +## Advanced + +### Supported Commands + +Each CLI command corresponds to one or more Ex commands executed in the editor. +The following table details the mapping between these commands: + +| CLI Command | Ex Commands | +|-----------------------------------------|---------------------------------------------------------------------| +| `vimwiki` | `:VimwikiIndex` | +| `vimwiki check-links` | `:VimwikiIndex \| VimwikiCheckLinks` | +| `vimwiki diary` | `:VimwikiDiaryIndex` | +| `vimwiki diary generate-links` | `:VimwikiDiaryIndex \| VimwikiDiaryGenerateLinks` | +| `vimwiki diary today` | `:VimwikiMakeDiaryNote` | +| `vimwiki diary tomorrow` | `:VimwikiMakeTomorrowDiaryNote` | +| `vimwiki diary yesterday` | `:VimwikiMakeYesterdayDiaryNote` | +| `vimwiki generate-links PAGE PATTERN` | `:VimwikiIndex \| VimwikiGoto PAGE \| VimwikiGenerateLinks PATTERN` | +| `vimwiki goto PAGE` | `:VimwikiIndex \| VimwikiGoto PAGE` | +| `vimwiki help` | `:help vimwiki.txt \| only` | +| `vimwiki search PATTERN` | `:VimwikiIndex \| VimwikiSearch PATTERN` | +| `vimwiki tags generate-links PAGE TAGS` | `:VimwikiIndex \| VimwikiGoto PAGE \| VimwikiGenerateTagLinks TAGS` | +| `vimwiki tags rebuild` | `:VimwikiIndex \| VimwikiRebuildTags` | +| `vimwiki tags search PATTERN` | `:VimwikiIndex \| VimwikiSearchTags PATTERN` | + +### Shell Completion + +Shell completion is available for `bash`, `fish`, and `zsh` shells. To generate +an activation script, issue: + + $ env _VIMWIKI_COMPLETE=source_$(basename $SHELL) vimwiki >/path/to/vimwiki-complete.sh + +Once generated, the activation script may be sourced directly or from the shell +startup file to provide completion: + + $ . /path/to/vimwiki-complete.sh + +### Git Integration + +For wikis managed with git, a pre-commit hook script is available that executes +non-interactive commands to rebuild tag metadata and generate links before +commit. + +The pre-commit hook relies on the following configuration options: + +| Configuration Option | Description | +|-------------------------------|------------------------------------------------| +| `vimwiki.options` | Extra options to pass to the `vimwiki` command | +| `vimwiki.linkspage` | Page which contains generated links | +| `vimwiki.taglinkspage` | Page which contains generated tag links | +| `vimwiki.generatelinks` | Generate links before commit (bool) | +| `vimwiki.generatediarylinks` | Generate diary links before commit (bool) | +| `vimwiki.generatetaglinks` | Generate tag links before commit (bool) | +| `vimwiki.rebuildtags` | Rebuild tag metadata before commit (bool) | + +For example, to configure the hook to rebuild tag metadata and generate tag +links in the `Tags` page before commit, issue: + + $ git config vimwiki.taglinkspage Tags + $ git config vimwiki.generatetaglinks true + $ git config vimwiki.rebuildtags true + +To enable the hook, copy or link [pre-commit.sh] to `.git/hooks/pre-commit` in +the wiki directory. + +## Contributing + +Pull requests are welcome! See [CONTRIBUTING.md] for more details. + +## License + +Source code in this repository is licensed under a Simplified BSD License. See +[LICENSE] for more details. + +[CONTRIBUTING.md]: https://github.com/sstallion/vimwiki-cli/blob/master/CONTRIBUTING.md +[LICENSE]: https://github.com/sstallion/vimwiki-cli/blob/master/LICENSE +[pre-commit.sh]: https://github.com/sstallion/vimwiki-cli/blob/master/scripts/pre-commit.sh +[PyPI]: https://pypi.org/project/vimwiki-cli/ +[Travis]: https://travis-ci.com/sstallion/vimwiki-cli +[Vim]: https://www.vim.org/ +[Vimwiki]: https://vimwiki.github.io/ diff --git a/docs/demo.cast b/docs/demo.cast new file mode 100644 index 0000000..601325d --- /dev/null +++ b/docs/demo.cast @@ -0,0 +1,982 @@ +{"version": 2, "width": 100, "height": 30, "timestamp": 1620184051, "env": {"SHELL": "/bin/bash", "TERM": "screen-256color"}} +[0.0, "o", "> "] +[0.60998, "o", "p"] +[0.745986, "o", "i"] +[0.852597, "o", "p"] +[0.966771, "o", " "] +[1.177992, "o", "i"] +[1.288622, "o", "n"] +[1.406953, "o", "s"] +[1.504404, "o", "t"] +[1.638533, "o", "a"] +[1.775429, "o", "l"] +[1.91585, "o", "l"] +[2.047486, "o", " "] +[2.180287, "o", "v"] +[2.256772, "o", "i"] +[2.356053, "o", "m"] +[2.480626, "o", "w"] +[2.597993, "o", "i"] +[2.777366, "o", "k"] +[2.936913, "o", "i"] +[3.123016, "o", "-"] +[3.309962, "o", "c"] +[3.391171, "o", "l"] +[3.503335, "o", "i"] +[3.730464, "o", "\r\n"] +[4.565348, "o", "Collecting vimwiki-cli\r\n"] +[5.374813, "o", " Downloading https://files.pythonhosted.org/packages/b8/91/ac220c4c811b268515d55550fdd2dde0bba7db646735b31c6ac4f27cf46d/vimwiki_cli-1.0.0-py2.py3-none-any.whl\r\n"] +[5.504618, "o", "Collecting click~=7.1 (from vimwiki-cli)\r\n"] +[5.886197, "o", "\u001b[?25l"] +[5.88637, "o", " Downloading https://files.pythonhosted.org/packages/d2/3d/fa76db83bf75c4f8d338c2fd15c8d33fdd7ad23a9b5e57eb6c5de26b430e/click-7.1.2-py2.py3-none-any.whl (82kB)\r\n"] +[5.889762, "o", "\r\u001b[K |████ | 10kB 2.9MB/s eta 0:00:01"] +[5.893345, "o", "\r\u001b[K |████████ | 20kB 2.9MB/s eta 0:00:01"] +[5.897592, "o", "\r\u001b[K |███████████▉ | 30kB 2.7MB/s eta 0:00:01"] +[5.901686, "o", "\r\u001b[K |███████████████▉ | 40kB 2.7MB/s eta 0:00:01"] +[5.905329, "o", "\r\u001b[K |███████████████████▉ | 51kB 2.7MB/s eta 0:00:01"] +[5.909076, "o", "\r\u001b[K |███████████████████████▊ | 61kB 2.7MB/s eta 0:00:01"] +[5.913712, "o", "\r\u001b[K |███████████████████████████▊ | 71kB 2.6MB/s eta 0:00:01"] +[5.917773, "o", "\r\u001b[K |███████████████████████████████▊| 81kB 2.6MB/s eta 0:00:01"] +[5.918975, "o", "\r\u001b[K |████████████████████████████████| 92kB 2.8MB/s \r\n"] +[6.03118, "o", "\u001b[?25hInstalling collected packages: click, vimwiki-cli\r\n"] +[6.073511, "o", "Successfully installed click-7.1.2 vimwiki-cli-1.0.0\r\n"] +[6.142408, "o", "\u001bk> "] +[7.176374, "o", "v"] +[7.230336, "o", "\bvi"] +[7.326891, "o", "m"] +[7.414424, "o", "w"] +[7.541765, "o", "i"] +[7.70334, "o", "k"] +[7.867321, "o", "i"] +[7.998455, "o", " "] +[8.153415, "o", "s"] +[8.271593, "o", "e"] +[8.38011, "o", "a"] +[8.476485, "o", "r"] +[8.660425, "o", "c"] +[8.744664, "o", "h"] +[8.811553, "o", " "] +[8.952174, "o", "\""] +[9.142498, "o", "R"] +[9.321398, "o", "e"] +[9.418041, "o", "l"] +[9.52775, "o", "a"] +[9.606224, "o", "t"] +[9.699445, "o", "e"] +[9.869113, "o", "d"] +[10.027922, "o", " "] +[10.187112, "o", "T"] +[10.327914, "o", "o"] +[10.464903, "o", "o"] +[10.595828, "o", "l"] +[10.684412, "o", "s"] +[11.161151, "o", "\""] +[11.532045, "o", "\u001b[?2004l\r\r\n"] +[11.744421, "o", "\u001b[?1006h\u001b[?1002h\u001b[?1049h\u001b[22;0;0t\u001b[?1004h\u001b[?2004h\u001b[>4;2m\u001b[?1h\u001b=\u001b[?2004h\u001b[1;30r\u001b[?12h\u001b[?12l\u001b[22;2t\u001b[22;1t"] +[11.745131, "o", "\u001b[27m\u001b[23m\u001b[29m\u001b[m\u001b[37m\u001b[H\u001b[2J"] +[11.754708, "o", "\u001b[?25l\u001b[30;1H\"~/.vimwiki/wiki/index.wiki\" 44L, 1532C"] +[11.802697, "o", "\u001b[2 q"] +[11.886064, "o", "\u001b[2;1H▽\u001b[6n\u001b[2;1H \u001b[1;1H\u001b[>c\u001b]10;?\u0007\u001b]11;?\u0007"] +[11.891861, "o", "\u001b[1;1H\u001b[1m\u001b[97m\u001b[44m 1 Related Tools.wiki \u001b[m\u001b[37m\u001b[34m\u001b[48;5;234m\u001b[m\u001b[37m\u001b[1;24H\u001b[38;5;254m\u001b[48;5;234m \u001b[m\u001b[37m\u001b[1;98H\u001b[38;5;234m\u001b[48;5;254m X \u001b[m\u001b[37m\u001b[2;1H\u001b[37m 1 \u001b[m\u001b[37m\u001b[48;5;234m= Related Tools = \u001b[m\u001b[37m\u001b[3;1H\u001b[90m 2 \r\n 3 \u001b[m\u001b[37mThis page contains Vim plugins and external tools that can be used with Vimwiki.\r\n\u001b[90m 4 \u001b[m\u001b[37mThese lists are incomplete so please _\u001b[3mhelp update them\u001b[23m\u001b[37m_ if you know of something\r\n\u001b[90m 5 \u001b[m\u001b[37mthat is missing!\r\n\u001b[90m 6 \r\n 7 \u001b[m\u001b[37m== Vim Plugins ==\r\n\u001b[90m 8 \r\n 9 \u001b[m\u001b[37m- \u001b[4m\u001b[94m[[https://github.com/mattn/calendar-vim|calendar-vim]]\u001b[m\u001b[37m\r\n\u001b[90m 10 \u001b[m\u001b[37m - Select a date to open a diary page.\r\n\u001b[90m 11 \u001b[m\u001b[37m- \u001b[4m\u001b[94m[[https://github.com/tbabej/taskwiki|taskwiki]]\u001b[m\u001b[37m\r\n\u001b[90m 12 \u001b[m\u001b[37m - Integration with \u001b[4m\u001b[94m[[https://taskwarrior.org/|taskwarrior]]\u001b[m\u001b[37m for task\r\n\u001b[90"] +[11.891884, "o", "m 13 \u001b[m\u001b[37m management. This only supports the default syntax.\r\n\u001b[90m 14 \u001b[m\u001b[37m- \u001b[4m\u001b[94m[[https://github.com/majutsushi/tagbar|Tagbar]]\u001b[m\u001b[37m\r\n\u001b[90m 15 \u001b[m\u001b[37m - Creates a sidebar to view generated tags. For Vimwiki this can be\r\n\u001b[90m 16 \u001b[m\u001b[37m used to display links to headers.\r\n\u001b[90m 17 \u001b[m\u001b[37m - For this to work an \u001b[4m\u001b[94m[[https://raw.githubusercontent.com/vimwiki/utils/master/vwtags.py|add\u001b[m\u001b[37m\u001b[19;1H\u001b[90m 18 \u001b[m\u001b[37m is needed which is available in the \u001b[4m\u001b[94m[[https://github.com/vimwiki/utils|utility repository]\u001b[m\u001b[37m\u001b[20;1H\u001b[90m 19 \u001b[m\u001b[37m Additional instructions are in the comments of the script.\r\n\u001b[90m 20 \u001b[m\u001b[37m - If using Markdown syntax `\u001b[34m#\u001b[m\u001b[37m` symbols used within code blocks are\r\n\u001b[90m 21 \u001b[m\u001b[37m recognized as a header i.e. in a shell script snippet. An alternative\r\n\u001b[90m 22 \u001b[m\u001b[37m version that works for Markdown can be found\r\n\u001b[90m 23 \u001b[m\u001b[37m "] +[11.892855, "o", "\u001b[4m\u001b[94m[[https://raw.githubusercontent.com/jszakmeister/markdown2ctags/master/markdown2ctags.py|h\u001b[m\u001b[37m\u001b[25;1H\u001b[90m 24 \u001b[m\u001b[37m- \u001b[4m\u001b[94m[[https://github.com/teranex/vimwiki-tasks|vimwiki-tasks]]\u001b[m\u001b[37m\r\n\u001b[90m 25 \u001b[m\u001b[37m - Another integration with \u001b[4m\u001b[94m[[https://taskwarrior.org/|taskwarrior]]\u001b[m\u001b[37m. This\r\n\u001b[90m 26 \u001b[m\u001b[37m plugin does not seem to be maintained any longer.\r\n\u001b[90m 27 \u001b[m\u001b[37m- \u001b[4m\u001b[94m[[https://github.com/matt-snider/vim-tagquery|vim-tagquery]]"] +[11.893512, "o", "\u001b[m\u001b[37m\r\n\u001b[1m\u001b[97m\u001b[44m NORMAL \u001b[m\u001b[37m\u001b[34m\u001b[48;5;238m\u001b[m\u001b[37m\u001b[29;10H\u001b[38;5;254m\u001b[48;5;238m \u001b[29;12H master \u001b[m\u001b[37m\u001b[38;5;238m\u001b[48;5;234m\u001b[m\u001b[37m\u001b[29;21H\u001b[38;5;254m\u001b[48;5;234m wiki/Related Tools.wiki \u001b[m\u001b[37m\u001b[38;5;234m\u001b[48;5;234m\u001b[m\u001b[37m\u001b[29;47H\u001b[38;5;254m\u001b[48;5;234m \u001b[m\u001b[37m\u001b[38;5;234m\u001b[48;5;234m\u001b[m\u001b[37m\u001b[29;83H\u001b[38;5;238m\u001b[48;5;234m\u001b[m\u001b[37m\u001b[29;84H\u001b[38;5;254m\u001b[48;5;238m 1% \u001b[m\u001b[37m\u001b[29;91H\u001b[38;5;234m\u001b[48;5;254m \u001b[29;93H 1:3 \u001b]2;Related Tools.wiki (~/.vimwiki/wiki) - VIM\u0007"] +[11.8942, "o", "\u001b[2;7H\u001b[?25h"] +[11.900501, "o", "\u001b[?1006l\u001b[?1002l\u001b[?2004l"] +[11.901584, "o", "\u001b[?2004h\u001b[?1006h\u001b[?1002h\u001b[?1006l\u001b[?1002l\u001b[?2004l"] +[11.903685, "o", "\u001b[?2004h\u001b[?1006h\u001b[?1002h\u001b[?2004h"] +[11.904302, "o", "\u001b]2;Related Tools.wiki (~/.vimwiki/wiki) - VIM\u0007"] +[12.799674, "o", "\u001b[?25l\u001b[m\u001b[37m\r\u001b[90m 38 \u001b[m\u001b[37m - Convert Vimwiki to various other formats such as HTML, PDF, etc.\u001b[2;75H\u001b[K\u001b[3;2H\u001b[90m39\u001b[m\u001b[37m\u001b[1C- \u001b[4m\u001b[94m[[https://github.com/patrickdavey/vimwiki_markdown|vimwiki_markdown]]\u001b[m\u001b[37m\u001b[4;2H\u001b[90m40\u001b[m\u001b[37m\u001b[1C - A Ruby gem to convert vimwiki Markdown files to HTML. Still being actively\u001b[5;2H\u001b[90m41\u001b[m\u001b[37m\u001b[1C developed.\u001b[5;19H\u001b[K\u001b[6;2H\u001b[90m42\u001b[m\u001b[37m\u001b[1C- \u001b[4m\u001b[94m[[https://github.com/WnP/vimwiki_markdown|vimwiki_markdown Python]]\u001b[m\u001b[37m\u001b[7;2H\u001b[90m43\u001b[m\u001b[37m\u001b[5C- A Python script to convert Markdown files to HTML.\u001b[8;2H\u001b[90m44\u001b[m\u001b[37m\u001b[1C - Also see \u001b[4m\u001b[94m[[https://github.com/vimwiki/vimwiki/issues/578|Issue #578]]\u001b[m\u001b[37m\u001b[9;2H\u001b[90m45\u001b[m\u001b[37m\u001b[1C- \u001b[4m\u001b[94m[[https://github.com/maqiv/vimwiki-godown#todo|vimwiki-godown]]\u001b[m\u001b[37m\u001b[10;2H\u001b[90m46\u001b[m\u001b[37m\u001b[1C - HTML converter that adds the ability to prefix relative links to other\u001b[11;2H\u001b[90m47\u001b[m\u001b[37m\u001b[5C Vimwiki pages\u001b[11;25H\u001b[K\u001b[12;2H\u001b[90m48\u001b[m\u001b[37m\u001b[1C - See \u001b[4m\u001b[94m[[https://github.com/vimwiki/vimwi"] +[12.799954, "o", "ki/issues/284|Issue #284]]\u001b[m\u001b[37m\u001b[13;2H\u001b[90m49\u001b[m\u001b[37m\u001b[1C- \u001b[4m\u001b[94m[[https://github.com/sstallion/vimwiki-skel|vimwiki-skel]]\u001b[m\u001b[37m\u001b[13;65H\u001b[K\u001b[14;2H\u001b[90m50\u001b[m\u001b[37m\u001b[5C- Uses \u001b[4m\u001b[94m[[https://dynalon.github.io/mdwiki/#!index.md|MDWiki]]\u001b[m\u001b[37m to convert\u001b[15;2H\u001b[90m51\u001b[m\u001b[37m\u001b[1C Markdown files to HTML.\u001b[15;32H\u001b[K\u001b[16;2H\u001b[90m52\u001b[m\u001b[37m\u001b[1C- \u001b[4m\u001b[94m[[https://gist.github.com/maikeldotuk/54a91c21ed9623705fdce7bab2989742|wiki2html.sh Gist]]\u001b[m\u001b[37m\u001b[17;2H\u001b[90m53\u001b[m\u001b[37m\u001b[5C- Uses Pandoc to convert Markdown files to HTML.\u001b[18;2H\u001b[90m54\u001b[m\u001b[37m\u001b[1C- \u001b[4m\u001b[94m[[https://github.com/fasheng/vimwiki2org|vimwiki2org]]\u001b[m\u001b[37m\u001b[18;61H\u001b[K\u001b[19;2H\u001b[90m55\u001b[m\u001b[37m\u001b[5C- Convert vimwiki to \u001b[4m\u001b[94m[[https://orgmode.org/|Emacs orgmode]]\u001b[m\u001b[37m. Has not been\u001b[19;82H\u001b[K\u001b[20;2H\u001b[90m56\u001b[m\u001b[37m\u001b[5Cupdated in 6 years.\u001b[20;28H\u001b[K\u001b[21;2H\u001b[90m57\u001b[m\u001b[37m\u001b[1C- \u001b[4m\u001b[94m[[https://github.com/ycpei/vwweb|vwweb]]\u001b[m\u001b[37m\u001b[21;48H\u001b[K\u001b[22;2H\u001b[90m58\u001b[m\u001b[37m"] +[12.804394, "o", "\u001b[5C- Python script to generate a website from Vimwiki files.\u001b[22;66H\u001b[K\u001b[23;2H\u001b[90m59\u001b[m\u001b[37m\u001b[1C- \u001b[4m\u001b[94m[[https://box.matto.nl/vimwikijabberbot.html|vimwiki-todo-jabberbot]]\u001b[m\u001b[37m\u001b[24;2H\u001b[90m60\u001b[m\u001b[37m\u001b[5C- Todo management using Jabberbot. The linked GitHub repository seems to\u001b[24;81H\u001b[K\u001b[25;2H\u001b[90m61\u001b[m\u001b[37m\u001b[1C have been removed.\u001b[25;27H\u001b[K\u001b[26;2H\u001b[90m62\u001b[m\u001b[37m\u001b[1C- \u001b[4m\u001b[94m[[https://github.com/zweifisch/vimwiki-tools|vimwiki-tools]]\u001b[m\u001b[37m\u001b[26;67H\u001b[K\u001b[27;2H\u001b[90m63\u001b[m\u001b[37m\u001b[5C- Python tool to generate an index and convert wiki files to Markdown\r\n\u001b[37m 64 \u001b[m\u001b[37m\u001b[48;5;234m format. This has not been updated in 6 years. "] +[12.805283, "o", "\u001b[m\u001b[37m\u001b[29;85H\u001b[38;5;254m\u001b[48;5;238m100\u001b[m\u001b[37m\u001b[7C\u001b[38;5;234m\u001b[48;5;254m64:5\u001b[28;9H\u001b[?25h"] +[13.082274, "o", "\u001b[?2004h"] +[13.095707, "o", "\u001b[?25l\u001b[6 q\u001b[2 q\u001b[6 q"] +[13.120049, "o", "\u001b[m\u001b[37m\u001b[1;23H\u001b[1m\u001b[97m\u001b[44m[+] \u001b[m\u001b[37m\u001b[34m\u001b[48;5;234m\u001b[2;28r\u001b[m\u001b[37m\u001b[28;1H\r\n\u001b[1;30r\u001b[27;1H\u001b[90m 64 \u001b[m\u001b[37m format. This has not been updated in 6 years.\u001b[27;54H\u001b[K\u001b[28;1H\u001b[37m 65 \u001b[m\u001b[37m\u001b[48;5;234m- "] +[13.122213, "o", "\u001b[m\u001b[37m\u001b[29;1H\u001b[1m\u001b[97m\u001b[42m INSERT \u001b[m\u001b[37m\u001b[32m\u001b[48;5;238m\u001b[m\u001b[37m\u001b[29;46H\u001b[38;5;254m\u001b[48;5;234m[+]\u001b[m\u001b[37m\u001b[1C\u001b[38;5;234m\u001b[48;5;234m\u001b[m\u001b[37m\u001b[29;96H\u001b[38;5;234m\u001b[48;5;254m5:3\u001b[m\u001b[37m\u001b[30;1H\u001b[K"] +[13.123258, "o", "\u001b]2;Related Tools.wiki + (~/.vimwiki/wiki) - VIM\u0007\u001b[28;7H\u001b[?25h"] +[13.254309, "o", "\u001b[?25l\u001b[2;2H\u001b[90m 39 \u001b[m\u001b[37m- \u001b[4m\u001b[94m[[https://github.com/patrickdavey/vimwiki_markdown|vimwiki_markdown]]\u001b[m\u001b[37m\u001b[3;2H\u001b[90m 40 \u001b[m\u001b[37m - A Ruby gem to convert vimwiki Markdown files to HTML. Still being actively\u001b[4;2H\u001b[90m 41 \u001b[m\u001b[37m developed.\u001b[5;2H\u001b[90m 42 \u001b[m\u001b[37m- \u001b[4m\u001b[94m[[https://github.com/WnP/vimwiki_markdown|vimwiki_markdown Python]]\u001b[m\u001b[37m\u001b[6;2H\u001b[90m 43 \u001b[m\u001b[37m - A Python script to convert Markdown files to HTML.\u001b[7;2H\u001b[90m 44 \u001b[m\u001b[37m - Also see \u001b[4m\u001b[94m[[https://github.com/vimwiki/vimwiki/issues/578|Issue #578]]\u001b[m\u001b[37m\u001b[8;2H\u001b[90m 45 \u001b[m\u001b[37m- \u001b[4m\u001b[94m[[https://github.com/maqiv/vimwiki-godown#todo|vimwiki-godown]]\u001b[m\u001b[37m\u001b[9;2H\u001b[90m 46 \u001b[m\u001b[37m - HTML converter that adds the ability to prefix relative links to other\u001b[10;2H\u001b[90m 47 \u001b[m\u001b[37m Vimwiki pages\u001b[11;2H\u001b[90m 48 \u001b[m\u001b[37m - See \u001b[4m\u001b[94m[[https://github.com/vimwiki/vimwiki/issues/284|Issue #284]]\u001b[m\u001b[37m\u001b[12;2H\u001b[90m 49 \u001b[m\u001b[37m- \u001b[4m\u001b[94m[[https://github.com/sstallion/vimwiki-skel|vimwiki-skel]]\u001b["] +[13.254557, "o", "m\u001b[37m\u001b[13;2H\u001b[90m 50 \u001b[m\u001b[37m - Uses \u001b[4m\u001b[94m[[https://dynalon.github.io/mdwiki/#!index.md|MDWiki]]\u001b[m\u001b[37m to convert\u001b[14;2H\u001b[90m 51 \u001b[m\u001b[37m Markdown files to HTML.\u001b[15;2H\u001b[90m 52 \u001b[m\u001b[37m- \u001b[4m\u001b[94m[[https://gist.github.com/maikeldotuk/54a91c21ed9623705fdce7bab2989742|wiki2html.sh Gist]]\u001b[m\u001b[37m\u001b[16;2H\u001b[90m 53 \u001b[m\u001b[37m - Uses Pandoc to convert Markdown files to HTML.\u001b[17;2H\u001b[90m 54 \u001b[m\u001b[37m- \u001b[4m\u001b[94m[[https://github.com/fasheng/vimwiki2org|vimwiki2org]]\u001b[m\u001b[37m\u001b[18;2H\u001b[90m 55 \u001b[m\u001b[37m - Convert vimwiki to \u001b[4m\u001b[94m[[https://orgmode.org/|Emacs orgmode]]\u001b[m\u001b[37m. Has not been\u001b[19;2H\u001b[90m 56 \u001b[m\u001b[37m updated in 6 years.\u001b[20;2H\u001b[90m 57 \u001b[m\u001b[37m- \u001b[4m\u001b[94m[[https://github.com/ycpei/vwweb|vwweb]]\u001b[m\u001b[37m\u001b[21;2H\u001b[90m 58 \u001b[m\u001b[37m - Python script to generate a website from Vimwiki files.\u001b[22;2H\u001b[90m 59 \u001b[m\u001b[37m- \u001b[4m\u001b[94m[[https://box.matto.nl/vimwikijabberbot.html|vimwiki-todo-jabberbot]]\u001b[m\u001b[37m\u001b[23;2H\u001b[90m 60 "] +[13.257274, "o", "\u001b[m\u001b[37m - Todo management using Jabberbot. The linked GitHub repository seems to\u001b[24;2H\u001b[90m 61 \u001b[m\u001b[37m have been removed.\u001b[25;2H\u001b[90m 62 \u001b[m\u001b[37m- \u001b[4m\u001b[94m[[https://github.com/zweifisch/vimwiki-tools|vimwiki-tools]]\u001b[m\u001b[37m\u001b[26;2H\u001b[90m 63 \u001b[m\u001b[37m - Python tool to generate an index and convert wiki files to Markdown\u001b[27;2H\u001b[90m 64 \u001b[m\u001b[37m format. This has not been updated in 6 years.\r\n\u001b[32m+ \u001b[m\u001b[37m\u001b[37m 65 \u001b[m\u001b[37m\u001b[48;5;234m-"] +[13.257845, "o", " \u001b[?25h"] +[13.718683, "o", "\u001b[?25l["] +[13.719307, "o", "\u001b[m\u001b[37m\u001b[29;98H\u001b[38;5;234m\u001b[48;5;254m4\u001b[28;10H\u001b[?25h"] +[13.833413, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234m["] +[13.83416, "o", "\u001b[m\u001b[37m\u001b[29;98H\u001b[38;5;234m\u001b[48;5;254m5\u001b[28;11H\u001b[?25h"] +[13.858612, "o", "\u001b[?25l"] +[13.85918, "o", "\u001b[?25h"] +[14.101624, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mh"] +[14.102287, "o", "\u001b[m\u001b[37m\u001b[29;98H\u001b[38;5;234m\u001b[48;5;254m6\u001b[28;12H\u001b[?25h"] +[14.239848, "o", "\u001b[?25l"] +[14.240646, "o", "\u001b[?25h"] +[14.243203, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mt"] +[14.244016, "o", "\u001b[m\u001b[37m\u001b[29;98H\u001b[38;5;234m\u001b[48;5;254m7\u001b[28;13H\u001b[?25h"] +[14.380137, "o", "\u001b[?25l"] +[14.380978, "o", "\u001b[?25h"] +[14.383572, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mt"] +[14.384283, "o", "\u001b[m\u001b[37m\u001b[29;98H\u001b[38;5;234m\u001b[48;5;254m8\u001b[28;14H\u001b[?25h"] +[14.483423, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mp"] +[14.484288, "o", "\u001b[m\u001b[37m\u001b[29;98H\u001b[38;5;234m\u001b[48;5;254m9\u001b[28;15H\u001b[?25h"] +[14.596427, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234ms"] +[14.597193, "o", "\u001b[m\u001b[37m\u001b[29;98H\u001b[38;5;234m\u001b[48;5;254m10\u001b[28;16H\u001b[?25h"] +[14.627297, "o", "\u001b[?25l"] +[14.627851, "o", "\u001b[?25h"] +[14.788174, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234m:"] +[14.788897, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m1\u001b[28;17H\u001b[?25h"] +[14.923514, "o", "\u001b[?25l"] +[14.924367, "o", "\u001b[?25h"] +[14.982904, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234m/"] +[14.983874, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m2\u001b[28;18H\u001b[?25h"] +[15.104467, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234m/"] +[15.105608, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m3\u001b[28;19H\u001b[?25h"] +[15.129053, "o", "\u001b[?25l"] +[15.129979, "o", "\u001b[?25h"] +[15.226079, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[28;11H\u001b[4m\u001b[94m\u001b[48;5;234mhttps://g"] +[15.227135, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m4\u001b[28;20H\u001b[?25h"] +[15.339252, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234mi"] +[15.340174, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m5\u001b[28;21H\u001b[?25h"] +[15.369903, "o", "\u001b[?25l"] +[15.370549, "o", "\u001b[?25h"] +[15.417769, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234mt"] +[15.418601, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m6\u001b[28;22H\u001b[?25h"] +[15.518466, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234mh"] +[15.519577, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m7\u001b[28;23H\u001b[?25h"] +[15.664213, "o", "\u001b[?25l"] +[15.665147, "o", "\u001b[?25h"] +[15.685513, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234mu"] +[15.686286, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m8\u001b[28;24H\u001b[?25h"] +[15.822897, "o", "\u001b[?25l"] +[15.823797, "o", "\u001b[?25h"] +[15.874037, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234mb"] +[15.875094, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m9\u001b[28;25H\u001b[?25h"] +[16.018851, "o", "\u001b[?25l"] +[16.01973, "o", "\u001b[?25h"] +[16.02268, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234m."] +[16.023755, "o", "\u001b[m\u001b[37m\u001b[29;98H\u001b[38;5;234m\u001b[48;5;254m20\u001b[28;26H\u001b[?25h"] +[16.168403, "o", "\u001b[?25l"] +[16.169318, "o", "\u001b[?25h"] +[16.172196, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234mc"] +[16.173121, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m1\u001b[28;27H\u001b[?25h"] +[16.248369, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234mo"] +[16.249411, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m2\u001b[28;28H\u001b[?25h"] +[16.359219, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234mm"] +[16.360317, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m3\u001b[28;29H\u001b[?25h"] +[16.392307, "o", "\u001b[?25l"] +[16.39292, "o", "\u001b[?25h"] +[16.529773, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234m/"] +[16.530903, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m4\u001b[28;30H\u001b[?25h"] +[16.647357, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234ms"] +[16.648303, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m5\u001b[28;31H\u001b[?25h"] +[16.674333, "o", "\u001b[?25l"] +[16.674899, "o", "\u001b[?25h"] +[16.775362, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234ms"] +[16.776053, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m6\u001b[28;32H\u001b[?25h"] +[16.916445, "o", "\u001b[?25l"] +[16.917188, "o", "\u001b[?25h"] +[16.952969, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234mt"] +[16.953932, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m7\u001b[28;33H\u001b[?25h"] +[17.042838, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234ma"] +[17.043856, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m8\u001b[28;34H\u001b[?25h"] +[17.113866, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234ml"] +[17.114902, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m9\u001b[28;35H\u001b[?25h"] +[17.260385, "o", "\u001b[?25l"] +[17.261291, "o", "\u001b[?25h"] +[17.264581, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234ml"] +[17.265628, "o", "\u001b[m\u001b[37m\u001b[29;98H\u001b[38;5;234m\u001b[48;5;254m30\u001b[28;36H\u001b[?25h"] +[17.379073, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234mi"] +[17.379894, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m1\u001b[28;37H\u001b[?25h"] +[17.400611, "o", "\u001b[?25l"] +[17.401187, "o", "\u001b[?25h"] +[17.514106, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234mo"] +[17.515067, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m2\u001b[28;38H\u001b[?25h"] +[17.617374, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234mn"] +[17.618372, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m3\u001b[28;39H\u001b[?25h"] +[17.76534, "o", "\u001b[?25l"] +[17.766312, "o", "\u001b[?25h"] +[17.770618, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234m/"] +[17.771801, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m4\u001b[28;40H\u001b[?25h"] +[17.90684, "o", "\u001b[?25l"] +[17.907615, "o", "\u001b[?25h"] +[17.910602, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234mv"] +[17.911383, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m5\u001b[28;41H\u001b[?25h"] +[18.032803, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234mi"] +[18.033755, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m6\u001b[28;42H\u001b[?25h"] +[18.057936, "o", "\u001b[?25l"] +[18.058859, "o", "\u001b[?25h"] +[18.109076, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234mm"] +[18.109917, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m7\u001b[28;43H\u001b[?25h"] +[18.200535, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234mw"] +[18.201645, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m8\u001b[28;44H\u001b[?25h"] +[18.342655, "o", "\u001b[?25l"] +[18.343328, "o", "\u001b[?25h"] +[18.345604, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234mi"] +[18.346392, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m9\u001b[28;45H\u001b[?25h"] +[18.481116, "o", "\u001b[?25l"] +[18.481999, "o", "\u001b[?25h"] +[18.514306, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234mk"] +[18.51506, "o", "\u001b[m\u001b[37m\u001b[29;98H\u001b[38;5;234m\u001b[48;5;254m40\u001b[28;46H\u001b[?25h"] +[18.65003, "o", "\u001b[?25l"] +[18.650641, "o", "\u001b[?25h"] +[18.667918, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234mi"] +[18.668703, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m1\u001b[28;47H\u001b[?25h"] +[18.812119, "o", "\u001b[?25l"] +[18.812931, "o", "\u001b[?25h"] +[18.869526, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234m-"] +[18.870426, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m2\u001b[28;48H\u001b[?25h"] +[19.014003, "o", "\u001b[?25l"] +[19.014768, "o", "\u001b[?25h"] +[19.101556, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234mc"] +[19.102269, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m3\u001b[28;49H\u001b[?25h"] +[19.202537, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234ml"] +[19.203514, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m4\u001b[28;50H\u001b[?25h"] +[19.274439, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234mi"] +[19.275394, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m5\u001b[28;51H\u001b[?25h"] +[19.420001, "o", "\u001b[?25l"] +[19.420785, "o", "\u001b[?25h"] +[19.553207, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234m|"] +[19.55409, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m6\u001b[28;52H\u001b[?25h"] +[19.699703, "o", "\u001b[?25l"] +[19.700532, "o", "\u001b[?25h"] +[19.766485, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234mv"] +[19.767299, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m7\u001b[28;53H\u001b[?25h"] +[19.863475, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234mi"] +[19.864491, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m8\u001b[28;54H\u001b[?25h"] +[19.962483, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234mm"] +[19.963459, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m9\u001b[28;55H\u001b[?25h"] +[20.037153, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234mw"] +[20.038167, "o", "\u001b[m\u001b[37m\u001b[29;98H\u001b[38;5;234m\u001b[48;5;254m50\u001b[28;56H\u001b[?25h"] +[20.182541, "o", "\u001b[?25l"] +[20.183353, "o", "\u001b[?25h"] +[20.186509, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234mi"] +[20.187328, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m1\u001b[28;57H\u001b[?25h"] +[20.326716, "o", "\u001b[?25l"] +[20.327351, "o", "\u001b[?25h"] +[20.332072, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234mk"] +[20.332729, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m2\u001b[28;58H\u001b[?25h"] +[20.465183, "o", "\u001b[?25l"] +[20.465752, "o", "\u001b[?25h"] +[20.471257, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234mi"] +[20.47191, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m3\u001b[28;59H\u001b[?25h"] +[20.601798, "o", "\u001b[?25l"] +[20.602446, "o", "\u001b[?25h"] +[20.65048, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234m-"] +[20.651204, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m4\u001b[28;60H\u001b[?25h"] +[20.786104, "o", "\u001b[?25l"] +[20.786777, "o", "\u001b[?25h"] +[20.825157, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234mc"] +[20.826096, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m5\u001b[28;61H\u001b[?25h"] +[20.926158, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234ml"] +[20.927096, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m6\u001b[28;62H\u001b[?25h"] +[21.036513, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234mi"] +[21.037601, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m7\u001b[28;63H\u001b[?25h"] +[21.069801, "o", "\u001b[?25l"] +[21.070747, "o", "\u001b[?25h"] +[21.451898, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234m]"] +[21.452853, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m8\u001b[28;10H\u001b[?25h"] +[21.584144, "o", "\u001b[?25l\u001b[m\u001b[37m\b\u001b[4m\u001b[94m\u001b[48;5;234m[[\u001b[53C]"] +[21.585104, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m9\u001b[28;9H\u001b[?25h"] +[21.821301, "o", "\u001b[2 q\u001b[?25l\u001b[m\u001b[37m\u001b[4m\u001b[38;5;202m\u001b[48;5;234m[\u001b[54C]"] +[21.822184, "o", "\u001b[m\u001b[37m\r\n\u001b[1m\u001b[97m\u001b[44m NORMAL \u001b[m\u001b[37m\u001b[34m\u001b[48;5;238m\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m8\u001b[28;64H\u001b[?25h\u001b[?2004h"] +[21.845278, "o", "\u001b[?25l\u001b[6 q\u001b[2 q\u001b[6 q\u001b[2;28r\u001b[m\u001b[37m\u001b[28;1H\r\n\u001b[1;30r\u001b[27;3H\u001b[90m 65 \u001b[m\u001b[37m- \u001b[4m\u001b[38;5;202m[\u001b[m\u001b[37m\u001b[4m\u001b[94m[https://github.com/sstallion/vimwiki-cli|vimwiki-cli]\u001b[m\u001b[37m\u001b[4m\u001b[38;5;202m]\u001b[m\u001b[37m\u001b[27;65H\u001b[K\u001b[28;1H\u001b[90m \u001b[m\u001b[37m\u001b[37m 66 \u001b[m\u001b[37m\u001b[48;5;234m- \u001b[m\u001b[37m\u001b[27;9H\u001b[4m\u001b[94m[\u001b[54C]"] +[21.857314, "o", "\u001b[m\u001b[37m\r\n\r\n\u001b[1m\u001b[97m\u001b[42m INSERT \u001b[m\u001b[37m\u001b[32m\u001b[48;5;238m\u001b[m\u001b[37m\u001b[29;96H\u001b[38;5;234m\u001b[48;5;254m6:3 \u001b[28;9H\u001b[?25h"] +[21.985458, "o", "\u001b[?25l\u001b[m\u001b[37m\r\u001b[32m+ "] +[21.985892, "o", "\u001b[6C\u001b[?25h"] +[22.158851, "o", "\u001b[?25l"] +[22.159435, "o", "\u001b[m\u001b[37m\u001b[29;98H\u001b[38;5;234m\u001b[48;5;254m2\u001b[28;8H\u001b[?25h"] +[22.282468, "o", "\u001b[?25l"] +[22.282935, "o", "\u001b[?25h"] +[22.291777, "o", "\u001b[?25l\u001b[m\u001b[37m\b\u001b[48;5;234m "] +[22.292338, "o", "\u001b[m\u001b[37m\u001b[29;98H\u001b[38;5;234m\u001b[48;5;254m1\u001b[28;7H\u001b[?25h"] +[22.417065, "o", "\u001b[?25l"] +[22.417635, "o", "\u001b[?25h"] +[22.647053, "o", "\u001b[?25l"] +[22.647749, "o", "\u001b[29;98H2\u001b[28;8H\u001b[?25h"] +[22.773459, "o", "\u001b[?25l"] +[22.77395, "o", "\u001b[?25h"] +[22.799658, "o", "\u001b[?25l"] +[22.800116, "o", "\u001b[29;98H3\u001b[28;9H\u001b[?25h"] +[22.928828, "o", "\u001b[?25l"] +[22.929281, "o", "\u001b[?25h"] +[22.973683, "o", "\u001b[?25l"] +[22.974481, "o", "\u001b[29;98H4\u001b[28;10H\u001b[?25h"] +[23.108678, "o", "\u001b[?25l"] +[23.109193, "o", "\u001b[?25h"] +[23.116138, "o", "\u001b[?25l"] +[23.116776, "o", "\u001b[29;98H5\u001b[28;11H\u001b[?25h"] +[23.251007, "o", "\u001b[?25l"] +[23.251647, "o", "\u001b[?25h"] +[23.26059, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234m-"] +[23.261317, "o", "\u001b[m\u001b[37m\u001b[29;98H\u001b[38;5;234m\u001b[48;5;254m6\u001b[28;12H\u001b[?25h"] +[23.392222, "o", "\u001b[?25l"] +[23.392803, "o", "\u001b[?25h"] +[23.430321, "o", "\u001b[?25l"] +[23.430991, "o", "\u001b[29;98H7\u001b[28;13H\u001b[?25h"] +[23.564002, "o", "\u001b[?25l"] +[23.56455, "o", "\u001b[?25h"] +[23.721707, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mP"] +[23.72243, "o", "\u001b[m\u001b[37m\u001b[29;98H\u001b[38;5;234m\u001b[48;5;254m8\u001b[28;14H\u001b[?25h"] +[23.858413, "o", "\u001b[?25l"] +[23.859288, "o", "\u001b[?25h"] +[23.91229, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234my"] +[23.913147, "o", "\u001b[m\u001b[37m\u001b[29;98H\u001b[38;5;234m\u001b[48;5;254m9\u001b[28;15H\u001b[?25h"] +[24.052934, "o", "\u001b[?25l"] +[24.053566, "o", "\u001b[?25h"] +[24.055655, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mt"] +[24.05628, "o", "\u001b[m\u001b[37m\u001b[29;98H\u001b[38;5;234m\u001b[48;5;254m10\u001b[28;16H\u001b[?25h"] +[24.142255, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mh"] +[24.143143, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m1\u001b[28;17H\u001b[?25h"] +[24.287582, "o", "\u001b[?25l"] +[24.288341, "o", "\u001b[?25h"] +[24.300208, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mo"] +[24.301059, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m2\u001b[28;18H\u001b[?25h"] +[24.368743, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mn"] +[24.369768, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m3\u001b[28;19H\u001b[?25h"] +[24.442869, "o", "\u001b[?25l"] +[24.443772, "o", "\u001b[29;99H4\u001b[28;20H\u001b[?25h"] +[24.586744, "o", "\u001b[?25l"] +[24.587483, "o", "\u001b[?25h"] +[24.634367, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mC"] +[24.635223, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m5\u001b[28;21H\u001b[?25h"] +[24.706561, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mL"] +[24.707477, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m6\u001b[28;22H\u001b[?25h"] +[24.785985, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mI"] +[24.786876, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m7\u001b[28;23H\u001b[?25h"] +[24.931715, "o", "\u001b[?25l"] +[24.93248, "o", "\u001b[?25h"] +[25.004825, "o", "\u001b[?25l"] +[25.005795, "o", "\u001b[29;99H8\u001b[28;24H\u001b[?25h"] +[25.152195, "o", "\u001b[?25l"] +[25.152968, "o", "\u001b[?25h"] +[25.155793, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mt"] +[25.156612, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m9\u001b[28;25H\u001b[?25h"] +[25.301767, "o", "\u001b[?25l"] +[25.302571, "o", "\u001b[?25h"] +[25.683864, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mh"] +[25.684754, "o", "\u001b[m\u001b[37m\u001b[29;98H\u001b[38;5;234m\u001b[48;5;254m20\u001b[28;26H\u001b[?25h"] +[25.826425, "o", "\u001b[?25l"] +[25.827484, "o", "\u001b[?25h"] +[25.879085, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234ma"] +[25.880277, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m1\u001b[28;27H\u001b[?25h"] +[25.997462, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mt"] +[25.998498, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m2\u001b[28;28H\u001b[?25h"] +[26.024159, "o", "\u001b[?25l"] +[26.02497, "o", "\u001b[?25h"] +[26.094299, "o", "\u001b[?25l"] +[26.095371, "o", "\u001b[29;99H3\u001b[28;29H\u001b[?25h"] +[26.240471, "o", "\u001b[?25l"] +[26.241175, "o", "\u001b[?25h"] +[26.246653, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mp"] +[26.247571, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m4\u001b[28;30H\u001b[?25h"] +[26.319458, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mr"] +[26.320309, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m5\u001b[28;31H\u001b[?25h"] +[26.413461, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mo"] +[26.414434, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m6\u001b[28;32H\u001b[?25h"] +[26.556753, "o", "\u001b[?25l"] +[26.557557, "o", "\u001b[?25h"] +[26.5599, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mv"] +[26.560537, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m7\u001b[28;33H\u001b[?25h"] +[26.612955, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mi"] +[26.613839, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m8\u001b[28;34H\u001b[?25h"] +[26.759828, "o", "\u001b[?25l"] +[26.760584, "o", "\u001b[?25h"] +[26.766508, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234md"] +[26.767376, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m9\u001b[28;35H\u001b[?25h"] +[26.912092, "o", "\u001b[?25l"] +[26.912923, "o", "\u001b[?25h"] +[26.94998, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234me"] +[26.951015, "o", "\u001b[m\u001b[37m\u001b[29;98H\u001b[38;5;234m\u001b[48;5;254m30\u001b[28;36H\u001b[?25h"] +[27.096894, "o", "\u001b[?25l"] +[27.097748, "o", "\u001b[?25h"] +[27.170984, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234ms"] +[27.171915, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m1\u001b[28;37H\u001b[?25h"] +[27.318116, "o", "\u001b[?25l"] +[27.319019, "o", "\u001b[?25h"] +[27.321883, "o", "\u001b[?25l"] +[27.322834, "o", "\u001b[29;99H2\u001b[28;38H\u001b[?25h"] +[27.457875, "o", "\u001b[?25l"] +[27.458718, "o", "\u001b[?25h"] +[27.481838, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234ma"] +[27.482593, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m3\u001b[28;39H\u001b[?25h"] +[27.620538, "o", "\u001b[?25l"] +[27.621348, "o", "\u001b[?25h"] +[27.624368, "o", "\u001b[?25l"] +[27.625126, "o", "\u001b[29;99H4\u001b[28;40H\u001b[?25h"] +[27.706153, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mf"] +[27.707131, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m5\u001b[28;41H\u001b[?25h"] +[27.852986, "o", "\u001b[?25l"] +[27.85375, "o", "\u001b[?25h"] +[27.891957, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mr"] +[27.892895, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m6\u001b[28;42H\u001b[?25h"] +[27.972714, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mo"] +[27.973609, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m7\u001b[28;43H\u001b[?25h"] +[28.043884, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mn"] +[28.04486, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m8\u001b[28;44H\u001b[?25h"] +[28.118409, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mt"] +[28.119425, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m9\u001b[28;45H\u001b[?25h"] +[28.262478, "o", "\u001b[?25l"] +[28.263441, "o", "\u001b[?25h"] +[28.266435, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234m-"] +[28.267342, "o", "\u001b[m\u001b[37m\u001b[29;98H\u001b[38;5;234m\u001b[48;5;254m40\u001b[28;46H\u001b[?25h"] +[28.3326, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234me"] +[28.333419, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m1\u001b[28;47H\u001b[?25h"] +[28.468519, "o", "\u001b[?25l"] +[28.469294, "o", "\u001b[?25h"] +[28.471794, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mn"] +[28.472674, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m2\u001b[28;48H\u001b[?25h"] +[28.541486, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234md"] +[28.5423, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m3\u001b[28;49H\u001b[?25h"] +[28.681787, "o", "\u001b[?25l"] +[28.682849, "o", "\u001b[?25h"] +[28.685888, "o", "\u001b[?25l"] +[28.686908, "o", "\u001b[29;99H4\u001b[28;50H\u001b[?25h"] +[28.77196, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mf"] +[28.773391, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m5\u001b[28;51H\u001b[?25h"] +[28.88365, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mo"] +[28.884614, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m6\u001b[28;52H\u001b[?25h"] +[28.918772, "o", "\u001b[?25l"] +[28.919746, "o", "\u001b[?25h"] +[28.95416, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mr"] +[28.955052, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m7\u001b[28;53H\u001b[?25h"] +[29.025358, "o", "\u001b[?25l"] +[29.026269, "o", "\u001b[29;99H8\u001b[28;54H\u001b[?25h"] +[29.088469, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mi"] +[29.089403, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m9\u001b[28;55H\u001b[?25h"] +[29.154716, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mn"] +[29.155791, "o", "\u001b[m\u001b[37m\u001b[29;98H\u001b[38;5;234m\u001b[48;5;254m50\u001b[28;56H\u001b[?25h"] +[29.208804, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mt"] +[29.209864, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m1\u001b[28;57H\u001b[?25h"] +[29.328828, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234me"] +[29.329821, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m2\u001b[28;58H\u001b[?25h"] +[29.35604, "o", "\u001b[?25l"] +[29.356944, "o", "\u001b[?25h"] +[29.571991, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mr"] +[29.573086, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m3\u001b[28;59H\u001b[?25h"] +[29.718752, "o", "\u001b[?25l"] +[29.719466, "o", "\u001b[?25h"] +[29.880976, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234ma"] +[29.881945, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m4\u001b[28;60H\u001b[?25h"] +[30.027332, "o", "\u001b[?25l"] +[30.028128, "o", "\u001b[?25h"] +[30.047658, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mc"] +[30.048337, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m5\u001b[28;61H\u001b[?25h"] +[30.184138, "o", "\u001b[?25l"] +[30.184944, "o", "\u001b[?25h"] +[30.258261, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mt"] +[30.258934, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m6\u001b[28;62H\u001b[?25h"] +[30.359586, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mi"] +[30.360575, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m7\u001b[28;63H\u001b[?25h"] +[30.454171, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mv"] +[30.455156, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m8\u001b[28;64H\u001b[?25h"] +[30.538143, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234me"] +[30.539117, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m9\u001b[28;65H\u001b[?25h"] +[30.621041, "o", "\u001b[?25l"] +[30.622113, "o", "\u001b[29;98H60\u001b[28;66H\u001b[?25h"] +[30.732961, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234me"] +[30.73399, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m1\u001b[28;67H\u001b[?25h"] +[30.766997, "o", "\u001b[?25l"] +[30.767974, "o", "\u001b[?25h"] +[30.890765, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234md"] +[30.891687, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m2\u001b[28;68H\u001b[?25h"] +[30.972655, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mi"] +[30.973352, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m3\u001b[28;69H\u001b[?25h"] +[31.120276, "o", "\u001b[?25l"] +[31.121108, "o", "\u001b[?25h"] +[31.124288, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mt"] +[31.125357, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m4\u001b[28;70H\u001b[?25h"] +[31.190374, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mo"] +[31.191513, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m5\u001b[28;71H\u001b[?25h"] +[31.286738, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mr"] +[31.287792, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m6\u001b[28;72H\u001b[?25h"] +[31.402397, "o", "\u001b[?25l"] +[31.403286, "o", "\u001b[29;99H7\u001b[28;73H\u001b[?25h"] +[31.433071, "o", "\u001b[?25l"] +[31.433862, "o", "\u001b[?25h"] +[31.49811, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mc"] +[31.498947, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m8\u001b[28;74H\u001b[?25h"] +[31.566619, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mo"] +[31.56732, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m9\u001b[28;75H\u001b[?25h"] +[31.657161, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mm"] +[31.65798, "o", "\u001b[m\u001b[37m\u001b[29;98H\u001b[38;5;234m\u001b[48;5;254m70\u001b[28;76H\u001b[?25h"] +[31.804636, "o", "\u001b[?25l"] +[31.805629, "o", "\u001b[?25h"] +[31.808729, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mm"] +[31.809611, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m1\u001b[28;77H\u001b[?25h"] +[31.919386, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234ma"] +[31.920401, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m2\u001b[28;78H\u001b[?25h"] +[31.954934, "o", "\u001b[?25l"] +[31.955721, "o", "\u001b[?25h"] +[31.985772, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mn"] +[31.98672, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m3\u001b[28;79H\u001b[?25h"] +[32.045565, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234md"] +[32.046519, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m4\u001b[28;80H\u001b[?25h"] +[32.156088, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234ms"] +[32.157134, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m5\u001b[28;81H\u001b[?25h"] +[32.192162, "o", "\u001b[?25l"] +[32.193012, "o", "\u001b[?25h"] +[32.256181, "o", "\u001b[?25l"] +[32.257195, "o", "\u001b[29;99H6\u001b[28;82H\u001b[?25h"] +[32.363555, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234ma"] +[32.364208, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m7\u001b[28;83H\u001b[?25h"] +[32.387466, "o", "\u001b[?25l"] +[32.388088, "o", "\u001b[?25h"] +[32.453689, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mn"] +[32.454388, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m8\u001b[28;84H\u001b[?25h"] +[32.527992, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234md"] +[32.528977, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m9\u001b[28;85H\u001b[?25h"] +[32.627327, "o", "\u001b[?25l"] +[32.62818, "o", "\u001b[29;98H80\u001b[28;86H\u001b[?25h"] +[32.756322, "o", "\u001b[?25l\u001b[2;28r\u001b[m\u001b[37m\u001b[28;1H\r\n\u001b[1;30r\u001b[27;3H\u001b[90m 66 \u001b[m\u001b[37m - Python CLI that provides a front-end for interactive editor commands and\u001b[27;85H\u001b[K\u001b[28;1H\u001b[90m \u001b[m\u001b[37m\u001b[37m 67 \u001b[m\u001b[37m\u001b[48;5;234m c "] +[32.757193, "o", "\u001b[m\u001b[37m\u001b[29;96H\u001b[38;5;234m\u001b[48;5;254m7:8 \u001b[28;14H\u001b[?25h"] +[32.774865, "o", "\u001b[?25l"] +[32.775418, "o", "\u001b[?25h"] +[32.800643, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234ma"] +[32.801274, "o", "\u001b[m\u001b[37m\u001b[29;98H\u001b[38;5;234m\u001b[48;5;254m9\u001b[28;15H\u001b[?25h"] +[32.862862, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mn"] +[32.863858, "o", "\u001b[m\u001b[37m\u001b[29;98H\u001b[38;5;234m\u001b[48;5;254m10\u001b[28;16H\u001b[?25h"] +[32.974627, "o", "\u001b[?25l"] +[32.975511, "o", "\u001b[29;99H1\u001b[28;17H\u001b[?25h"] +[33.008573, "o", "\u001b[?25l\u001b[m\u001b[37m\r\u001b[32m+ "] +[33.009078, "o", "\u001b[14C\u001b[?25h"] +[33.073381, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mb"] +[33.074136, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m2\u001b[28;18H\u001b[?25h"] +[33.137584, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234me"] +[33.138272, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m3\u001b[28;19H\u001b[?25h"] +[33.232708, "o", "\u001b[?25l"] +[33.233365, "o", "\u001b[29;99H4\u001b[28;20H\u001b[?25h"] +[33.344077, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mu"] +[33.344914, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m5\u001b[28;21H\u001b[?25h"] +[33.37419, "o", "\u001b[?25l"] +[33.374843, "o", "\u001b[?25h"] +[33.423348, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234ms"] +[33.424081, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m6\u001b[28;22H\u001b[?25h"] +[33.515425, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234me"] +[33.51633, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m7\u001b[28;23H\u001b[?25h"] +[33.655082, "o", "\u001b[?25l"] +[33.655912, "o", "\u001b[?25h"] +[33.658731, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234md"] +[33.659589, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m8\u001b[28;24H\u001b[?25h"] +[33.743565, "o", "\u001b[?25l"] +[33.744307, "o", "\u001b[29;99H9\u001b[28;25H\u001b[?25h"] +[33.88971, "o", "\u001b[?25l"] +[33.89061, "o", "\u001b[?25h"] +[33.893384, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mt"] +[33.89488, "o", "\u001b[m\u001b[37m\u001b[29;98H\u001b[38;5;234m\u001b[48;5;254m20\u001b[28;26H\u001b[?25h"] +[33.934558, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mo"] +[33.935524, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m1\u001b[28;27H\u001b[?25h"] +[34.035885, "o", "\u001b[?25l"] +[34.036824, "o", "\u001b[29;99H2\u001b[28;28H\u001b[?25h"] +[34.099026, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234ma"] +[34.100225, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m3\u001b[28;29H\u001b[?25h"] +[34.21684, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mu"] +[34.217698, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m4\u001b[28;30H\u001b[?25h"] +[34.241434, "o", "\u001b[?25l"] +[34.242069, "o", "\u001b[?25h"] +[34.336887, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mt"] +[34.337857, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m5\u001b[28;31H\u001b[?25h"] +[34.414496, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mo"] +[34.415494, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m6\u001b[28;32H\u001b[?25h"] +[34.559326, "o", "\u001b[?25l"] +[34.560215, "o", "\u001b[?25h"] +[34.61467, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mm"] +[34.61583, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m7\u001b[28;33H\u001b[?25h"] +[34.691256, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234ma"] +[34.692503, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m8\u001b[28;34H\u001b[?25h"] +[34.780385, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mt"] +[34.7818, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m9\u001b[28;35H\u001b[?25h"] +[34.864086, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234me"] +[34.865391, "o", "\u001b[m\u001b[37m\u001b[29;98H\u001b[38;5;234m\u001b[48;5;254m30\u001b[28;36H\u001b[?25h"] +[34.953578, "o", "\u001b[?25l"] +[34.954868, "o", "\u001b[29;99H1\u001b[28;37H\u001b[?25h"] +[35.100679, "o", "\u001b[?25l"] +[35.101826, "o", "\u001b[?25h"] +[35.14399, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mr"] +[35.145364, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m2\u001b[28;38H\u001b[?25h"] +[35.261474, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234me"] +[35.26273, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m3\u001b[28;39H\u001b[?25h"] +[35.29037, "o", "\u001b[?25l"] +[35.291301, "o", "\u001b[?25h"] +[35.457246, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mp"] +[35.458263, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m4\u001b[28;40H\u001b[?25h"] +[35.595301, "o", "\u001b[?25l"] +[35.596011, "o", "\u001b[?25h"] +[35.61606, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234me"] +[35.616914, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m5\u001b[28;41H\u001b[?25h"] +[35.762954, "o", "\u001b[?25l"] +[35.764032, "o", "\u001b[?25h"] +[35.920713, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mt"] +[35.922072, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m6\u001b[28;42H\u001b[?25h"] +[36.066113, "o", "\u001b[?25l"] +[36.06695, "o", "\u001b[?25h"] +[36.521298, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mi"] +[36.522091, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m7\u001b[28;43H\u001b[?25h"] +[36.657939, "o", "\u001b[?25l"] +[36.658751, "o", "\u001b[?25h"] +[36.791548, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mt"] +[36.792446, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m8\u001b[28;44H\u001b[?25h"] +[36.933075, "o", "\u001b[?25l"] +[36.933794, "o", "\u001b[?25h"] +[37.041085, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mi"] +[37.041769, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m9\u001b[28;45H\u001b[?25h"] +[37.174299, "o", "\u001b[?25l"] +[37.175056, "o", "\u001b[?25h"] +[37.207559, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mv"] +[37.208148, "o", "\u001b[m\u001b[37m\u001b[29;98H\u001b[38;5;234m\u001b[48;5;254m40\u001b[28;46H\u001b[?25h"] +[37.354923, "o", "\u001b[?25l"] +[37.355749, "o", "\u001b[?25h"] +[37.413162, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234me"] +[37.414224, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m1\u001b[28;47H\u001b[?25h"] +[37.55719, "o", "\u001b[?25l"] +[37.558318, "o", "\u001b[?25h"] +[37.577037, "o", "\u001b[?25l"] +[37.578396, "o", "\u001b[29;99H2\u001b[28;48H\u001b[?25h"] +[37.72376, "o", "\u001b[?25l"] +[37.724844, "o", "\u001b[?25h"] +[37.814478, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mt"] +[37.815767, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m3\u001b[28;49H\u001b[?25h"] +[37.963287, "o", "\u001b[?25l"] +[37.964292, "o", "\u001b[?25h"] +[37.968918, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234ma"] +[37.970018, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m4\u001b[28;50H\u001b[?25h"] +[38.046656, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234ms"] +[38.047654, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m5\u001b[28;51H\u001b[?25h"] +[38.111703, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mk"] +[38.112678, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m6\u001b[28;52H\u001b[?25h"] +[38.221292, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234ms"] +[38.222342, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m7\u001b[28;53H\u001b[?25h"] +[38.251582, "o", "\u001b[?25l"] +[38.252353, "o", "\u001b[?25h"] +[38.342932, "o", "\u001b[?25l"] +[38.343911, "o", "\u001b[29;99H8\u001b[28;54H\u001b[?25h"] +[38.458516, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234ms"] +[38.459832, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m9\u001b[28;55H\u001b[?25h"] +[38.48929, "o", "\u001b[?25l"] +[38.490336, "o", "\u001b[?25h"] +[38.594979, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mu"] +[38.595964, "o", "\u001b[m\u001b[37m\u001b[29;98H\u001b[38;5;234m\u001b[48;5;254m50\u001b[28;56H\u001b[?25h"] +[38.732763, "o", "\u001b[?25l"] +[38.733534, "o", "\u001b[?25h"] +[38.739392, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mc"] +[38.740299, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m1\u001b[28;57H\u001b[?25h"] +[38.826583, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mh"] +[38.827539, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m2\u001b[28;58H\u001b[?25h"] +[38.91907, "o", "\u001b[?25l"] +[38.920218, "o", "\u001b[29;99H3\u001b[28;59H\u001b[?25h"] +[38.957774, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234ma"] +[38.958956, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m4\u001b[28;60H\u001b[?25h"] +[39.044488, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234ms"] +[39.045654, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m5\u001b[28;61H\u001b[?25h"] +[39.134906, "o", "\u001b[?25l"] +[39.135988, "o", "\u001b[29;99H6\u001b[28;62H\u001b[?25h"] +[39.283332, "o", "\u001b[?25l"] +[39.284256, "o", "\u001b[?25h"] +[39.287577, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mr"] +[39.288657, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m7\u001b[28;63H\u001b[?25h"] +[39.36209, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234me"] +[39.363122, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m8\u001b[28;64H\u001b[?25h"] +[39.415685, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mb"] +[39.416556, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m9\u001b[28;65H\u001b[?25h"] +[39.560642, "o", "\u001b[?25l"] +[39.561596, "o", "\u001b[?25h"] +[39.599806, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mu"] +[39.600636, "o", "\u001b[m\u001b[37m\u001b[29;98H\u001b[38;5;234m\u001b[48;5;254m60\u001b[28;66H\u001b[?25h"] +[39.680406, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mi"] +[39.681541, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m1\u001b[28;67H\u001b[?25h"] +[39.773368, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234ml"] +[39.774596, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m2\u001b[28;68H\u001b[?25h"] +[39.919325, "o", "\u001b[?25l"] +[39.920257, "o", "\u001b[?25h"] +[39.923432, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234md"] +[39.924452, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m3\u001b[28;69H\u001b[?25h"] +[39.974142, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mi"] +[39.975046, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m4\u001b[28;70H\u001b[?25h"] +[40.063146, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mn"] +[40.063975, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m5\u001b[28;71H\u001b[?25h"] +[40.154803, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mg"] +[40.155698, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m6\u001b[28;72H\u001b[?25h"] +[40.246864, "o", "\u001b[?25l"] +[40.247767, "o", "\u001b[29;99H7\u001b[28;73H\u001b[?25h"] +[40.385426, "o", "\u001b[?25l"] +[40.386525, "o", "\u001b[?25h"] +[40.39003, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mt"] +[40.39115, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m8\u001b[28;74H\u001b[?25h"] +[40.48997, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234ma"] +[40.491175, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m9\u001b[28;75H\u001b[?25h"] +[40.614718, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mg"] +[40.615719, "o", "\u001b[m\u001b[37m\u001b[29;98H\u001b[38;5;234m\u001b[48;5;254m70\u001b[28;76H\u001b[?25h"] +[40.637613, "o", "\u001b[?25l"] +[40.638399, "o", "\u001b[?25h"] +[40.68564, "o", "\u001b[?25l"] +[40.68645, "o", "\u001b[29;99H1\u001b[28;77H\u001b[?25h"] +[40.780558, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mm"] +[40.781618, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m2\u001b[28;78H\u001b[?25h"] +[40.854055, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234me"] +[40.855156, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m3\u001b[28;79H\u001b[?25h"] +[41.001458, "o", "\u001b[?25l"] +[41.002301, "o", "\u001b[?25h"] +[41.019126, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mt"] +[41.019976, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m4\u001b[28;80H\u001b[?25h"] +[41.138065, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234ma"] +[41.139192, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m5\u001b[28;81H\u001b[?25h"] +[41.167684, "o", "\u001b[?25l"] +[41.168504, "o", "\u001b[?25h"] +[41.335272, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234md"] +[41.336028, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m6\u001b[28;82H\u001b[?25h"] +[41.47234, "o", "\u001b[?25l"] +[41.473162, "o", "\u001b[?25h"] +[41.508526, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234ma"] +[41.509429, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m7\u001b[28;83H\u001b[?25h"] +[41.584993, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mt"] +[41.586026, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m8\u001b[28;84H\u001b[?25h"] +[41.702766, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234ma"] +[41.703706, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m9\u001b[28;85H\u001b[?25h"] +[41.730804, "o", "\u001b[?25l"] +[41.731427, "o", "\u001b[?25h"] +[41.837398, "o", "\u001b[?25l"] +[41.838435, "o", "\u001b[29;98H80\u001b[28;86H\u001b[?25h"] +[41.974874, "o", "\u001b[?25l\u001b[2;28r\u001b[m\u001b[37m\u001b[28;1H\r\n\u001b[1;30r\u001b[27;3H\u001b[90m 67 \u001b[m\u001b[37m can be used to automate repetitive tasks such as rebuilding tag metadata\u001b[27;85H\u001b[K\u001b[28;1H\u001b[90m \u001b[m\u001b[37m\u001b[37m 68 \u001b[m\u001b[37m\u001b[48;5;234m a "] +[41.975811, "o", "\u001b[m\u001b[37m\u001b[29;96H\u001b[38;5;234m\u001b[48;5;254m8:8 \u001b[28;14H\u001b[?25h"] +[41.993712, "o", "\u001b[?25l"] +[41.994336, "o", "\u001b[?25h"] +[42.047005, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mn"] +[42.047645, "o", "\u001b[m\u001b[37m\u001b[29;98H\u001b[38;5;234m\u001b[48;5;254m9\u001b[28;15H\u001b[?25h"] +[42.137218, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234md"] +[42.137991, "o", "\u001b[m\u001b[37m\u001b[29;98H\u001b[38;5;234m\u001b[48;5;254m10\u001b[28;16H\u001b[?25h"] +[42.213117, "o", "\u001b[?25l"] +[42.214164, "o", "\u001b[29;99H1\u001b[28;17H\u001b[?25h"] +[42.340034, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mg"] +[42.341355, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m2\u001b[28;18H\u001b[?25h"] +[42.365934, "o", "\u001b[?25l\u001b[m\u001b[37m\r\u001b[32m+ "] +[42.366796, "o", "\u001b[15C\u001b[?25h"] +[42.404093, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234me"] +[42.405069, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m3\u001b[28;19H\u001b[?25h"] +[42.551789, "o", "\u001b[?25l"] +[42.552888, "o", "\u001b[?25h"] +[42.559314, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mn"] +[42.560541, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m4\u001b[28;20H\u001b[?25h"] +[42.705835, "o", "\u001b[?25l"] +[42.706809, "o", "\u001b[?25h"] +[42.72486, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234me"] +[42.726168, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m5\u001b[28;21H\u001b[?25h"] +[42.869905, "o", "\u001b[?25l"] +[42.870718, "o", "\u001b[?25h"] +[42.873565, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mr"] +[42.874482, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m6\u001b[28;22H\u001b[?25h"] +[43.014694, "o", "\u001b[?25l"] +[43.015509, "o", "\u001b[?25h"] +[43.022193, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234ma"] +[43.023122, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m7\u001b[28;23H\u001b[?25h"] +[43.125865, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mt"] +[43.126786, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m8\u001b[28;24H\u001b[?25h"] +[43.234107, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mi"] +[43.234854, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m9\u001b[28;25H\u001b[?25h"] +[43.263644, "o", "\u001b[?25l"] +[43.264453, "o", "\u001b[?25h"] +[43.295809, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mn"] +[43.296805, "o", "\u001b[m\u001b[37m\u001b[29;98H\u001b[38;5;234m\u001b[48;5;254m20\u001b[28;26H\u001b[?25h"] +[43.372896, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mg"] +[43.374056, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m1\u001b[28;27H\u001b[?25h"] +[43.45383, "o", "\u001b[?25l"] +[43.454888, "o", "\u001b[29;99H2\u001b[28;28H\u001b[?25h"] +[43.552608, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234ml"] +[43.553643, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m3\u001b[28;29H\u001b[?25h"] +[43.649564, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mi"] +[43.650637, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m4\u001b[28;30H\u001b[?25h"] +[43.7467, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mn"] +[43.747761, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m5\u001b[28;31H\u001b[?25h"] +[43.86212, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mk"] +[43.863321, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m6\u001b[28;32H\u001b[?25h"] +[43.887125, "o", "\u001b[?25l"] +[43.887964, "o", "\u001b[?25h"] +[43.950905, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234ms"] +[43.951914, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m7\u001b[28;33H\u001b[?25h"] +[44.073351, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234m,"] +[44.074454, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m8\u001b[28;34H\u001b[?25h"] +[44.099344, "o", "\u001b[?25l"] +[44.10022, "o", "\u001b[?25h"] +[44.184955, "o", "\u001b[?25l"] +[44.185961, "o", "\u001b[29;99H9\u001b[28;35H\u001b[?25h"] +[44.244327, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234ma"] +[44.245529, "o", "\u001b[m\u001b[37m\u001b[29;98H\u001b[38;5;234m\u001b[48;5;254m30\u001b[28;36H\u001b[?25h"] +[44.363092, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234ml"] +[44.364029, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m1\u001b[28;37H\u001b[?25h"] +[44.389896, "o", "\u001b[?25l"] +[44.390556, "o", "\u001b[?25h"] +[44.478631, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234ml"] +[44.479409, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m2\u001b[28;38H\u001b[?25h"] +[44.578042, "o", "\u001b[?25l"] +[44.578798, "o", "\u001b[29;99H3\u001b[28;39H\u001b[?25h"] +[44.621906, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mf"] +[44.622908, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m4\u001b[28;40H\u001b[?25h"] +[44.767686, "o", "\u001b[?25l"] +[44.768467, "o", "\u001b[?25h"] +[44.801401, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mr"] +[44.80251, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m5\u001b[28;41H\u001b[?25h"] +[44.865767, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mo"] +[44.866761, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m6\u001b[28;42H\u001b[?25h"] +[44.930382, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mm"] +[44.93145, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m7\u001b[28;43H\u001b[?25h"] +[45.002999, "o", "\u001b[?25l"] +[45.004089, "o", "\u001b[29;99H8\u001b[28;44H\u001b[?25h"] +[45.066857, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mt"] +[45.067907, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m9\u001b[28;45H\u001b[?25h"] +[45.154543, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mh"] +[45.155553, "o", "\u001b[m\u001b[37m\u001b[29;98H\u001b[38;5;234m\u001b[48;5;254m40\u001b[28;46H\u001b[?25h"] +[45.219308, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234me"] +[45.220292, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m1\u001b[28;47H\u001b[?25h"] +[45.27361, "o", "\u001b[?25l"] +[45.274581, "o", "\u001b[29;99H2\u001b[28;48H\u001b[?25h"] +[45.420821, "o", "\u001b[?25l"] +[45.421644, "o", "\u001b[?25h"] +[45.427495, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mc"] +[45.428436, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m3\u001b[28;49H\u001b[?25h"] +[45.485035, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mo"] +[45.486004, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m4\u001b[28;50H\u001b[?25h"] +[45.535923, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mm"] +[45.536864, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m5\u001b[28;51H\u001b[?25h"] +[45.67832, "o", "\u001b[?25l"] +[45.678958, "o", "\u001b[?25h"] +[45.681262, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mm"] +[45.681905, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m6\u001b[28;52H\u001b[?25h"] +[45.781754, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234ma"] +[45.78298, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m7\u001b[28;53H\u001b[?25h"] +[45.867023, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mn"] +[45.86838, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m8\u001b[28;54H\u001b[?25h"] +[45.947653, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234md"] +[45.949036, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m9\u001b[28;55H\u001b[?25h"] +[45.996746, "o", "\u001b[?25l"] +[45.99802, "o", "\u001b[29;98H50\u001b[28;56H\u001b[?25h"] +[46.111751, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234ml"] +[46.112995, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m1\u001b[28;57H\u001b[?25h"] +[46.143361, "o", "\u001b[?25l"] +[46.144204, "o", "\u001b[?25h"] +[46.176066, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mi"] +[46.177009, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m2\u001b[28;58H\u001b[?25h"] +[46.290998, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234mn"] +[46.292134, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m3\u001b[28;59H\u001b[?25h"] +[46.322155, "o", "\u001b[?25l"] +[46.322854, "o", "\u001b[?25h"] +[46.35816, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234me"] +[46.359055, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m4\u001b[28;60H\u001b[?25h"] +[46.501581, "o", "\u001b[?25l"] +[46.502707, "o", "\u001b[?25h"] +[46.507489, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[48;5;234m."] +[46.50863, "o", "\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m5\u001b[28;61H\u001b[?25h"] +[46.6442, "o", "\u001b[?25l"] +[46.644989, "o", "\u001b[?25h"] +[47.863226, "o", "\b"] +[47.879544, "o", "\u001b[2 q\u001b[?25l"] +[47.880345, "o", "\u001b[m\u001b[37m\r\n\u001b[1m\u001b[97m\u001b[44m NORMAL \u001b[m\u001b[37m\u001b[34m\u001b[48;5;238m\u001b[m\u001b[37m\u001b[29;99H\u001b[38;5;234m\u001b[48;5;254m4\u001b[28;60H\u001b[?25h"] +[47.993999, "o", "\u001b[?25l"] +[47.994637, "o", "\u001b[?25h"] +[48.657375, "o", "\u001b[?25l\r\n\r\n\u001b[m\u001b[37m:\u001b[?2004h\u001b[?25h"] +[48.901196, "o", "w"] +[49.143303, "o", "q"] +[49.637485, "o", "\r"] +[49.637936, "o", "\u001b[?25l\u001b[?1006l\u001b[?1002l\u001b[?2004l"] +[49.638343, "o", "\"wiki/Related Tools.wiki\""] +[49.644714, "o", " 68L, 3882C written"] +[49.662295, "o", "\r\u001b]2;Related Tools.wiki + (~/.vimwiki/wiki) - VIM\u0007\u001b[23;2t\u001b[23;1t\u001b[22;2t\u001b[22;1t\u001b[23;2t\u001b[23;1t\r\r\n\u001b[39;49m\u001b[?2004l\u001b[?1l\u001b>\u001b[?25h\u001b[>4;m\u001b[?2004l\u001b[?1004l\u001b[?1049l\u001b[23;0;0t"] +[49.668616, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[J> \u001b[K\u001b[67C \u001b[68D"] +[49.66948, "o", "\r\r\u001b[?2004h\u001b[0m\u001b[27m\u001b[24m\u001b[J> \u001b[K\u001b[67C \u001b[68D"] +[50.265778, "o", "v"] +[50.354279, "o", "\bvi"] +[50.453211, "o", "m"] +[50.591559, "o", "w"] +[50.73511, "o", "i"] +[50.912015, "o", "k"] +[51.067609, "o", "i"] +[51.170407, "o", " "] +[51.318616, "o", "d"] +[51.403, "o", "i"] +[51.522321, "o", "a"] +[51.621358, "o", "r"] +[51.721855, "o", "y"] +[51.79326, "o", " "] +[51.915283, "o", "t"] +[52.014835, "o", "o"] +[52.148808, "o", "d"] +[52.221807, "o", "a"] +[52.289313, "o", "y"] +[52.765627, "o", "\u001b[?2004l\r\r\n"] +[52.977471, "o", "\u001b[?1006h\u001b[?1002h\u001b[?1049h\u001b[22;0;0t\u001b[?1004h\u001b[?2004h\u001b[>4;2m\u001b[?1h\u001b=\u001b[?2004h\u001b[1;30r\u001b[?12h\u001b[?12l\u001b[22;2t\u001b[22;1t"] +[52.978207, "o", "\u001b[27m\u001b[23m\u001b[29m\u001b[m\u001b[37m\u001b[H\u001b[2J"] +[52.988064, "o", "\u001b[?25l\u001b[30;1H\"~/.vimwiki/wiki/diary/2021-05-04.wiki\" 8L, 182C"] +[53.025928, "o", "\u001b[2 q\u001b[2;1H▽\u001b[6n\u001b[2;1H \u001b[1;1H\u001b[>c\u001b]10;?\u0007\u001b]11;?\u0007"] +[53.031171, "o", "\u001b[1;1H\u001b[1m\u001b[97m\u001b[44m 1 2021-05-04.wiki \u001b[m\u001b[37m\u001b[34m\u001b[48;5;234m\u001b[m\u001b[37m\u001b[1;21H\u001b[38;5;254m\u001b[48;5;234m \u001b[m\u001b[37m\u001b[1;98H\u001b[38;5;234m\u001b[48;5;254m X \u001b[m\u001b[37m\u001b[2;1H\u001b[90m 1 \u001b[m\u001b[37m= 2021-05-04 Notes =\r\n\u001b[90m 2 \r\n 3 \u001b[m\u001b[37m== vimwiki-cli ==\r\n\u001b[90m 4 \r\n 5 \u001b[m\u001b[37m- [X] Finish writing documentation.\r\n\u001b[90m 6 \u001b[m\u001b[37m- [X] Add CONTRIBUTORS.md.\r\n\u001b[90m 7 \u001b[m\u001b[37m- [ ] Record a *\u001b[1mgreat\u001b[m\u001b[37m* demo!\r\n\u001b[37m 8 \u001b[m\u001b[37m\u001b[48;5;234m - [ ] Add `\u001b[m\u001b[37m\u001b[34m\u001b[48;5;234mvimwiki-cli\u001b[m\u001b[37m\u001b[48;5;234m` to \u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234m[[Related Tools]]\u001b[m\u001b[37m\u001b[48;5;234m \u001b[m\u001b[37m\u001b[10;1H\u001b[94m~ \u001b[11;1H~ \u001b[12;1H~ "] +[53.031215, "o", " \u001b[13;1H~ \u001b[14;1H~ \u001b[15;1H~ \u001b[16;1H~ \u001b[17;1H~ \u001b[18;1H~ \u001b[19;1H~ \u001b[20;1H~ \u001b[21;1H~ "] +[53.031755, "o", "\u001b[22;1H~ \u001b[23;1H~ \u001b[24;1H~ \u001b[25;1H~ \u001b[26;1H~ \u001b[27;1H~ \u001b[28;1H~ "] +[53.032326, "o", "\u001b[m\u001b[37m\u001b[29;1H\u001b[1m\u001b[97m\u001b[44m NORMAL \u001b[m\u001b[37m\u001b[34m\u001b[48;5;238m\u001b[m\u001b[37m\u001b[29;10H\u001b[38;5;254m\u001b[48;5;238m \u001b[29;12H master \u001b[m\u001b[37m\u001b[38;5;238m\u001b[48;5;234m\u001b[m\u001b[37m\u001b[29;21H\u001b[38;5;254m\u001b[48;5;234m wiki/diary/2021-05-04.wiki \u001b[m\u001b[37m\u001b[38;5;234m\u001b[48;5;234m\u001b[m\u001b[37m\u001b[29;50H\u001b[38;5;254m\u001b[48;5;234m \u001b[m\u001b[37m\u001b[38;5;234m\u001b[48;5;234m\u001b[m\u001b[37m\u001b[29;83H\u001b[38;5;238m\u001b[48;5;234m\u001b[m\u001b[37m\u001b[29;84H\u001b[38;5;254m\u001b[48;5;238m 100% \u001b[m\u001b[37m\u001b[29;91H\u001b[38;5;234m\u001b[48;5;254m \u001b[29;93H 8:5 \u001b]2;2021-05-04.wiki (~/.vimwiki/wiki/diary) - VIM\u0007"] +[53.033219, "o", "\u001b[9;9H\u001b[?25h"] +[53.090079, "o", "\u001b[?1006l\u001b[?1002l\u001b[?2004l"] +[53.091114, "o", "\u001b[?2004h\u001b[?1006h\u001b[?1002h\u001b[?1006l\u001b[?1002l\u001b[?2004l"] +[53.093338, "o", "\u001b[?2004h\u001b[?1006h\u001b[?1002h\u001b[?1006l\u001b[?1002l\u001b[?2004l"] +[53.096335, "o", "\u001b[?2004h\u001b[?1006h\u001b[?1002h\u001b[?2004h"] +[53.096634, "o", "\u001b]2;2021-05-04.wiki (~/.vimwiki/wiki/diary) - VIM\u0007"] +[54.665725, "o", "\u001b[?2004h"] +[54.67551, "o", "\u001b[?25l"] +[54.678659, "o", "\u001b[m\u001b[37m\u001b[1;20H\u001b[1m\u001b[97m\u001b[44m[+] \u001b[m\u001b[37m\u001b[34m\u001b[48;5;234m\u001b[m\u001b[37m\u001b[8;8HX\u001b[9;12H\u001b[48;5;234mX"] +[54.679382, "o", "\u001b[m\u001b[37m\u001b[29;49H\u001b[38;5;254m\u001b[48;5;234m[+]\u001b[m\u001b[37m\u001b[1C\u001b[38;5;234m\u001b[48;5;234m\u001b]2;2021-05-04.wiki + (~/.vimwiki/wiki/diary) - VIM\u0007"] +[54.68113, "o", "\u001b[9;9H\u001b[?25h"] +[54.803825, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[2;3H\u001b[90m 1 \u001b[m\u001b[37m= 2021-05-04 Notes =\u001b[3;3H\u001b[90m 2 \r\n 3 \u001b[m\u001b[37m== vimwiki-cli ==\u001b[5;3H\u001b[90m 4 \r\n 5 \u001b[m\u001b[37m- [X] Finish writing documentation.\u001b[7;3H\u001b[90m 6 \u001b[m\u001b[37m- [X] Add CONTRIBUTORS.md.\r\n\u001b[33m~ \u001b[m\u001b[37m\u001b[90m 7 \u001b[m\u001b[37m- [X] Record a *\u001b[1mgreat\u001b[m\u001b[37m* demo!\r\n\u001b[33m~ \u001b[m\u001b[37m\u001b[37m 8 \u001b[m\u001b[37m\u001b[2C\u001b[48;5;234m - [X] Add `\u001b[m\u001b[37m\u001b[34m\u001b[48;5;234mvimwiki-cli\u001b[m\u001b[37m\u001b[48;5;234m` to \u001b[m\u001b[37m\u001b[4m\u001b[94m\u001b[48;5;234m[[Related Tools]]"] +[54.804284, "o", "\u001b[9;11H\u001b[?25h"] +[56.059347, "o", "\u001b[?25l\u001b[m\u001b[37m\u001b[30;1H\u001b[K\u001b[30;1H:\u001b[?2004h"] +[56.059701, "o", "\u001b[?25h"] +[56.307923, "o", "w"] +[56.523436, "o", "q"] +[56.731317, "o", "\r"] +[56.731699, "o", "\u001b[?25l\u001b[?1006l\u001b[?1002l\u001b[?2004l"] +[56.731804, "o", "\"wiki/diary/2021-05-04.wiki\""] +[56.7415, "o", " 8L, 182C written"] +[56.758986, "o", "\r\u001b]2;Related Tools.wiki + (~/.vimwiki/wiki) - VIM\u0007\u001b[23;2t\u001b[23;1t\u001b[22;2t\u001b[22;1t\u001b[23;2t\u001b[23;1t\r\r\n\u001b[39;49m\u001b[?2004l\u001b[?1l\u001b>\u001b[?25h\u001b[>4;m\u001b[?2004l\u001b[?1004l\u001b[?1049l\u001b[23;0;0t"] +[56.763887, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[J> \u001b[K\u001b[67C \u001b[68D"] +[56.764349, "o", "\r\r\u001b[?2004h\u001b[0m\u001b[27m\u001b[24m\u001b[J> \u001b[K\u001b[67C \u001b[68D"] +[57.138927, "o", "g"] +[57.235826, "o", "\bgi"] +[57.332261, "o", "t"] +[57.380013, "o", " "] +[57.552776, "o", "c"] +[57.618149, "o", "o"] +[57.695046, "o", "m"] +[57.853982, "o", "m"] +[57.943164, "o", "i"] +[58.053934, "o", "t"] +[58.134827, "o", " "] +[58.395612, "o", "-"] +[58.478227, "o", "a"] +[58.637724, "o", " "] +[58.778272, "o", "-"] +[58.993868, "o", "m"] +[59.10482, "o", " "] +[59.268853, "o", "'"] +[59.548293, "o", "U"] +[59.708867, "o", "p"] +[59.796098, "o", "d"] +[59.900954, "o", "a"] +[59.994491, "o", "t"] +[60.089628, "o", "e"] +[60.146289, "o", " "] +[60.322568, "o", "v"] +[60.428998, "o", "i"] +[60.510147, "o", "m"] +[60.638009, "o", "w"] +[60.774885, "o", "i"] +[60.948263, "o", "k"] +[61.103887, "o", "i"] +[61.256502, "o", "w"] +[61.361821, "o", "i"] +[61.520288, "o", "k"] +[61.695677, "o", "i"] +[62.05812, "o", "!"] +[62.318457, "o", "'"] +[63.567055, "o", "\u001b[?2004l\r\r\n"] +[63.653316, "o", "\u001b[90mvim\u001b[92m|\u001b[37mwiki\u001b[0m: Generating links..."] +[66.050112, "o", "done.\r\n"] +[66.051653, "o", "\u001b[90mvim\u001b[92m|\u001b[37mwiki\u001b[0m: Generating diary links..."] +[68.361937, "o", "done.\r\n"] +[68.363488, "o", "\u001b[90mvim\u001b[92m|\u001b[37mwiki\u001b[0m: Rebuilding tag metadata..."] +[70.680921, "o", "done.\r\n"] +[70.683838, "o", "\u001b[90mvim\u001b[92m|\u001b[37mwiki\u001b[0m: Generating tag links..."] +[73.022411, "o", "done.\r\n"] +[73.035142, "o", "[master 593e5ec] Update vimwikiwiki!\r\n 2 files changed, 6 insertions(+), 2 deletions(-)\r\n"] +[73.035556, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[J> \u001b[K\u001b[67C \u001b[68D"] +[75.268685, "o", "\u001b[?2004l\r\r\n"] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..d1e6ae6 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,2 @@ +[build-system] +requires = ["setuptools", "wheel"] diff --git a/scripts/pre-commit.sh b/scripts/pre-commit.sh new file mode 100755 index 0000000..3bcf91b --- /dev/null +++ b/scripts/pre-commit.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +# +# A pre-commit hook script to execute non-interactive vimwiki commands to +# rebuild tag metadata and generate links before commit. +# +# This hook relies on the following configuration options: +# +# vimwiki.options -- Extra options to pass to the vimwiki command +# vimwiki.linkspage -- Page which contains generated links +# vimwiki.taglinkspage -- Page which contains generated tag links +# vimwiki.generatelinks -- Generate links before commit (bool) +# vimwiki.generatediarylinks -- Generate diary links before commit (bool) +# vimwiki.generatetaglinks -- Generate tag links before commit (bool) +# vimwiki.rebuildtags -- Rebuild tag metadata before commit (bool) +# +# To enable this hook, copy or link this file to ".git/hooks/pre-commit". + +if git config --get-colorbool color.interactive +then + # See https://github.com/vimwiki/vimwiki/blob/master/doc/logo.svg + say_prefix='\e[90mvim\e[92m|\e[37mwiki\e[0m' +else + say_prefix='vimwiki' +fi + +say () { + printf '%b: %s' "$say_prefix" "$*" +} + +say_done () { + printf 'done.\n' +} + +# Exit if no changes staged for commit +if git diff --cached --diff-filter=tuxb --quiet +then + exit 0 +fi + +# Stash untracked changes +git stash push --keep-index --include-untracked --quiet >/dev/null 2>&1 + +trap 'git stash pop --quiet >/dev/null 2>&1' 0 +trap 'exit 1' 1 2 3 15 + +options=$(git config vimwiki.options) + +if test "$(git config --bool vimwiki.generatelinks || echo false)" = true +then + page=$(git config vimwiki.linkspage || echo index) + + say 'Generating links...' + vimwiki $options generate-links "$page" || exit + say_done +fi + +if test "$(git config --bool vimwiki.generatediarylinks || echo false)" = true +then + say 'Generating diary links...' + vimwiki $options diary generate-links || exit + say_done +fi + +if test "$(git config --bool vimwiki.rebuildtags || echo false)" = true +then + say 'Rebuilding tag metadata...' + vimwiki $options tags rebuild || exit + say_done +fi + +if test "$(git config --bool vimwiki.generatetaglinks || echo false)" = true +then + page=$(git config vimwiki.taglinkspage || echo index) + + say 'Generating tag links...' + vimwiki $options tags generate-links "$page" || exit + say_done +fi + +# Add changes to index +git add --all diff --git a/scripts/release_notes.py b/scripts/release_notes.py new file mode 100755 index 0000000..211aa87 --- /dev/null +++ b/scripts/release_notes.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +# Copyright (c) 2021 Steven Stallion +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + +import argparse +import logging +import sys +from pprint import pformat +from textwrap import indent + +import keepachangelog + +logger = logging.getLogger(__name__) + + +def main(): + parser = argparse.ArgumentParser(description=""" + Generate release notes from a changelog that conforms to Keep a + Changelog v1.0.0 (https://keepachangelog.com/en/1.0.0/). + """) + + parser.add_argument('-f', '--file', nargs='?', default='CHANGELOG.md', + help='read changes from file (default: CHANGELOG.md)') + + parser.add_argument('-o', '--output', nargs='?', default=sys.stdout, + type=argparse.FileType('w'), + help='write output to file (default: )', metavar='FILE') + + parser.add_argument('-v', '--verbose', action='store_true', + help='increase output verbosity') + + parser.add_argument('version', help='version to generate') + + args = parser.parse_args() + + logging.basicConfig(format='%(levelname)s: %(message)s', + level=logging.DEBUG if args.verbose else logging.INFO) + + logger.debug('Reading changes from %s', args.file) + changes = keepachangelog.to_raw_dict(args.file) + + logger.debug('Found changes:\n%s', indent(pformat(changes), ' ')) + if args.version not in changes: + logger.error('Unable to find version %s', args.version) + sys.exit(1) + + logger.debug('Writing output to %s', args.output.name) + with args.output as f: + input = changes[args.version]['raw'] + for index, line in enumerate(input.splitlines(True)): + # keepachangelog will strip bare newlines, which can impact + # readability. Newlines are added around headings to minimally + # conform to the Google Markdown style guide. + if line.startswith('#'): + if index > 0: + f.write('\n') + f.write('%s\n' % line) + else: + f.write(line) + + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..2d8f3b1 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,27 @@ +[bumpversion] +current_version = 0.0.0 + +[bumpversion:file:setup.py] +search = version='{current_version}' +replace = version='{new_version}' + +[bumpversion:file:vimwiki_cli/__init__.py] +search = __version__ = '{current_version}' +replace = __version__ = '{new_version}' + +[bdist_wheel] +universal = 1 + +[coverage:report] +fail_under = 90 +show_missing = True + +[pycodestyle] +exclude = .git,__pycache__,build,dist,venv +select = E,W +max-line-length = 99 +show-source = True + +[tool:pytest] +addopts = --cov=vimwiki_cli +testpaths = tests diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..78a7579 --- /dev/null +++ b/setup.py @@ -0,0 +1,60 @@ +import os.path +from setuptools import find_packages, setup + +dirname = os.path.dirname(os.path.abspath(__file__)) +with open(os.path.join(dirname, 'README.md')) as f: + long_description = f.read() + +setup(name='vimwiki-cli', + version='0.0.0', + description='Vimwiki Command-Line Interface', + long_description=long_description, + long_description_content_type='text/markdown', + author='Steven Stallion', + author_email='sstallion@gmail.com', + url='https://github.com/sstallion/vimwiki-cli', + packages=find_packages(), + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Environment :: Console', + 'Intended Audience :: End Users/Desktop', + 'License :: OSI Approved :: BSD License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Topic :: Text Editors', + 'Topic :: Text Editors :: Documentation' + ], + license='BSD-2-Clause', + keywords='cli vim vimwiki wiki', + install_requires=[ + 'click~=7.1', + ], + entry_points={ + 'console_scripts': [ + 'vimwiki=vimwiki_cli.__main__:cli' + ] + }, + extras_require={ + 'build': [ + 'build>=0.3.0', + 'virtualenv>=20.0.35' + ], + 'test': [ + 'coverage~=5.5', + 'mock~=3.0', + 'pycodestyle~=2.7', + 'pytest~=4.6', + 'pytest-cov~=2.11' + ], + 'deploy': [ + 'bump2version>=1.0.0', + 'keepachangelog>=0.5.0' + ] + }) diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..917bfd7 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,49 @@ +# Copyright (c) 2021 Steven Stallion +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + +import os + +import pytest + +from vimwiki_cli.context import CONTEXT_SETTINGS +from vimwiki_cli.wiki import Wiki + + +# Remove conflicting environment variables: +@pytest.fixture(scope='session', autouse=True) +def setup_env(): + prefix = CONTEXT_SETTINGS.get('auto_envvar_prefix') + for key in os.environ: + if key.startswith(prefix): + del os.environ[key] + + +@pytest.fixture(autouse=True) +def wiki_options(): + return {} + + +@pytest.fixture +def wiki(wiki_options): + return Wiki(**wiki_options) diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 0000000..8f9eee2 --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,271 @@ +# Copyright (c) 2021 Steven Stallion +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + +import mock +import pytest +from click.testing import CliRunner + +from vimwiki_cli.__main__ import * +from vimwiki_cli.wiki import Wiki + + +@pytest.fixture +def runner(): + return CliRunner() + + +@mock.patch('vimwiki_cli.__main__.make_wiki') +@mock.patch('vimwiki_cli.__main__.index') +@pytest.mark.parametrize('args,expected', [ + ('--editor EDITOR', {'editor': 'EDITOR'}), + ('--count 42', {'count': 42}), + ('--select', {'select': True}), + ('--open-matches', {'open_matches': True}), + ('--open-tabs', {'open_tabs': True}) +]) +def test_options(_, mock_make_wiki, runner, args, expected): + result = runner.invoke(cli, args=args) + assert result.exit_code == 0 + + for key, value in expected.items(): + assert key in mock_make_wiki.call_args.kwargs and \ + mock_make_wiki.call_args.kwargs[key] == value + + +@mock.patch('vimwiki_cli.__main__.make_wiki') +@mock.patch('vimwiki_cli.__main__.index') +@pytest.mark.parametrize('expected', [ + ({'editor': Wiki.DEFAULT_EDITOR}), + ({'count': Wiki.DEFAULT_COUNT}), + ({'select': Wiki.DEFAULT_SELECT}), + ({'open_matches': Wiki.DEFAULT_OPEN_MATCHES}), + ({'open_tabs': Wiki.DEFAULT_OPEN_TABS}) +]) +def test_options_with_defaults(_, mock_make_wiki, runner, expected): + result = runner.invoke(cli) + assert result.exit_code == 0 + + for key, value in expected.items(): + assert key in mock_make_wiki.call_args.kwargs and \ + mock_make_wiki.call_args.kwargs[key] == value + + +@mock.patch('vimwiki_cli.__main__.make_wiki') +@mock.patch('vimwiki_cli.__main__.index') +@pytest.mark.parametrize('env,expected', [ + ({'VIMWIKI_EDITOR': 'EDITOR'}, {'editor': 'EDITOR'}), + ({'VIMWIKI_COUNT': '42'}, {'count': 42}), + ({'VIMWIKI_SELECT': '1'}, {'select': True}), + ({'VIMWIKI_OPEN_MATCHES': '1'}, {'open_matches': True}), + ({'VIMWIKI_OPEN_TABS': '1'}, {'open_tabs': True}) +]) +def test_options_with_env(_, mock_make_wiki, runner, env, expected): + result = runner.invoke(cli, env=env) + assert result.exit_code == 0 + + for key, value in expected.items(): + assert key in mock_make_wiki.call_args.kwargs and \ + mock_make_wiki.call_args.kwargs[key] == value + + +@mock.patch('vimwiki_cli.__main__.index') +def test_default(mock_index, runner): + result = runner.invoke(cli) + assert result.exit_code == 0 + + # index() should be invoked if no command is specified: + mock_index.assert_called_with() + + +@mock.patch('vimwiki_cli.wiki.Wiki.check_links') +def test_check_links(mock_check_links, runner): + result = runner.invoke(cli, 'check-links') + assert result.exit_code == 0 + + mock_check_links.assert_called_with() + + +@mock.patch('vimwiki_cli.wiki.Wiki.generate_links') +@pytest.mark.parametrize('args,expected', [ + ('PAGE', ('PAGE', '')), + ('PAGE PATTERN', ('PAGE', 'PATTERN')) +]) +def test_generate_links(mock_generate_links, runner, args, expected): + result = runner.invoke(cli, 'generate-links ' + args) + assert result.exit_code == 0 + + mock_generate_links.assert_called_with(*expected) + + +@mock.patch('vimwiki_cli.wiki.Wiki.generate_links') +def test_generate_links_with_empty_page(mock_generate_links, runner): + result = runner.invoke(cli, 'generate-links ""') + assert result.exit_code != 0 + + mock_generate_links.assert_not_called() + + +@mock.patch('vimwiki_cli.wiki.Wiki.goto') +def test_goto(mock_goto, runner): + result = runner.invoke(cli, 'goto PAGE') + assert result.exit_code == 0 + + mock_goto.assert_called_with('PAGE') + + +@mock.patch('vimwiki_cli.wiki.Wiki.goto') +def test_goto_with_empty_page(mock_goto, runner): + result = runner.invoke(cli, 'goto ""') + assert result.exit_code != 0 + + mock_goto.assert_not_called() + + +@mock.patch('vimwiki_cli.wiki.Wiki.help') +def test_help(mock_help, runner): + result = runner.invoke(cli, 'help') + assert result.exit_code == 0 + + mock_help.assert_called_with() + + +@mock.patch('vimwiki_cli.wiki.Wiki.index') +def test_index(mock_index, runner): + result = runner.invoke(cli, 'index') + assert result.exit_code == 0 + + mock_index.assert_called_with() + + +@mock.patch('vimwiki_cli.wiki.Wiki.search') +def test_search(mock_search, runner): + result = runner.invoke(cli, 'search PATTERN') + assert result.exit_code == 0 + + mock_search.assert_called_with('PATTERN') + + +@mock.patch('vimwiki_cli.wiki.Wiki.search') +def test_search_with_empty_pattern(mock_search, runner): + result = runner.invoke(cli, 'search ""') + assert result.exit_code != 0 + + mock_search.assert_not_called() + + +@mock.patch('vimwiki_cli.diary.index') +def test_diary_default(mock_index, runner): + result = runner.invoke(cli, 'diary') + assert result.exit_code == 0 + + # index() should be invoked if no command is specified: + mock_index.assert_called_with() + + +@mock.patch('vimwiki_cli.wiki.Wiki.diary_generate_links') +def test_diary_generate_links(mock_diary_generate_links, runner): + result = runner.invoke(cli, 'diary generate-links') + assert result.exit_code == 0 + + mock_diary_generate_links.assert_called_with() + + +@mock.patch('vimwiki_cli.wiki.Wiki.diary_index') +def test_diary_index(mock_diary_index, runner): + result = runner.invoke(cli, 'diary index') + assert result.exit_code == 0 + + mock_diary_index.assert_called_with() + + +@mock.patch('vimwiki_cli.wiki.Wiki.make_diary_note') +def test_diary_today(mock_make_diary_note, runner): + result = runner.invoke(cli, 'diary today') + assert result.exit_code == 0 + + mock_make_diary_note.assert_called_with() + + +@mock.patch('vimwiki_cli.wiki.Wiki.make_tomorrow_diary_note') +def test_diary_tomorrow(mock_make_tomorrow_diary_note, runner): + result = runner.invoke(cli, 'diary tomorrow') + assert result.exit_code == 0 + + mock_make_tomorrow_diary_note.assert_called_with() + + +@mock.patch('vimwiki_cli.wiki.Wiki.make_yesterday_diary_note') +def test_diary_yesterday(mock_make_yesterday_diary_note, runner): + result = runner.invoke(cli, 'diary yesterday') + assert result.exit_code == 0 + + mock_make_yesterday_diary_note.assert_called_with() + + +@mock.patch('vimwiki_cli.wiki.Wiki.generate_tag_links') +@pytest.mark.parametrize('args,expected', [ + ('PAGE', ('PAGE', ())), + ('PAGE TAG1 TAG2', ('PAGE', ('TAG1', 'TAG2'))) +]) +def test_tags_generate_links(mock_generate_tag_links, runner, args, expected): + result = runner.invoke(cli, 'tags generate-links ' + args) + assert result.exit_code == 0 + + mock_generate_tag_links.assert_called_with(*expected) + + +@mock.patch('vimwiki_cli.wiki.Wiki.generate_tag_links') +def test_tags_generate_links_with_empty_page(mock_generate_tag_links, runner): + result = runner.invoke(cli, 'tags generate-links ""') + assert result.exit_code != 0 + + mock_generate_tag_links.assert_not_called() + + +@mock.patch('vimwiki_cli.wiki.Wiki.rebuild_tags') +@pytest.mark.parametrize('args,expected', [ + ('', False), + ('--all', True) +]) +def test_tags_rebuild(mock_rebuild_tags, runner, args, expected): + result = runner.invoke(cli, 'tags rebuild ' + args) + assert result.exit_code == 0 + + mock_rebuild_tags.assert_called_with(expected) + + +@mock.patch('vimwiki_cli.wiki.Wiki.search_tags') +def test_search_tags(mock_search_tags, runner): + result = runner.invoke(cli, 'tags search PATTERN') + assert result.exit_code == 0 + + mock_search_tags.assert_called_with('PATTERN') + + +@mock.patch('vimwiki_cli.wiki.Wiki.search_tags') +def test_search_tags_with_empty_pattern(mock_search_tags, runner): + result = runner.invoke(cli, 'tags search ""') + assert result.exit_code != 0 + + mock_search_tags.assert_not_called() diff --git a/tests/test_editor.py b/tests/test_editor.py new file mode 100644 index 0000000..b1c0a3c --- /dev/null +++ b/tests/test_editor.py @@ -0,0 +1,188 @@ +# Copyright (c) 2021 Steven Stallion +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + +import subprocess + +import mock +import pytest + +from vimwiki_cli.editor import * + + +@pytest.fixture(autouse=True) +def cmd_options(): + return {} + + +@pytest.fixture +def cmd(wiki, cmd_options): + return Command(wiki, 'COMMAND', **cmd_options) + + +@pytest.fixture +def cmd_global(wiki, cmd_options): + return GlobalCommand(wiki, 'GLOBAL_COMMAND', **cmd_options) + + +@pytest.fixture +def cmd_local(wiki, cmd_options): + return LocalCommand(wiki, 'LOCAL_COMMAND', **cmd_options) + + +@pytest.fixture +def cmd_diary(wiki, cmd_options): + return DiaryCommand(wiki, 'DIARY_COMMAND', **cmd_options) + + +@pytest.mark.parametrize('wiki_options', [{'open_matches': True}]) +@pytest.mark.parametrize('cmd_options', [{'interactive': True, 'open_matches': True}]) +def test_interactive_with_matches(cmd): + assert cmd._args == ['COMMAND', 'lopen'] + + +@pytest.mark.parametrize('wiki_options', [{'open_tabs': True}]) +@pytest.mark.parametrize('cmd_options', [{'interactive': True}]) +def test_interactive_with_tabs(cmd): + assert cmd._args == ['$tabnew', 'COMMAND'] + + +@pytest.mark.parametrize('cmd_options', [{'interactive': True, 'quit': True}]) +def test_interactive_with_quit(cmd): + assert cmd._args == ['COMMAND', 'q!'] + + +@pytest.mark.parametrize('cmd_options', [{'interactive': True, 'write_quit': True}]) +def test_interactive_with_write_quit(cmd): + assert cmd._args == ['COMMAND', 'wq!'] + + +@mock.patch('os.execvp') +@pytest.mark.parametrize('wiki_options', [{'editor': 'EDITOR'}]) +@pytest.mark.parametrize('cmd_options', [{'interactive': True}]) +def test_interactive_run(mock_execvp, cmd): + cmd.run() + + mock_execvp.assert_called_with('EDITOR', ['EDITOR', '-c', 'COMMAND']) + + +@mock.patch('os.execvp', side_effect=OSError) +@pytest.mark.parametrize('wiki_options', [{'editor': 'EDITOR'}]) +@pytest.mark.parametrize('cmd_options', [{'interactive': True}]) +def test_interactive_run_with_error(mock_execvp, cmd): + with pytest.raises(OSError): + cmd.run() + + +@pytest.mark.parametrize('wiki_options', [{'open_matches': True}]) +@pytest.mark.parametrize('cmd_options', [{'interactive': False, 'open_matches': True}]) +def test_noninteractive_with_matches(cmd): + assert 'lopen' not in cmd._args + + +@pytest.mark.parametrize('wiki_options', [{'open_tabs': True}]) +@pytest.mark.parametrize('cmd_options', [{'interactive': False}]) +def test_noninteractive_with_tabs(cmd): + assert '$tabnew' not in cmd._args + + +@pytest.mark.parametrize('cmd_options', [{'interactive': False, 'quit': True}]) +def test_noninteractive_with_quit(cmd): + assert cmd._args == ['COMMAND', 'q!'] + + +@pytest.mark.parametrize('cmd_options', [{'interactive': False, 'write_quit': True}]) +def test_noninteractive_with_write_quit(cmd): + assert cmd._args == ['COMMAND', 'wq!'] + + +@mock.patch('sys.exit') +@mock.patch('subprocess.Popen') +@pytest.mark.parametrize('wiki_options', [{'editor': 'EDITOR'}]) +@pytest.mark.parametrize('cmd_options', [{'interactive': False}]) +def test_noninteractive_run(mock_Popen, mock_exit, cmd): + mock_Popen.return_value.communicate.return_value = (b'', b'') + mock_Popen.return_value.returncode = 0 + + cmd.run() + + mock_Popen.assert_called_with(['EDITOR', '-c', 'COMMAND'], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + + mock_exit.assert_called_with(mock_Popen.return_value.returncode) + + +@mock.patch('subprocess.Popen', side_effect=OSError) +@pytest.mark.parametrize('wiki_options', [{'editor': 'EDITOR'}]) +@pytest.mark.parametrize('cmd_options', [{'interactive': False}]) +def test_noninteractive_run_with_error(mock_Popen, cmd): + with pytest.raises(OSError): + cmd.run() + + +@mock.patch('sys.exit') +@mock.patch('subprocess.Popen') +@pytest.mark.parametrize('wiki_options', [{'editor': 'EDITOR'}]) +@pytest.mark.parametrize('cmd_options', [{'interactive': False}]) +def test_noninteractive_run_with_returncode(mock_Popen, mock_exit, cmd): + mock_Popen.return_value.communicate.return_value = (b'', b'') + mock_Popen.return_value.returncode = ~0 + + cmd.run() + + mock_exit.assert_called_with(mock_Popen.return_value.returncode) + + +def test_global_with_defaults(cmd_global): + assert cmd_global._args == ['GLOBAL_COMMAND'] + + +@pytest.mark.parametrize('wiki_options', [{'count': 42}]) +def test_global_with_count(cmd_global): + assert cmd_global._args == ['GLOBAL_COMMAND 42'] + + +@pytest.mark.parametrize('wiki_options', [{'count': 42, 'select': True}]) +def test_global_with_count_and_select(cmd_global): + assert 'VimwikiUISelect' not in cmd_global._args + + +@pytest.mark.parametrize('wiki_options', [{'select': True}]) +def test_global_select(cmd_global): + assert cmd_global._args == ['VimwikiUISelect', 'GLOBAL_COMMAND'] + + +@pytest.mark.parametrize('wiki_options', [{'select': True}]) +@pytest.mark.parametrize('cmd_options', [{'interactive': False}]) +def test_global_with_select_forces_interactive(cmd_global): + assert cmd_global.interactive is True + + +def test_local_with_defaults(cmd_local): + assert cmd_local._args == ['VimwikiIndex', 'LOCAL_COMMAND'] + + +def test_diary_with_defaults(cmd_diary): + assert cmd_diary._args == ['VimwikiDiaryIndex', 'DIARY_COMMAND'] diff --git a/tests/test_wiki.py b/tests/test_wiki.py new file mode 100644 index 0000000..e18cbce --- /dev/null +++ b/tests/test_wiki.py @@ -0,0 +1,144 @@ +# Copyright (c) 2021 Steven Stallion +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + +import mock +import pytest + +from vimwiki_cli.wiki import * + + +@mock.patch('vimwiki_cli.wiki.Command') +def test_help(mock_cmd, wiki): + wiki.help() + + mock_cmd.assert_called_with(wiki, 'help vimwiki.txt', 'only') + + +@mock.patch('vimwiki_cli.wiki.GlobalCommand') +def test_index(mock_cmd, wiki): + wiki.index() + + mock_cmd.assert_called_with(wiki, 'VimwikiIndex') + + +@mock.patch('vimwiki_cli.wiki.GlobalCommand') +def test_diary_index(mock_cmd, wiki): + wiki.diary_index() + + mock_cmd.assert_called_with(wiki, 'VimwikiDiaryIndex') + + +@mock.patch('vimwiki_cli.wiki.GlobalCommand') +def test_make_diary_note(mock_cmd, wiki): + wiki.make_diary_note() + + mock_cmd.assert_called_with(wiki, 'VimwikiMakeDiaryNote') + + +@mock.patch('vimwiki_cli.wiki.GlobalCommand') +def test_make_yesterday_diary_note(mock_cmd, wiki): + wiki.make_yesterday_diary_note() + + mock_cmd.assert_called_with(wiki, 'VimwikiMakeYesterdayDiaryNote') + + +@mock.patch('vimwiki_cli.wiki.GlobalCommand') +def test_make_tomorrow_diary_note(mock_cmd, wiki): + wiki.make_tomorrow_diary_note() + + mock_cmd.assert_called_with(wiki, 'VimwikiMakeTomorrowDiaryNote') + + +@mock.patch('vimwiki_cli.wiki.LocalCommand') +def test_goto(mock_cmd, wiki): + wiki.goto('PAGE') + + mock_cmd.assert_called_with(wiki, 'VimwikiGoto PAGE') + + +@mock.patch('vimwiki_cli.wiki.LocalCommand') +def test_search(mock_cmd, wiki): + wiki.search('PATTERN') + + mock_cmd.assert_called_with(wiki, 'silent! VimwikiSearch PATTERN', + open_matches=True) + + +@mock.patch('vimwiki_cli.wiki.LocalCommand') +@pytest.mark.parametrize('args,expected', [ + (('PAGE',), ('VimwikiGoto PAGE', 'VimwikiGenerateLinks ')), + (('PAGE', 'PATTERN'), ('VimwikiGoto PAGE', 'VimwikiGenerateLinks PATTERN')) +]) +def test_generate_links(mock_cmd, wiki, args, expected): + wiki.generate_links(*args) + + mock_cmd.assert_called_with(wiki, *expected, + interactive=False, write_quit=True) + + +@mock.patch('vimwiki_cli.wiki.DiaryCommand') +def test_diary_generate_links(mock_cmd, wiki): + wiki.diary_generate_links() + + mock_cmd.assert_called_with(wiki, 'VimwikiDiaryGenerateLinks', + interactive=False, write_quit=True) + + +@mock.patch('vimwiki_cli.wiki.LocalCommand') +def test_check_links(mock_cmd, wiki): + wiki.check_links() + + mock_cmd.assert_called_with(wiki, 'VimwikiCheckLinks') + + +@mock.patch('vimwiki_cli.wiki.LocalCommand') +@pytest.mark.parametrize('args,expected', [ + (False, 'VimwikiRebuildTags'), + (True, 'VimwikiRebuildTags!') +]) +def test_rebuild_tags(mock_cmd, wiki, args, expected): + wiki.rebuild_tags(args) + + mock_cmd.assert_called_with(wiki, expected, + interactive=False, quit=True) + + +@mock.patch('vimwiki_cli.wiki.LocalCommand') +def test_search_tags(mock_cmd, wiki): + wiki.search_tags('PATTERN') + + mock_cmd.assert_called_with(wiki, 'silent! VimwikiSearchTags PATTERN', + open_matches=True) + + +@mock.patch('vimwiki_cli.wiki.LocalCommand') +@pytest.mark.parametrize('args,expected', [ + (('PAGE',), ('VimwikiGoto PAGE', 'VimwikiGenerateTagLinks ')), + (('PAGE', ('TAG1', 'TAG2')), ('VimwikiGoto PAGE', 'VimwikiGenerateTagLinks TAG1 TAG2')) +]) +def test_generate_tag_links(mock_cmd, wiki, args, expected): + wiki.generate_tag_links(*args) + + mock_cmd.assert_called_with(wiki, *expected, + interactive=False, write_quit=True) diff --git a/vimwiki_cli/__init__.py b/vimwiki_cli/__init__.py new file mode 100644 index 0000000..8a16a2f --- /dev/null +++ b/vimwiki_cli/__init__.py @@ -0,0 +1,25 @@ +# Copyright (c) 2021 Steven Stallion +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + +__version__ = '0.0.0' diff --git a/vimwiki_cli/__main__.py b/vimwiki_cli/__main__.py new file mode 100644 index 0000000..b1e273b --- /dev/null +++ b/vimwiki_cli/__main__.py @@ -0,0 +1,139 @@ +# Copyright (c) 2021 Steven Stallion +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + +import logging +import sys + +import click + +from . import __version__ +from .context import * +from .diary import diary +from .tags import tags + +logger = logging.getLogger(__name__) + + +@click.group(context_settings=CONTEXT_SETTINGS, invoke_without_command=True, + epilog='Please report issues to https://github.com/sstallion/vimwiki-cli/issues.') +@click.option('--editor', + help='Editor to launch, defaults to $EDITOR or vim.') +@click.option('--count', type=int, + help='Index of wiki to open.') +@click.option('--select', is_flag=True, + help='Select wiki from interactive list.') +@click.option('--open-matches', is_flag=True, + help='Open search results by default.') +@click.option('--open-tabs', is_flag=True, + help='Open pages in a new tab by default.') +@click.option('-v', '--verbose', is_flag=True, + help='Increase output verbosity.') +@click.version_option(message='%(prog)s %(version)s') +@click.pass_context +def cli(ctx, *args, **kwargs): + """Vimwiki Command-Line Interface + + vimwiki is a command-line interface to Vimwiki, a plugin for the Vim text + editor. It provides a front-end for interactive editor commands and can + be used to automate repetitive tasks such as rebuilding tag metadata and + generating links, all from the command line. + + Global options may also be configured using environment variables: + + \b + VIMWIKI_EDITOR See --editor. + VIMWIKI_COUNT See --count. + VIMWIKI_SELECT See --select. + VIMWIKI_OPEN_MATCHES See --open-matches. + VIMWIKI_OPEN_TABS See --open-tabs. + + If no command is specified, the wiki index will be opened by default. + """ + verbose = kwargs.pop('verbose', False) + logging.basicConfig(format='%(levelname)s: %(message)s', + level=logging.DEBUG if verbose else logging.INFO) + + logger.debug('Python %s', sys.version) + logger.debug('Version %s', __version__) + + make_wiki(ctx, *args, **kwargs) + if ctx.invoked_subcommand is None: + ctx.invoke(index) + + +@cli.command() +@pass_wiki +def check_links(wiki): + """Search files and check reachability of links.""" + wiki.check_links() + + +@cli.command() +@click.argument('page', callback=validate_nonempty) +@click.argument('pattern', required=False, default='') +@pass_wiki +def generate_links(wiki, page, pattern): + """Create or update an overview of all pages in PAGE. + + An optional PATTERN may be specified to indicate which files to search for + using a glob path. + """ + wiki.generate_links(page, pattern) + + +@cli.command() +@click.argument('page', callback=validate_nonempty) +@pass_wiki +def goto(wiki, page): + """Open or create PAGE.""" + wiki.goto(page) + + +@cli.command() +@pass_wiki +def help(wiki): + """Open plugin help file.""" + wiki.help() + + +@cli.command(hidden=True) +@pass_wiki +def index(wiki): + """Open wiki index.""" + wiki.index() + + +@cli.command() +@click.argument('pattern', callback=validate_nonempty) +@pass_wiki +def search(wiki, pattern): + """Search wiki for text matching PATTERN.""" + wiki.search(pattern) + + +cli.add_command(diary) +cli.add_command(tags) + +if __name__ == '__main__': # pragma: no cover + cli() diff --git a/vimwiki_cli/context.py b/vimwiki_cli/context.py new file mode 100644 index 0000000..7bb5428 --- /dev/null +++ b/vimwiki_cli/context.py @@ -0,0 +1,53 @@ +# Copyright (c) 2021 Steven Stallion +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + +import click + +from .wiki import Wiki + +CONTEXT_SETTINGS = { + 'auto_envvar_prefix': 'VIMWIKI', + 'default_map': { + 'editor': Wiki.DEFAULT_EDITOR, + 'count': Wiki.DEFAULT_COUNT, + 'select': Wiki.DEFAULT_SELECT, + 'open_matches': Wiki.DEFAULT_OPEN_MATCHES, + 'open_tabs': Wiki.DEFAULT_OPEN_TABS + } +} + +pass_wiki = click.make_pass_decorator(Wiki, ensure=True) + + +def make_wiki(ctx, *args, **kwargs): + """Create Wiki instance as user data and add to context.""" + ctx.obj = Wiki(**kwargs) + + +def validate_nonempty(ctx, param, value): + """Validate parameter is not an empty string.""" + if not value.strip(): + raise click.BadParameter('value cannot be empty') + + return value diff --git a/vimwiki_cli/diary.py b/vimwiki_cli/diary.py new file mode 100644 index 0000000..d8704f0 --- /dev/null +++ b/vimwiki_cli/diary.py @@ -0,0 +1,73 @@ +# Copyright (c) 2021 Steven Stallion +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + +import click + +from .context import * + + +@click.group(invoke_without_command=True) +@click.pass_context +def diary(ctx): + """Command group for interacting with the diary. + + If no command is specified, the diary index will be opened by default. + """ + if ctx.invoked_subcommand is None: + ctx.invoke(index) + + +@diary.command() +@pass_wiki +def generate_links(wiki): + """Create or update an overview of diary pages.""" + wiki.diary_generate_links() + + +@diary.command(hidden=True) +@pass_wiki +def index(wiki): + """Open diary index.""" + wiki.diary_index() + + +@diary.command() +@pass_wiki +def today(wiki): + """Open diary page for today.""" + wiki.make_diary_note() + + +@diary.command() +@pass_wiki +def tomorrow(wiki): + """Open diary page for tomorrow.""" + wiki.make_tomorrow_diary_note() + + +@diary.command() +@pass_wiki +def yesterday(wiki): + """Open diary page for yesterday.""" + wiki.make_yesterday_diary_note() diff --git a/vimwiki_cli/editor.py b/vimwiki_cli/editor.py new file mode 100644 index 0000000..00eb820 --- /dev/null +++ b/vimwiki_cli/editor.py @@ -0,0 +1,132 @@ +# Copyright (c) 2021 Steven Stallion +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + +import logging +import os +import subprocess +import sys + +logger = logging.getLogger(__name__) + + +class Command(object): + DEFAULT_INTERACTIVE = True + DEFAULT_OPEN_MATCHES = False + DEFAULT_QUIT = False + DEFAULT_WRITE_QUIT = False + + def __init__(self, wiki, *args, **options): + self._wiki = wiki + self._options = options + + args = list(args) + if self.interactive: + if wiki.open_tabs: + args.insert(0, '$tabnew') + + if self.open_matches and wiki.open_matches: + args.append('lopen') + + if self.quit or self.write_quit: + args.append('%sq!' % ('w' if self.write_quit else '')) + + self._args = args + + def __repr__(self): + return '%s(%r, %r, %r)' % (self.__class__.__name__, + self._wiki, + self._args, + self._options) + + @property + def interactive(self): + return self._options.get('interactive', Command.DEFAULT_INTERACTIVE) + + @property + def open_matches(self): + return self._options.get('open_matches', Command.DEFAULT_OPEN_MATCHES) + + @property + def quit(self): + return self._options.get('quit', Command.DEFAULT_QUIT) + + @property + def write_quit(self): + return self._options.get('write_quit', Command.DEFAULT_WRITE_QUIT) + + def run(self): + """Run command in the editor. This method does not return as + interactive commands replace the runing process with the editor and + non-interactive commands exit with the resulting return code. + """ + logger.debug('Running %r' % self) + + args = [self._wiki.editor] + for arg in self._args: + args.extend(['-c', arg.strip()]) + + logger.debug('Launching %r' % args) + if self.interactive: + os.execvp(args[0], args) + else: + process = subprocess.Popen(args, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + + (_, stderr) = process.communicate() + if process.returncode != 0: + logger.error(stderr.decode(sys.stderr.encoding)) + + sys.exit(process.returncode) + + +class GlobalCommand(Command): + + def __init__(self, wiki, *args, **options): + assert len(args) > 0 + + args = list(args) + if wiki.count is not None: + args.insert(0, '%s %d' % (args.pop(0), wiki.count)) + + elif wiki.select: + args.insert(0, 'VimwikiUISelect') + + # Force interactive if VimwikiUISelect executed: + options['interactive'] = True + + Command.__init__(self, wiki, *args, **options) + + +class LocalCommand(GlobalCommand): + + def __init__(self, wiki, *args, **options): + GlobalCommand.__init__(self, wiki, 'VimwikiIndex', *args, **options) + + +class DiaryCommand(GlobalCommand): + + def __init__(self, wiki, *args, **options): + GlobalCommand.__init__(self, wiki, 'VimwikiDiaryIndex', *args, **options) diff --git a/vimwiki_cli/tags.py b/vimwiki_cli/tags.py new file mode 100644 index 0000000..7ba1989 --- /dev/null +++ b/vimwiki_cli/tags.py @@ -0,0 +1,62 @@ +# Copyright (c) 2021 Steven Stallion +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + +import click + +from .context import * + + +@click.group() +def tags(): + """Command group for interacting with tags.""" + + +@tags.command() +@click.argument('page', callback=validate_nonempty) +@click.argument('tags', nargs=-1) +@pass_wiki +def generate_links(wiki, page, tags): + """Create or update an overview of all tags in PAGE. + + An optional list of TAGS may be specified to restrict output. This + command requires tag metadata built using the rebuild subcommand. + """ + wiki.generate_tag_links(page, tags) + + +@tags.command() +@click.option('--all', is_flag=True, + help='Rebuild all files, not just those that are newer.') +@pass_wiki +def rebuild(wiki, all): + """Rebuild tag metadata.""" + wiki.rebuild_tags(all) + + +@tags.command() +@click.argument('pattern', callback=validate_nonempty) +@pass_wiki +def search(wiki, pattern): + """Search wiki for tags matching PATTERN.""" + wiki.search_tags(pattern) diff --git a/vimwiki_cli/wiki.py b/vimwiki_cli/wiki.py new file mode 100644 index 0000000..5069822 --- /dev/null +++ b/vimwiki_cli/wiki.py @@ -0,0 +1,135 @@ +# Copyright (c) 2021 Steven Stallion +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + +import os + +from .editor import Command, GlobalCommand, LocalCommand, DiaryCommand + + +class Wiki(object): + DEFAULT_EDITOR = os.getenv('EDITOR', 'vim') + DEFAULT_COUNT = None + DEFAULT_SELECT = False + DEFAULT_OPEN_MATCHES = False + DEFAULT_OPEN_TABS = False + + def __init__(self, **options): + self._options = options + + def __repr__(self): + return '%s(%r)' % (self.__class__.__name__, + self._options) + + @property + def editor(self): + return self._options.get('editor', Wiki.DEFAULT_EDITOR) + + @property + def count(self): + return self._options.get('count', Wiki.DEFAULT_COUNT) + + @property + def select(self): + return self._options.get('select', Wiki.DEFAULT_SELECT) + + @property + def open_matches(self): + return self._options.get('open_matches', Wiki.DEFAULT_OPEN_MATCHES) + + @property + def open_tabs(self): + return self._options.get('open_tabs', Wiki.DEFAULT_OPEN_TABS) + + # Help commands: + + def help(self): + """Open plugin help file.""" + Command(self, 'help vimwiki.txt', 'only').run() + + # Global commands: + + def index(self): + """Open wiki index.""" + GlobalCommand(self, 'VimwikiIndex').run() + + def diary_index(self): + """Open diary index.""" + GlobalCommand(self, 'VimwikiDiaryIndex').run() + + def make_diary_note(self): + """Open diary page for today.""" + GlobalCommand(self, 'VimwikiMakeDiaryNote').run() + + def make_yesterday_diary_note(self): + """Open diary page for yesterday.""" + GlobalCommand(self, 'VimwikiMakeYesterdayDiaryNote').run() + + def make_tomorrow_diary_note(self): + """Open diary page for tomorrow.""" + GlobalCommand(self, 'VimwikiMakeTomorrowDiaryNote').run() + + # Local commands: + + def goto(self, page): + """Open or create page.""" + assert page.strip() + LocalCommand(self, 'VimwikiGoto ' + page).run() + + def search(self, pattern): + """Search wiki for text matching pattern.""" + assert pattern.strip() + LocalCommand(self, 'silent! VimwikiSearch ' + pattern, + open_matches=True).run() + + def generate_links(self, page, pattern=''): + """Create or update an overview of all pages in page.""" + assert page.strip() + LocalCommand(self, 'VimwikiGoto ' + page, 'VimwikiGenerateLinks ' + pattern, + interactive=False, write_quit=True).run() + + def diary_generate_links(self): + """Create or update an overview of diary pages.""" + DiaryCommand(self, 'VimwikiDiaryGenerateLinks', + interactive=False, write_quit=True).run() + + def check_links(self): + """Search files and check reachability of links.""" + LocalCommand(self, 'VimwikiCheckLinks').run() + + def rebuild_tags(self, all=False): + """Rebuild tag metadata.""" + LocalCommand(self, 'VimwikiRebuildTags' + ('!' if all else ''), + interactive=False, quit=True).run() + + def search_tags(self, pattern): + """Search wiki for tags matching pattern.""" + assert pattern.strip() + LocalCommand(self, 'silent! VimwikiSearchTags ' + pattern, + open_matches=True).run() + + def generate_tag_links(self, page, tags=()): + """Create or update an overview of all tags in page.""" + assert page.strip() + LocalCommand(self, 'VimwikiGoto ' + page, 'VimwikiGenerateTagLinks ' + ' '.join(tags), + interactive=False, write_quit=True).run()