commit 6b0eda97246eec0e5fdd42879fa68921301643d0 Author: Steven Stallion Date: Sun Apr 18 16:15:17 2021 -0500 Initial commit 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()