├── .codeclimate.yml ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ └── new-link-for-the-list.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── awesomebot.yml │ └── pre-commit.yml ├── .pre-commit-config.yaml ├── CODE_OF_CONDUCT.md ├── Contributing.md ├── LICENSE ├── README.md ├── Writing_Plugins_and_Themes.md └── zsh-plugin-assessor ├── git-process-output.zsh ├── zpa.config └── zsh-plugin-assessor /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: "2" 3 | plugins: 4 | pep8: 5 | enabled: true 6 | duplication: 7 | enabled: true 8 | config: 9 | languages: 10 | - "python" 11 | fixme: 12 | enabled: true 13 | exclude_patterns: 14 | - config/engines.yml 15 | markdownlint: 16 | enabled: true 17 | checks: 18 | MD004: 19 | enabled: false 20 | MD013: 21 | enabled: false 22 | MD026: 23 | enabled: false 24 | MD029: 25 | enabled: false 26 | MD033: 27 | enabled: false 28 | shellcheck: 29 | enabled: true 30 | exclude_patterns: 31 | - .bundle/ 32 | - benchmarks/**/* 33 | - node_modules/**/* 34 | - bin/**/* 35 | - include/**/* 36 | - lib/**/* 37 | - License.md 38 | - spec/**/* 39 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: unixorn # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: unixorn # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: unixorn 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: https://www.redbubble.com/people/unixorn/shop?asc=u 13 | # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/new-link-for-the-list.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: New link for the list 3 | about: Submit a link to a zsh plugin, theme or completion 4 | title: "[LINK]" 5 | labels: submission 6 | assignees: '' 7 | 8 | --- 9 | 10 | 13 | I am submitting a 14 | - [ ] Article link - something useful to ZSH users 15 | - [ ] A link to a new ZSH framework 16 | - [ ] New plugin 17 | - [ ] New theme 18 | - [ ] New tab completion 19 | - [ ] New utility 20 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | # Description 13 | 14 | 15 | 16 | ## Type of changes 17 | 18 | 19 | 20 | - [ ] A link to an external resource like a blog post 21 | - [ ] Add/remove/update a link to a framework 22 | - [ ] Add/remove/update a link to a plugin 23 | - [ ] Add/remove/update a link to a tab completion 24 | - [ ] Add/remove/update a link to a theme 25 | - [ ] Add/remove/update a link to a utility 26 | - [ ] Text cleanups/typo fixes 27 | 28 | ## Copyright Assignment 29 | 30 | - [ ] This document is covered by the [BSD License](https://github.com/unixorn/awesome-zsh-plugins/blob/master/LICENSE), and I agree to contribute this PR under the terms of the license. This is for the list submission, not for the project(s) you're adding, I don't care what license the plugins have as long as they have something. 31 | 32 | ## Checklist 33 | 34 | 39 | 40 | - [ ] I have read the [CONTRIBUTING](https://github.com/unixorn/awesome-zsh-plugins/blob/main/Contributing.md) document. 41 | - [ ] All new and existing tests passed. 42 | - [ ] I have confirmed that the link(s) in my PR is valid. 43 | - [ ] I have signed off my commits. You can use `git commit --amend --no-edit --signoff` to amend an existing commit, and you can find more details about signing off commits on the DCO GitHub action page [here](https://probot.github.io/apps/dco/). 44 | - [ ] My entries are single lines and are in the appropriate (plugins, themes, or completions) section, and in alphabetical order in their section. 45 | - [ ] The completion/plugin/theme has a plugin file in the repository that conforms to the [ZSH Plugin Standard](https://zdharma-continuum.github.io/Zsh-100-Commits-Club/Zsh-Plugin-Standard.html) - TLDR, there's a plugin file with a `.plugin.zsh`, `.zsh` or `.sh` suffix, it is not just bare instructions to be added to `.zshrc` 46 | - [ ] Any added completions have a readme and a license file in their repository. 47 | - [ ] Any added frameworks have a readme and a license file in their repository. 48 | - [ ] Any added plugins have a readme and a license file in their repository. 49 | - [ ] Any added themes have a screenshot, a readme, and a license file in their repository. 50 | - [ ] Any added utilities have a readme and a license file in their repository. 51 | - [ ] I have stripped any leading and/or trailing **zsh-**, **zsh-plugin** and/or **oh-my-zsh-** substrings from the link's displayed name. This makes it easier to find plugins/themes/completions by name by preventing big clusters in the **O** and **Z** sections of the list. 52 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Use `allow` to specify which dependencies to maintain 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: "github-actions" 7 | directory: "/" 8 | schedule: 9 | interval: "weekly" 10 | -------------------------------------------------------------------------------- /.github/workflows/awesomebot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Check links in ZSH list 3 | 4 | on: 5 | # Run daily 6 | schedule: [{cron: "0 1 * * *"}] 7 | push: 8 | branches: ["*"] 9 | pull_request: 10 | branches: ["*"] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: docker://dkhamsing/awesome_bot:latest 19 | with: 20 | args: /github/workspace/README.md --allow-timeout --allow 202,403,429,500,501,502,503,504,509,521,522 --allow-dupe --allow-ssl --request-delay 1 --allow-redirect --white-list https://ipfs.io,slideshare,https://img.shields.io,https://codeclimate.com/github/unixorn/awesome-zsh-plugins,www-s.acm.illinois.edu,https://mgdm.net,https://www.concourse.ci,https://grml.org/zsh/zsh-lovers.html,https://geeknote.me,https://en.ipip.net,https://docs.virtuozzo.com,kubernetes.io,https://youtube-dl.org,https://1password.com,https://iterm2.com,https://mercurial-scm.org,https://hitokoto.cn,https://www.cyberciti.biz,https://keybase.io,https://exercism.io,https://bitbucket.org,https://code.visualstudio.com,https://www.gnu.org 21 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit.yml: -------------------------------------------------------------------------------- 1 | name: pre-commit 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: [main] 7 | 8 | jobs: 9 | pre-commit: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: actions/setup-python@v5 14 | - uses: pre-commit/action@v3.0.1 15 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | repos: 3 | - repo: https://github.com/pre-commit/pre-commit-hooks 4 | rev: v5.0.0 5 | hooks: 6 | - id: check-merge-conflict 7 | - id: check-yaml 8 | - id: end-of-file-fixer 9 | - id: trailing-whitespace 10 | - id: forbid-submodules 11 | - id: mixed-line-ending 12 | - repo: https://github.com/igorshubovych/markdownlint-cli 13 | rev: v0.45.0 14 | hooks: 15 | - id: markdownlint-fix 16 | args: ["--ignore", "LICENSE.md", "--disable", "~MD013"] 17 | - repo: https://github.com/thlorenz/doctoc 18 | rev: v2.2.0 19 | hooks: 20 | - id: doctoc 21 | args: ["--update-only"] 22 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## TL;DR 4 | 5 | Don't be an asshole. I'm fine with losing contributions from smart assholes. 6 | 7 | ## Our Pledge 8 | 9 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 10 | 11 | ## Our Standards 12 | 13 | Examples of behavior that contributes to creating a positive environment include: 14 | 15 | * Using welcoming and inclusive language 16 | * Being respectful of differing viewpoints and experiences 17 | * Gracefully accepting constructive criticism 18 | * Focusing on what is best for the community 19 | * Showing empathy towards other community members 20 | 21 | Examples of unacceptable behavior by participants include: 22 | 23 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 24 | * Trolling, insulting/derogatory comments, and personal or political attacks 25 | * Public or private harassment 26 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 27 | * Other conduct which could reasonably be considered inappropriate in a professional setting 28 | 29 | ## Our Responsibilities 30 | 31 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 32 | 33 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 34 | 35 | ## Scope 36 | 37 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 38 | 39 | ## Enforcement 40 | 41 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at . The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 42 | 43 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 44 | 45 | ## Attribution 46 | 47 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 48 | 49 | [homepage]: http://contributor-covenant.org 50 | [version]: http://contributor-covenant.org/version/1/4/ 51 | -------------------------------------------------------------------------------- /Contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | First and foremost, thanks for the help, I appreciate all of the contributions, and the awesome-zsh-plugins list wouldn't be nearly as complete without them. You may add to the list by submitting a pull request or adding a link in an issue. 4 | 5 | I want to be clear that real, useful PRs are always welcome. _But_ if you came here just to make a useless PR because you watched that idiot CodeWithHarry's youtube video, _I will report it to DigitalOcean_, you will be disqualified from Hacktoberfest and you won't get the free stuff you're trying for._ 6 | 7 | ## Entry Guidelines 8 | 9 | ### General 10 | 11 | - Please make sure all new framework, plugin, themes or completions entries have a license file. Some users are particular about the code they use and want to be sure they are compliant with licensing, so please make sure it's easy for them to determine what the license is. I am aware that there are existing entries without licenses, they were added before I instituted the license policy. 12 | 13 | - Please make sure new entries have a readme. While obviously it's good to read the source code for things we're adding to our environment, it's even better if users can get an overall idea of what a plugin does so they know if it is even potentially useful to them first. 14 | 15 | - Descriptions should be clear, concise, and non-promotional. 16 | 17 | - The list is split into sections for frameworks, plugins, themes and completions, please add your entries to the appropriate section(s). If an entry is a plugin that provides both plugin functionality and tab completions, add it to the plugins section. If an entry is a plugin that provides both plugin functionality and a theme, please add it to the theme section. The completions section is meant for projects that only provide extra tab completions. 18 | 19 | - Descriptions should follow the link, on the same line, with capitalization consistent with the other entries in the section. 20 | 21 | - Each entry should be a single line that ends in a period. This makes keeping the sections sorted easier. 22 | 23 | - Plugin, theme and completions entries _must_ be a single line and be added in alphabetical order in their respective sections of the list. We let GitHub's markdown formatter handle adding any required line breaks rather than embedding line breaks in the entries ourselves, this also allows us to work correctly with any browser window width. 24 | 25 | - For consistency, please use all caps for ZSH in all entry descriptions. 26 | 27 | - Each entry should be limited to one link, and that link should be the first part of the line. It is ok to submit more than one entry in a PR. 28 | 29 | - Please make sure all framework, plugin, themes or completions list entries are sorted _alphabetically_. 30 | - The link should be named the name of the package or project. Please remove any leading or trailing `zsh-plugin`, `zsh-theme` from the visible portion of the link. 31 | 32 | - Your PR should pass the Github Action checks. If the checks show an error that you didn't add (a previous plugin entry has gone 404, for example) you don't _have_ to fix those errors, though I'll certainly appreciate the help if you do, or if you create an issue documenting the problem so I can fix it. 33 | 34 | ### Themes 35 | 36 | - If you're submitting a theme, please make sure that the theme repo has a screen shot so that list users can tell what it looks like before installing it. 37 | 38 | - It is fine to have multiple new entries in a single PR. Just make sure they all have readmes and licenses, and screenshots for theme entries. 39 | 40 | - No need to squash commits. It is fine to have multiple commits in a PR. 41 | 42 | ## To remove an entry 43 | 44 | Open an issue to discuss why the entry should be removed, and optionally create a PR. 45 | 46 | Entries that have gone 404 do not require an issue to remove - just make a PR. 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2025, Joe Block 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | * Neither the name of awesome-zsh-plugins nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE AND THE LIST ENTRIES ARE PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /Writing_Plugins_and_Themes.md: -------------------------------------------------------------------------------- 1 | # Writing Plugins and Themes 2 | 3 | Here are some suggestions to make installing and using your plugin/theme as simple as possible for end users, no matter what ZSH framework (if any) they are using. 4 | 5 | 1. Make using your plugin easier for end users and put the plugin file at the root level of your plugin repository instead of hiding it in a subdirectory. This allows [oh-my-zsh](https://github.com/robbyrussell/oh-my-zsh) users to install it with a simple `git clone git@github.com:you/yourplugin.git` in their `custom/plugins` directory and also lets [Antigen](https://github.com/zsh-users/antigen) and [zgenom](https://github.com/jandamm/zgenom) users to let the framework automatically clone the repository without having to specify a subdirectory path. 6 | 7 | 2. Only put one plugin or theme in a repository. This makes using it a simple `git clone` for [oh-my-zsh](https://github.com/robbyrussell/oh-my-zsh) users, and simpler for other framework users as well - they won't have to specify a subdirectory, just username/reponame. 8 | 9 | 3. Only oh-my-zsh sets the `${ZSH_CUSTOM}` variable. Relying on your plugin being in `${ZSH_CUSTOM}/yourPluginName` will make your plugin not work with anything but oh-my-zsh. The [ZSH Plugin Standard](https://zdharma-continuum.github.io/Zsh-100-Commits-Club/Zsh-Plugin-Standard.html#zero-handling) has sample code to make it easy to find your plugin's home directory in a cross-framework way and won't break when a user inevitably renames your plugin directory. 10 | 11 | 4. Don't assume your plugin will be checked out into a directory with the same name you gave the plugin. This is another case where `$(dirname ${0})` will work and `${ZSH_CUSTOM}/hardcoded-directory-name` will fail miserably. 12 | 13 | 5. Use `yourplugin.plugin.zsh` for the main plugin file. This is what [oh-my-zsh](https://github.com/robbyrussell/oh-my-zsh) looks for. [Antigen](https://github.com/zsh-users/antigen), [zgenom](https://github.com/jandamm/zgenom) and most other ZSH frameworks will also automatically find and load that filename. 14 | 15 | 6. If you’re making a theme, include a screenshot so prospective users can see what it looks like without having to install it. If it relies on Powerline-compatible fonts or Nerdfonts, put that in the readme. 16 | 17 | 7. If your plugin adds any of its subdirectories to the user's `fpath`, make sure those subdirectories only contain function definition files. This allows for frameworks to correctly [zcompile all functions](http://zsh.sourceforge.net/Doc/Release/Functions.html#Autoloading-Functions). Please don't make your plugin add its root directory to the `fpath` - this will cause problems with `zcompile`. 18 | 19 | 8. If your plugin adds aliases or functions that rely on a given program to be installed, check for the program and only add them when it's present. Same if it only works on one OS - check for the OS first. Example: 20 | 21 | ```sh 22 | function has_command() { 23 | which "$@" > /dev/null 2>&1 24 | } 25 | if has_command pbcopy; then 26 | # On macOS, make ^Y yank the selection to the system clipboard. On Linux you can alias pbcopy to `xclip -selection clipboard` or corresponding tool. 27 | fzf_default_opts+=("--bind 'ctrl-y:execute-silent(echo {+} | pbcopy)'") 28 | fi 29 | ``` 30 | 31 | 9. Leave ZSH settings alone. 32 | 33 | - If you're using `setopt` to override the user's existing settings, you _will_ break someone's workflow. If you feel you absolutely must tweak `setopt` settings, make sure there's an easy way to disable your overrides - consider looking for a file named `~/.YOURPLUGIN_disable_SETTINGSNAME`. 34 | - If you're touching the magic ZSH settings variables like `HISTSIZE`, _only do it if the variable is unset_. Wrap it in a `if [[ -z "$VARNAME" ]]; then` block so you don't step on a user's existing settings. 35 | 36 | 10. Don't forget to add a license. A lot of people won't use anything that doesn't have a license. [choosealicense.com](https://choosealicense.com) is a good tool to help you pick one if you don't already have something specific in mind. 37 | 38 | 11. Look at the [ZSH Plugin Standard](https://zdharma-continuum.github.io/Zsh-100-Commits-Club/Zsh-Plugin-Standard.html). It has a lot of good suggestions. 39 | 40 | 12. Submit a PR or add an issue here at [awesome-zsh-plugins](https://github.com/unixorn/awesome-zsh-plugins) so your plugin is easy for users to find :-) 41 | -------------------------------------------------------------------------------- /zsh-plugin-assessor/git-process-output.zsh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | 3 | # Copyright (c) 2018 Sebastian Gniazdowski 4 | # 5 | # This Zsh script is to be fed with Git progress-bar output 6 | # (normally occuring on stderr, so the piping "|&" is needed, 7 | # it redirects both stdout and stderr), induced by --progress 8 | # Git option (for non-terminal stderr, Git skips the progress 9 | # bar and it has to be asked to still generate it). 10 | # 11 | # The script shows animated progress bar instead of the text 12 | # progress data that Git normally shows. 13 | # 14 | # Example: 15 | # git clone --progress https://github.com/... |& git-process-output.zsh 16 | # 17 | # The script can be also used to mute Git's progress bar (keeping 18 | # the error and authentication messages) by passing an option "-q" 19 | # (to the script). 20 | 21 | emulate -LR zsh -o typesetsilent -o extendedglob -o warncreateglobal 22 | 23 | trap "tput cnorm" EXIT 24 | trap "tput cnorm" INT 25 | trap "tput cnorm" TERM 26 | 27 | local first=1 28 | 29 | # Code by leoj3n 30 | timeline() { 31 | local sp='▚▞'; sp="${sp:$2%2:1}" 32 | local bar="$(print -f "%-$2s▓%$(($3-$2))s" "${sp}" "${sp}")" 33 | print -f "%s %s" "${bar// /░}" "" 34 | } 35 | 36 | # $1 - n. of objects 37 | # $2 - packed objects 38 | # $3 - total objects 39 | # $4 - receiving percentage 40 | # $5 - resolving percentage 41 | print_my_line() { 42 | print -nr -- "OBJ: $1, PACKED: $2/$3${${4:#...}:+, RECEIVING: $4%}${${5:#...}:+, RESOLVING: $5%} " 43 | print -n $'\015' 44 | } 45 | 46 | print_my_line_compress() { 47 | print -nr -- "OBJ: $1, PACKED: $2/$3, COMPRESS: $4%${${5:#...}:+, RECEIVING: $5%}${${6:#...}:+, RESOLVING: $6%} " 48 | print -n $'\015' 49 | } 50 | 51 | integer have_1_counting=0 have_2_total=0 have_3_receiving=0 have_4_deltas=0 have_5_compress=0 52 | integer counting_1=0 total_2=0 total_packed_2=0 receiving_3=0 deltas_4=0 compress_5=0 53 | integer loop_count=0 54 | 55 | IFS='' 56 | 57 | tput civis 58 | 59 | { command perl -pe 'BEGIN { $|++; $/ = \1 }; tr/\r/\n/' || \ 60 | gstdbuf -o0 gtr '\r' '\n' || \ 61 | cat } |& \ 62 | while read -r line; do 63 | (( ++ loop_count )) 64 | if [[ "$line" = "Cloning into"* ]]; then 65 | #print; print $line 66 | continue 67 | elif [[ "$line" = (#i)*user*name* || "$line" = (#i)*password* ]]; then 68 | print; print $line 69 | continue 70 | elif [[ "$line" = remote:*~*(Counting|Total|Compressing|Enumerating)* || "$line" = fatal:* ]]; then 71 | print $line 72 | continue 73 | fi 74 | if [[ "$line" = (#b)"remote: Counting objects:"[\ ]#([0-9]##)(*) ]]; then 75 | have_1_counting=1 76 | counting_1="${match[1]}" 77 | fi 78 | if [[ "$line" = (#b)"remote: Enumerating objects:"[\ ]#([0-9]##)(*) ]]; then 79 | have_1_counting=1 80 | counting_1="${match[1]}" 81 | fi 82 | if [[ "$line" = (#b)*"remote: Total"[\ ]#([0-9]##)*"pack-reused"[\ ]#([0-9]##)* ]]; then 83 | have_2_total=1 84 | total_2="${match[1]}" total_packed_2="${match[2]}" 85 | fi 86 | if [[ "$line" = (#b)"Receiving objects:"[\ ]#([0-9]##)%* ]]; then 87 | have_3_receiving=1 88 | receiving_3="${match[1]}" 89 | fi 90 | if [[ "$line" = (#b)"Resolving deltas:"[\ ]#([0-9]##)%* ]]; then 91 | have_4_deltas=1 92 | deltas_4="${match[1]}" 93 | fi 94 | if [[ "$line" = (#b)"remote: Compressing objects:"[\ ]#([0-9]##)"%"(*) ]]; then 95 | have_5_compress=1 96 | compress_5="${match[1]}" 97 | fi 98 | 99 | if (( loop_count >= 2 )); then 100 | integer pr 101 | (( pr = have_4_deltas ? deltas_4 / 10 : ( 102 | have_3_receiving ? receiving_3 / 10 : ( 103 | have_5_compress ? compress_5 / 10 : ( ( ( loop_count - 1 ) / 14 ) % 10 ) + 1 ) ) )) 104 | 105 | [[ "$1" != "-q" ]] &&timeline "" $pr 11 106 | 107 | if (( have_5_compress )); then 108 | [[ "$1" != "-q" ]] && print_my_line_compress "${${${(M)have_1_counting:#1}:+$counting_1}:-...}" \ 109 | "${${${(M)have_2_total:#1}:+$total_packed_2}:-0}" \ 110 | "${${${(M)have_2_total:#1}:+$total_2}:-0}" \ 111 | "${${${(M)have_5_compress:#1}:+$compress_5}:-...}" \ 112 | "${${${(M)have_3_receiving:#1}:+$receiving_3}:-...}" \ 113 | "${${${(M)have_4_deltas:#1}:+$deltas_4}:-...}" 114 | else 115 | [[ "$1" != "-q" ]] && print_my_line "${${${(M)have_1_counting:#1}:+$counting_1}:-...}" \ 116 | "${${${(M)have_2_total:#1}:+$total_packed_2}:-0}" \ 117 | "${${${(M)have_2_total:#1}:+$total_2}:-0}" \ 118 | "${${${(M)have_3_receiving:#1}:+$receiving_3}:-...}" \ 119 | "${${${(M)have_4_deltas:#1}:+$deltas_4}:-...}" 120 | fi 121 | fi 122 | done 123 | 124 | [[ "$1" != "-q" ]] && print 125 | 126 | tput cnorm 127 | 128 | # vim:ft=zsh:et:sw=4:sts=4 129 | -------------------------------------------------------------------------------- /zsh-plugin-assessor/zpa.config: -------------------------------------------------------------------------------- 1 | zsh_control_bin="zsh" # Select the zsh binary that is to 2 | # run the file `zsh-plugin-assessor' 3 | -------------------------------------------------------------------------------- /zsh-plugin-assessor/zsh-plugin-assessor: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Copyright (c) 2018 Sebastian Gniazdowski 4 | # 5 | # This script parses and processes the data in the unixorn/awesone-zsh-plugins 6 | # README.md page. It extracts plugins from the "Plugins" section of the 7 | # document, clones each of them, runs a few git commands to establish facts 8 | # about the plugin and outputs the plugin's overall score. 9 | # 10 | # It then creates a README.md_new file with the scoress visible near each 11 | # plugin's name. 12 | 13 | ## 14 | ## CONFIGURATION, RESTART TO LEAVE /bin/sh 15 | ## 16 | ## Via /bin/sh running, to allow selection via configuration 17 | ## file, of the `zsh' binary that is to run this script 18 | ## 19 | 20 | ZERO="$0" 21 | ZPA_DIR="${ZERO%/*}" 22 | [ "$ZPA_DIR" = "${ZPA_DIR#/}" ] && ZPA_DIR="$PWD/$ZPA_DIR" 23 | 24 | [ "x$ZPA_CONFIG" = "x" ] && { 25 | if [ -f ${XDG_CONFIG_HOME:-$HOME/.config}/zsh-plugin-assessor/zsd.config ]; then 26 | ZPA_CONFIG="${XDG_CONFIG_HOME:-$HOME/.config}/zsh-plugin-assessor/zsd.config" 27 | elif [ -f "${ZPA_DIR}/zpa.config" ]; then 28 | ZPA_CONFIG="${ZPA_DIR}/zpa.config" 29 | elif [ -f /usr/local/share/zsh-plugin-assessor/zpa.config ]; then 30 | ZPA_CONFIG="/usr/local/share/zsh-plugin-assessor/zpa.config" 31 | elif [ -f /usr/share/zsh-plugin-assessor/zpa.config ]; then 32 | ZPA_CONFIG="/usr/share/zsh-plugin-assessor/zpa.config" 33 | elif [ -f /opt/share/zsh-plugin-assessor/zpa.config ]; then 34 | ZPA_CONFIG="/opt/share/zsh-plugin-assessor/zpa.config" 35 | fi 36 | 37 | [ "x$ZPA_CONFIG" != "x" ] && { 38 | export ZPA_CONFIG 39 | . "$ZPA_CONFIG" 40 | } 41 | } || { 42 | [ -n "$ZSH_VERSION" ] && { 43 | setopt extendedglob 44 | if [[ " $* " != *-[^[:blank:]-]#q[^[:blank:]]#[[:blank:]]##* && \ 45 | " $* " != *[[:blank:]]##--quiet[[:blank:]]##* ]] 46 | then 47 | if [[ -z "$ZPA_CONFIG" ]];then 48 | print -r -- "Couldn't find configuration file, will use the default zsh binary" 49 | else 50 | print -r -- "Reading configuration from: ${ZPA_CONFIG/$HOME/~}" 51 | . "$ZPA_CONFIG" 52 | fi 53 | else 54 | [[ -n "$ZPA_CONFIG" ]] && . "$ZPA_CONFIG" 55 | fi 56 | } 57 | } 58 | 59 | [ -z "$zsh_control_bin" ] && zsh_control_bin="zsh" 60 | 61 | if [ -z "$ZSH_VERSION" ]; then 62 | args="\"$0\"" 63 | for arg; do 64 | args="$args \"$arg\"" 65 | done 66 | exec /usr/bin/env "$zsh_control_bin" -f -c "source $args" 67 | fi 68 | 69 | ## 70 | ## START 71 | ## 72 | ## Finally in a Zsh, the Zsh binary possibly selected by zpa.conf 73 | ## 74 | 75 | typeset -gF4 SECONDS=0 76 | 77 | emulate -R zsh -o extendedglob -o typesetsilent -o warncreateglobal 78 | 79 | zmodload zsh/datetime || { print -u2 -r -- "Module zsh/datetime is needed, aborting"; exit 1; } 80 | zmodload zsh/zutil || { print -u2 -r -- "Module zsh/zutil is needed, aborting"; exit 1; } 81 | 82 | local -A opthash 83 | 84 | zparseopts -E -D -A opthash h -help v -verbose q -quiet a -no-ansi n -only-new || \ 85 | { print -r -- "Improper options given, see help (-h/--help)"; return 1; } 86 | 87 | (( ${+opthash[-h]} + ${+opthash[--help]} )) && local OPT_HELP="-h" 88 | (( ${+opthash[-v]} + ${+opthash[--verbose]} )) && local OPT_VERBOSE="-v" 89 | (( ${+opthash[-q]} + ${+opthash[--quiet]} )) && local OPT_QUIET="-q" 90 | (( ${+opthash[-a]} + ${+opthash[--no-ansi]} )) && local OPT_NO_ANSI="-a" 91 | (( ${+opthash[-n]} + ${+opthash[--only-new]} )) && local OPT_ONLY_NEW="-n" 92 | 93 | [[ -z "$OPT_QUIET" ]] && print -r -- "Running under Zsh-binary: \`$zsh_control_bin' (passed to /usr/bin/env), its version: $ZSH_VERSION" 94 | [[ -z "$OPT_QUIET" ]] && print 95 | 96 | [[ -z "$1" || "$1" = (-h|--help) ]] && { 97 | print -r -- "Usage: zsh-plugin-assessor [-h/--help] [-v|--verbose] [-a|--no-ansi] [-n|--only-new] {file.md}" 98 | print -r -- " e.g.: zsh-plugin-assessor --only-new ./README.md" 99 | print 100 | print -r -- "The option -n/--only-new will compute the emoji indicators and add them to the" 101 | print -r -- "README.md document only if there are no emoji indicators already assigned to" 102 | print -r -- "the plugin or theme. This is useful to evaluate only newly added repositories." 103 | print -r -- "It will however still process repositories that are inactive, etc. and do not" 104 | print -r -- "have the emoji-indicators in their normal state." 105 | print 106 | print -r -- "Option -q/--quiet will silence all messages except of stderr error messages." 107 | print -r -- "Option -v/--verbose will add some messages or extend them (e.g. to show git's" 108 | print -r -- "clone progress-bar (processed into an animated gauge)." 109 | exit 0 110 | } 111 | 112 | [[ ! -e "$1" ]] && { 113 | print -u2 -r -- "The input file ($1) doesn't exist, aborting" 114 | exit 1 115 | } 116 | 117 | [[ ! -f "$1" ]] && { 118 | print -u2 -r -- "The input file ($1) isn't a regular file, aborting" 119 | exit 1 120 | } 121 | 122 | [[ ! -r "$1" ]] && { 123 | print -u2 -r -- "The input file ($1) is unreadable, aborting" 124 | exit 1 125 | } 126 | 127 | typeset -g INPUT_FILE_PATH="${${(M)1:#/*}:-$PWD/$1}" INPUT_FILE_CONTENTS="$(<$1)" 128 | typeset -gi NUMBER_OF_PLUGINS=0 NUMBER_OF_THEMES=0 CURRENT_LINE=0 DEBUG_PLUGIN_COUNT_LIMIT=0 LAST_PLUGIN_IDX=0 129 | typeset -ga input_file_lines gathered_plugins plugin_scores 130 | typeset -gA plugin_to_url plugin_to_line_num plugin_to_score plugin_to_emoji_str plugin_no_process 131 | input_file_lines=( "${(@f)INPUT_FILE_CONTENTS}" ) 132 | 133 | [[ -z "$OPT_QUIET" ]] && print -r -- "The input file has ${#input_file_lines} lines" 134 | 135 | # 136 | # Extract plugins 137 | # 138 | 139 | typeset -g LINE IN_PLUGINS_SECTION=0 IN_THEMES_SECTION=0 140 | for LINE in "${input_file_lines[@]}"; do 141 | (( ++ CURRENT_LINE )) 142 | if (( ! IN_PLUGINS_SECTION && ! IN_THEMES_SECTION )); then 143 | [[ "$LINE" = "## Plugins" ]] && { IN_PLUGINS_SECTION=1; [[ -z "$OPT_QUIET" ]] && print -r -- "Processing plugins..."; } 144 | [[ "$LINE" = "## Themes" ]] && { IN_THEMES_SECTION=1; [[ -z "$OPT_QUIET" ]] && print -r -- "Processing themes..."; } 145 | else 146 | if [[ "$LINE" = "##"[[:blank:]]##[a-zA-Z0-9]##* ]]; then 147 | (( IN_PLUGINS_SECTION )) && { 148 | IN_PLUGINS_SECTION=0 149 | LAST_PLUGIN_IDX="${#gathered_plugins}" 150 | [[ -z "$OPT_QUIET" ]] && print -r -- "Found #$NUMBER_OF_PLUGINS plugins" 151 | } 152 | (( IN_THEMES_SECTION )) && { 153 | IN_THEMES_SECTION=0 154 | [[ -z "$OPT_QUIET" ]] && print -r -- "Found #$NUMBER_OF_THEMES themes" 155 | } 156 | [[ "$LINE" = "## Themes" ]] && IN_THEMES_SECTION=1 157 | elif (( IN_PLUGINS_SECTION && DEBUG_PLUGIN_COUNT_LIMIT > 0 && NUMBER_OF_PLUGINS >= DEBUG_PLUGIN_COUNT_LIMIT )); then 158 | IN_PLUGINS_SECTION=0 IN_THEMES_SECTION=0 159 | LAST_PLUGIN_IDX="${#gathered_plugins}" 160 | elif (( IN_THEMES_SECTION && DEBUG_PLUGIN_COUNT_LIMIT > 0 && NUMBER_OF_THEMES >= DEBUG_PLUGIN_COUNT_LIMIT )); then 161 | IN_PLUGINS_SECTION=0 IN_THEMES_SECTION=0 162 | # \[[^\]]##\] - plugin name (## works like + in regex) 163 | # \([^\)]##\) - plugin URL 164 | elif [[ "$LINE" = (#b)\*[[:blank:]]##\[([^\]]##)\]\(([^\)]##)\)[[:blank:]]##* ]]; then 165 | (( IN_PLUGINS_SECTION )) && (( ++ NUMBER_OF_PLUGINS )) 166 | (( IN_THEMES_SECTION )) && (( ++ NUMBER_OF_THEMES )) 167 | gathered_plugins+=( "${match[1]}" ) 168 | plugin_to_url+=( "${match[1]}" "${match[2]}" ) 169 | plugin_to_line_num+=( "${match[1]}" "$CURRENT_LINE" ) 170 | plugin_scores+=( "0000" ) 171 | local plugin_name="${match[1]}" 172 | 173 | # Should skip processing it? This is when --only-new/-n option 174 | # is given and the plugin/theme already has emoji indicators, 175 | # i.e. it was already processed, isn't a new entry. 176 | # 177 | # \[[^\]]##\] - plugin name 178 | # \([^\)]##\) - plugin URL 179 | # [a-z:_[:blank:]]## - emoji string 180 | if [[ -z "$OPT_ONLY_NEW" || "$LINE" != (#b)\*[[:blank:]]##\[([^\]]##)\]\(([^\)]##)\)[[:blank:]]##([a-z0-9:_[:blank:]]##)-[[:blank:]]##* ]]; then 181 | plugin_no_process+=( $plugin_name 0 ) 182 | else 183 | plugin_no_process+=( $plugin_name 1 ) 184 | fi 185 | fi 186 | fi 187 | done 188 | 189 | # 190 | # Prepare working directory (in XDG_CACHE_HOME or ~/.config) 191 | # 192 | 193 | typeset -g WORK_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/zsh-plugin-assessor/clones" 194 | 195 | [[ -z "$OPT_QUIET" ]] && print -r -- "== Removing clones from previous ZPA run... ==" 196 | 197 | builtin cd -q -- "${WORK_DIR:h}" 198 | command rm -rf -- "$WORK_DIR" 199 | command mkdir -p -- "$WORK_DIR" 200 | builtin cd -q -- "$WORK_DIR" 201 | 202 | [[ -z "$OPT_QUIET" ]] && print -r -- "== Working in $WORK_DIR ==" 203 | [[ -z "$OPT_QUIET" ]] && print 204 | 205 | # 206 | # Clone each plugin, establish its score 207 | # 208 | 209 | typeset -g PLUGIN 210 | for PLUGIN in "${gathered_plugins[@]}"; do 211 | typeset -g URL="${plugin_to_url[$PLUGIN]}" 212 | 213 | if (( plugin_no_process[$PLUGIN] )); then 214 | [[ -z "$OPT_QUIET" ]] && print "Skipping ${PLUGIN}...${OPT_VERBOSE:+ (it is not a new, unprocessed plugin)}" 215 | plugin_to_score[$PLUGIN]="0000" 216 | plugin_to_emoji_str[$PLUGIN]="" 217 | continue 218 | fi 219 | if [[ -n "$OPT_VERBOSE" ]]; then 220 | [[ -z "$OPT_QUIET" ]] && print 221 | command git clone --progress "$URL" "$PLUGIN" |& "${ZPA_DIR}/git-process-output.zsh" 222 | else 223 | command git clone --progress "$URL" "$PLUGIN" |& "${ZPA_DIR}/git-process-output.zsh" -q 224 | fi 225 | 226 | # 227 | # The basic score [ABCD] 228 | # 229 | 230 | # Number of commits in master 231 | typeset -g DATA_COMMIT_COUNT=$(command git -C "$PLUGIN" rev-list --count master 2>/dev/null) 232 | # Time of last commit in master 233 | typeset -g DATA_LAST_COMMIT_DATE=$(command git -C "$PLUGIN" log --max-count=1 --pretty=format:%ct master 2>/dev/null) 234 | # As above, for ^master 235 | typeset -g DATA_COMMIT_COUNT_ALL=$(command git -C "$PLUGIN" rev-list --all --count '^master' 2>/dev/null) 236 | # As above, for ^master 237 | typeset -g DATA_LAST_COMMIT_DATE_BRANCHES=$(command git -C "$PLUGIN" log --max-count=1 --pretty=format:%ct --all '^master' 2>/dev/null) 238 | 239 | # 240 | # Progress detection 241 | # 242 | 243 | integer has_walking_pace=0 has_running_pace=0 244 | 245 | typeset -g DATA_CURRENT_MONTH_COMMIT_COUNT=$(command git -C "$PLUGIN" rev-list --after='30 days ago' --count master 2>/dev/null) 246 | typeset -g DATA_CURRENT_1_MONTH_COMMIT_COUNT=$(command git -C "$PLUGIN" rev-list --after='60 days ago' --before='30 days ago' --count master 2>/dev/null) 247 | typeset -g DATA_CURRENT_2_MONTH_COMMIT_COUNT=$(command git -C "$PLUGIN" rev-list --after='90 days ago' --before='60 days ago' --count master 2>/dev/null) 248 | typeset -g DATA_3_LAST_MONTHS_COMMIT_COUNT=$(command git -C "$PLUGIN" rev-list --after='90 days ago' --count master 2>/dev/null) 249 | 250 | if (( DATA_CURRENT_MONTH_COMMIT_COUNT > 0 && \ 251 | DATA_CURRENT_1_MONTH_COMMIT_COUNT > 0 && \ 252 | DATA_CURRENT_2_MONTH_COMMIT_COUNT > 0 )) 253 | then 254 | has_walking_pace=1 255 | if (( DATA_CURRENT_MONTH_COMMIT_COUNT >= 3 && \ 256 | DATA_CURRENT_1_MONTH_COMMIT_COUNT >= 3 && \ 257 | DATA_CURRENT_2_MONTH_COMMIT_COUNT >= 3 )) 258 | then 259 | has_running_pace=1 260 | elif (( DATA_3_LAST_MONTHS_COMMIT_COUNT >= 10 )); then 261 | has_running_pace=1 262 | fi 263 | elif (( DATA_3_LAST_MONTHS_COMMIT_COUNT >= 5 )); then 264 | has_walking_pace=1 265 | if (( DATA_3_LAST_MONTHS_COMMIT_COUNT >= 10 )); then 266 | has_running_pace=1 267 | fi 268 | fi 269 | 270 | # 271 | # Attention detection - the basic score [ABCD] 272 | # 273 | 274 | # The score is e.g. 1121 for 1-50-commits, 1-active, 2-100-commits, 1-active 275 | integer score_activity=0 score_commit_count=0 score_activity_branches=0 score_commit_count_branches=0 276 | 277 | # Master 278 | (( DATA_COMMIT_COUNT >= 50 )) && score_commit_count=1 279 | (( DATA_COMMIT_COUNT >= 100 )) && score_commit_count=2 280 | if (( EPOCHSECONDS - ${DATA_LAST_COMMIT_DATE:-0} <= 6*30.5*24*60*60 )); then 281 | score_activity=1 282 | fi 283 | if (( EPOCHSECONDS - ${DATA_LAST_COMMIT_DATE:-0} <= 3*30.5*24*60*60 )); then 284 | score_activity=2 285 | fi 286 | 287 | # Branches-only 288 | (( DATA_COMMIT_COUNT_ALL >= 50 )) && score_commit_count_branches=1 289 | (( DATA_COMMIT_COUNT_ALL >= 100 )) && score_commit_count_branches=2 290 | if (( EPOCHSECONDS - ${DATA_LAST_COMMIT_DATE_BRANCHES:-0} <= 6*30.5*24*60*60 )); then 291 | score_activity_branches=1 292 | fi 293 | if (( EPOCHSECONDS - ${DATA_LAST_COMMIT_DATE_BRANCHES:-0} <= 3*30.5*24*60*60 )); then 294 | score_activity_branches=2 295 | fi 296 | 297 | integer score=score_commit_count*1000+score_activity*100+score_commit_count_branches*10+score_activity_branches 298 | 299 | # 300 | # Attention detection - emoji 301 | # 302 | 303 | integer attention_rank=0 304 | 305 | # Time of last commit in master 306 | if (( score_activity == 2 )); then 307 | typeset -g DATA_THIS_MONTH_COMMIT_COUNT=$DATA_CURRENT_MONTH_COMMIT_COUNT 308 | typeset -g DATA_PREVIOUS_MONTH_COMMIT_COUNT=$DATA_CURRENT_1_MONTH_COMMIT_COUNT 309 | if (( DATA_THIS_MONTH_COMMIT_COUNT > 0 && DATA_PREVIOUS_MONTH_COMMIT_COUNT > 0 )); then 310 | attention_rank=3 311 | else 312 | attention_rank=2 313 | fi 314 | else 315 | attention_rank=score_activity 316 | fi 317 | 318 | # 319 | # Research & development detection 320 | # 321 | 322 | integer has_r_and_d=0 r_and_d_active=0 323 | if (( score_commit_count_branches > 0 )); then 324 | has_r_and_d=1 325 | fi 326 | typeset -g DATA_LAST_4_MONTHS_COMMIT_COUNT=$(command git -C "$PLUGIN" rev-list --after='120 days ago' --all --count '^master' 2>/dev/null) 327 | if (( DATA_LAST_4_MONTHS_COMMIT_COUNT >= 3 )); then 328 | r_and_d_active=1 329 | fi 330 | 331 | # 332 | # Time and work investment detection 333 | # 334 | 335 | integer which_place_medal=0 336 | if (( DATA_COMMIT_COUNT >= 50 )); then 337 | which_place_medal=2 338 | fi 339 | if (( DATA_COMMIT_COUNT >= 100 )); then 340 | which_place_medal=1 341 | fi 342 | 343 | # 344 | # Construct emoji string 345 | # 346 | 347 | local emoji_string="" 348 | 349 | # 1. Commits-medal 350 | if (( which_place_medal >= 2 )); then 351 | emoji_string+=":2nd_place_medal: " 352 | elif (( which_place_medal == 1 )); then 353 | emoji_string+=":1st_place_medal: " 354 | fi 355 | 356 | # 2. Progress pace 357 | if (( has_running_pace )); then 358 | emoji_string+=":running_man: " 359 | elif (( has_walking_pace )); then 360 | emoji_string+=":walking_man: " 361 | fi 362 | 363 | # 3. Current attention 364 | if (( attention_rank >= 3 )); then 365 | emoji_string+=":alarm_clock: " 366 | elif (( attention_rank == 2 )); then 367 | emoji_string+=":hourglass_flowing_sand: " 368 | elif (( attention_rank == 1 )); then 369 | emoji_string+=":hourglass: " 370 | elif (( attention_rank == 0 )); then 371 | #emoji_string+=":zzz: " 372 | : 373 | fi 374 | 375 | # 4. Research and development 376 | if (( has_r_and_d )); then 377 | emoji_string+=":briefcase: " 378 | if (( r_and_d_active )); then 379 | emoji_string+=":chart_with_upwards_trend: " 380 | fi 381 | fi 382 | 383 | [[ -z "$OPT_QUIET" ]] && print -r -- "$PLUGIN: ${(l:4::0:)score}, emoji: $emoji_string" 384 | 385 | plugin_to_score[$PLUGIN]="${(l:4::0:)score}" 386 | plugin_to_emoji_str[$PLUGIN]="${emoji_string%%[[:blank:]]##}" 387 | done 388 | 389 | # 390 | # Create new README.md with the score 391 | # occuring near each plugin's name 392 | # 393 | 394 | typeset -g OUTPUT_FILE_PATH="${INPUT_FILE_PATH}"_new 395 | 396 | # Output all lines preceding "Plugins"-section body 397 | integer top_block_last_line_num=$(( ${plugin_to_line_num[${gathered_plugins[1]}]} - 1 )) 398 | print -r -- "${(F)input_file_lines[1,top_block_last_line_num]}" >! "$OUTPUT_FILE_PATH" 399 | 400 | integer plugin_idx=0 line_num 401 | 402 | for PLUGIN in "${gathered_plugins[@]}"; do 403 | plugin_idx+=1 404 | 405 | # Plugins ending? 406 | if (( plugin_idx == LAST_PLUGIN_IDX+1 )); then 407 | # Output unchanged middle document part 408 | print -r -- "${(F)input_file_lines[line_num+1,${plugin_to_line_num[$PLUGIN]}-1]}" >>! "$OUTPUT_FILE_PATH" 409 | fi 410 | 411 | line_num="${plugin_to_line_num[$PLUGIN]}" start_pos=0 412 | LINE="${input_file_lines[line_num]}" 413 | 414 | # Output a verbatim copy of previous line 415 | if (( plugin_no_process[$PLUGIN] )); then 416 | print -r -- "$LINE" >>! "$OUTPUT_FILE_PATH" 417 | continue 418 | fi 419 | 420 | # \[[^\]]##\] - plugin name 421 | # \([^\)]##\) - plugin URL 422 | # [a-z:_[:blank:]]## - emoji string 423 | if [[ "$LINE" = (#b)\*[[:blank:]]##\[([^\]]##)\]\(([^\)]##)\)[[:blank:]]##([a-z0-9:_[:blank:]]##)-[[:blank:]]##* ]]; then 424 | # Remove previous e.g. *[0001]* 425 | LINE[${mbegin[3]},${mend[3]}]="" 426 | start_pos=$(( mbegin[3] -1 )) 427 | elif [[ "$LINE" = (#b)\*[[:blank:]]##\[([^\]]##)\]\(([^\)]##)\)[[:blank:]]##* ]]; then 428 | start_pos=$(( mend[2] + 2 )) 429 | fi 430 | 431 | LINE[start_pos]=" ${plugin_to_emoji_str[$PLUGIN]}${plugin_to_emoji_str[$PLUGIN]:+ }" 432 | print -r -- "$LINE" >>! "$OUTPUT_FILE_PATH" 433 | done 434 | 435 | # Output unchanged trailing document part 436 | print -r -- "${(F)input_file_lines[line_num+1,-1]}" >>! "$OUTPUT_FILE_PATH" 437 | 438 | float -F2 elapsed_time=$(( SECONDS / 60.0 )) 439 | [[ -z "$OPT_QUIET" ]] && print -r -- "The processing took $elapsed_time minutes" 440 | # vim:ft=zsh:et:sw=4:sts=4 441 | --------------------------------------------------------------------------------