├── tests ├── repos │ └── compdef │ │ ├── plugin.zsh │ │ └── _compd_file.sh ├── configs │ ├── Basic.hs │ └── Compdef.hs ├── compdef.t ├── basic.t └── .zshenv ├── stack.yaml ├── .gitignore ├── .travis.yml ├── Makefile ├── antigen-hs.cabal ├── LICENSE.txt ├── init.zsh ├── README.md └── Antigen.hs /tests/repos/compdef/plugin.zsh: -------------------------------------------------------------------------------- 1 | set_some_var=123 2 | -------------------------------------------------------------------------------- /tests/repos/compdef/_compd_file.sh: -------------------------------------------------------------------------------- 1 | #compdef blah 2 | 3 | bleh 4 | -------------------------------------------------------------------------------- /stack.yaml: -------------------------------------------------------------------------------- 1 | flags: {} 2 | packages: 3 | - '.' 4 | extra-deps: [] 5 | resolver: lts-3.4 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | cabal-dev 3 | *.o 4 | *.hi 5 | *.chi 6 | *.chs.h 7 | .virtualenv 8 | .hsenv 9 | .cabal-sandbox/ 10 | cabal.sandbox.config 11 | cabal.config 12 | /.stack-work/ 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: haskell 3 | ghc: 4 | - 7.8 5 | addons: 6 | apt: 7 | packages: 8 | - zsh 9 | - python-pip 10 | before_install: 11 | - pip install --user cram 12 | - cabal install --user text 13 | - zsh --version 14 | script: "make test" 15 | -------------------------------------------------------------------------------- /tests/configs/Basic.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module Basic where 3 | 4 | import Antigen 5 | 6 | bundles = 7 | [ bundle "Tarrasch/zsh-bd" 8 | , bundle "Tarrasch/zsh-mcd" 9 | ] 10 | 11 | config = defaultConfig { plugins = bundles } 12 | 13 | main :: IO () 14 | main = antigen config 15 | -------------------------------------------------------------------------------- /tests/compdef.t: -------------------------------------------------------------------------------- 1 | A test to see if we register #compdef marks (setting the fpath) 2 | 3 | $ compile-and-source "$TESTDIR/configs/Compdef.hs" >& /dev/null 4 | $ [[ -f "$ANTIGEN_HS_OUT/antigen-hs.zsh" ]] || (echo 'File not created! :(' && exit 1) 5 | $ echo $set_some_var 6 | 123 7 | $ echo $fpath[-1] 8 | /tmp/antigen-hs/tests/repos/compdef/etc 9 | -------------------------------------------------------------------------------- /tests/configs/Compdef.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module Compdef where 3 | 4 | import Antigen 5 | 6 | bundles = 7 | [ (local "/tmp/antigen-hs/tests/repos/compdef") { 8 | sourcingStrategy = antigenSourcingStrategy, 9 | fpathLocations = ["etc"] 10 | } 11 | ] 12 | 13 | config = defaultConfig { plugins = bundles } 14 | 15 | main :: IO () 16 | main = antigen config 17 | -------------------------------------------------------------------------------- /tests/basic.t: -------------------------------------------------------------------------------- 1 | A small test of the main functionality 2 | 3 | $ compile-and-source "$TESTDIR/configs/Basic.hs" >& /dev/null 4 | $ [[ -f "$ANTIGEN_HS_OUT/antigen-hs.zsh" ]] || (echo 'File not created! :(' && exit 1) 5 | $ mcd a/b/c/d 6 | $ bd b 7 | $ ls 8 | c 9 | $ echo $fpath[-1] 10 | /tmp/cramtests-??????/basic.t/antigen-hs-out/repos/https-COLON--SLASH--SLASH-github.com-SLASH-Tarrasch-SLASH-zsh-mcd (glob) 11 | -------------------------------------------------------------------------------- /tests/.zshenv: -------------------------------------------------------------------------------- 1 | export ANTIGEN_HS_HOME="$TESTDIR/.." 2 | export ANTIGEN_HS_OUT="$PWD/antigen-hs-out" 3 | 4 | # Copy over repos 5 | rm -rf /tmp/antigen-hs/tests/ 6 | mkdir -p /tmp/antigen-hs/tests/ 7 | cp -r -t /tmp/antigen-hs/tests/ $TESTDIR/repos 8 | 9 | # First time we source to define antigen-hs-compile 10 | # We pipe to /dev/null to mute the advice being echoed 11 | yes N | source "$TESTDIR/../init.zsh" > /dev/null 12 | 13 | compile-and-source () { 14 | rm -f $ANTIGEN_HS_OUT/antigen-hs.zsh 15 | export ANTIGEN_HS_MY="$1" 16 | _antigen-hs-compile 17 | # Now we source to source the file antigen-hs-compile created 18 | source "$TESTDIR/../init.zsh" 19 | } 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # I took the file from here: 2 | # https://github.com/Tarrasch/zsh-autoenv/blob/58409ac/Makefile 3 | .PHONY: itest test 4 | 5 | test: 6 | ZDOTDIR="${PWD}/tests" cram --shell=zsh -v tests 7 | 8 | itest: 9 | ZDOTDIR="${PWD}/tests" cram -i --shell=zsh tests 10 | 11 | # Define targets for test files, with relative and abolute path. 12 | # Use verbose output, which is useful with Vim's 'errorformat'. 13 | TESTS:=$(wildcard tests/*.t) 14 | 15 | uniq = $(if $1,$(firstword $1) $(call uniq,$(filter-out $(firstword $1),$1))) 16 | _TESTS_REL_AND_ABS:=$(call uniq,$(abspath $(TESTS)) $(TESTS)) 17 | $(_TESTS_REL_AND_ABS): 18 | ZDOTDIR="${PWD}/tests" cram --shell=zsh -v $@ 19 | .PHONY: $(_TESTS_REL_AND_ABS) 20 | -------------------------------------------------------------------------------- /antigen-hs.cabal: -------------------------------------------------------------------------------- 1 | name: antigen-hs 2 | version: 0.1.0.0 3 | synopsis: A fast zsh plugin manager 4 | description: Please see README.md 5 | homepage: https://github.com/Tarrasch/antigen-hs#readme 6 | license: MIT 7 | license-file: LICENSE.txt 8 | author: Arash Rouhani 9 | maintainer: arash@spotify.com 10 | copyright: 2014 Arash Rouhani 11 | category: Tools 12 | build-type: Simple 13 | cabal-version: >=1.10 14 | 15 | library 16 | exposed-modules: Antigen 17 | default-language: Haskell2010 18 | build-depends: base >= 4.6 && < 5 19 | , text -any 20 | , directory -any 21 | , filepath -any 22 | , process >= 1.2 23 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Arash Rouhani 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /init.zsh: -------------------------------------------------------------------------------- 1 | # See https://github.com/Tarrasch/antigen-hs#readme 2 | 3 | ANTIGEN_HS_HOME=${${0:A}:h} 4 | 5 | if [[ -z "$ANTIGEN_HS_OUT" ]] ; then 6 | ANTIGEN_HS_OUT="$HOME/.antigen-hs" 7 | fi 8 | 9 | if [[ -z "$ANTIGEN_HS_MY" ]] ; then 10 | ANTIGEN_HS_MY="$ANTIGEN_HS_HOME/../MyAntigen.hs" 11 | fi 12 | 13 | # usage: _antigen-hs-ask "question" action 14 | # or _antigen-hs-ask "question" action1 action2 15 | _antigen-hs-ask () { 16 | while true; do 17 | local REQUEST="read" 18 | 19 | if ! [[ -t 0 ]]; then 20 | REQUEST=$REQUEST" -u 0" 21 | fi 22 | REQUEST=$REQUEST' -sk 1 "RESPONSE?$fg[green]$1 $fg_bold[green][Y/n] "$reset_color' 23 | 24 | local RESPONSE=$(eval $REQUEST ; echo $RESPONSE) 25 | 26 | case $RESPONSE in 27 | [yY] | "" ) echo $fg[yellow]'Yes'$reset_color 28 | eval $2 29 | break 30 | ;; 31 | [nN] ) echo $fg[blue]'No'$reset_color 32 | if [[ $# == 3 ]]; then 33 | eval $3 34 | fi 35 | break 36 | ;; 37 | * ) echo $fg[red]$RESPONSE$reset_color 38 | echo $fg[red]"Please answer yes or no."$reset_color 39 | ;; 40 | esac 41 | done 42 | } 43 | 44 | _antigen-hs-sandbox () { 45 | _ANTIGEN_HS_SANDBOX=$1 46 | 47 | eval "${@:3}" 2>&1 48 | local ANTIGEN_HS_SANDBOX_RESULT=$? 49 | return "$ANTIGEN_HS_SANDBOX_RESULT" 50 | } 51 | 52 | _antigen-hs-sandbox-stack () { 53 | _antigen-hs-sandbox "stack" .stack-work "stack setup && stack build" 54 | } 55 | 56 | _antigen-hs-sandbox-cabal () { 57 | _antigen-hs-sandbox "cabal" .cabal-sandbox "cabal sandbox init && _antigen-hs-cabal" 58 | } 59 | 60 | _antigen-hs-sandbox-cabal-check () { 61 | if (( $+commands[cabal] )) ; then 62 | echo $fg[green]"Cabal executable found and will be used."$reset_color 63 | _antigen-hs-sandbox-cabal 64 | else 65 | echo $fg[red]'Executable for cabal not found. Install it or check your $PATH. Skip setup.'$reset_color 66 | return 1 67 | fi 68 | } 69 | 70 | _antigen-hs-cabal () { 71 | cabal update 72 | cabal install --only-dependencies 73 | } 74 | 75 | _antigen-hs-cabal-global () { 76 | _ANTIGEN_HS_SANDBOX="empty" 77 | _antigen-hs-cabal 78 | } 79 | 80 | _antigen-hs-init-sandbox () { 81 | if (( $+commands[stack] )) ; then 82 | _antigen-hs-ask "Stack executable found. Use it for sandbox?" _antigen-hs-sandbox-stack _antigen-hs-sandbox-cabal-check 83 | else 84 | _antigen-hs-sandbox-cabal-check 85 | fi 86 | } 87 | 88 | _antigen-hs-repeat () { 89 | if ! eval $1 ; then 90 | _antigen-hs-ask "$2 finish without success. Would you like try again?" "$0 $*" 91 | fi 92 | } 93 | 94 | _antigen-hs-init () { 95 | _antigen-hs-ask "Use sandbox for haskell dependencies?" _antigen-hs-init-sandbox _antigen-hs-cabal-global 96 | } 97 | 98 | antigen-hs-setup () { 99 | _antigen-hs-repeat _antigen-hs-init "Setup" 100 | _antigen-hs-repeat _antigen-hs-compile "Compilation" 101 | } 102 | 103 | _antigen-hs-source () { 104 | local FILE_TO_SOURCE="$ANTIGEN_HS_OUT/antigen-hs.zsh" 105 | 106 | if [[ -f $FILE_TO_SOURCE ]] ; then 107 | source $FILE_TO_SOURCE 108 | else 109 | echo $fg[red]"Didn't find file $FILE_TO_SOURCE"$reset_color 110 | return 1 111 | fi 112 | } 113 | 114 | _antigen-hs-init-source () { 115 | _antigen-hs-source 116 | local ANTIGEN_HS_SOURCE_RESULT=$? 117 | 118 | if [[ "$ANTIGEN_HS_SOURCE_RESULT" == 0 ]] ; then 119 | if [[ -z $_ANTIGEN_HS_SANDBOX ]]; then 120 | 121 | if [[ -d "$ANTIGEN_HS_HOME"/.stack-work ]] ; then 122 | _ANTIGEN_HS_SANDBOX="stack" 123 | elif [[ -d "$ANTIGEN_HS_HOME"/.cabal-sandbox ]] ; then 124 | _ANTIGEN_HS_SANDBOX="cabal" 125 | else 126 | _ANTIGEN_HS_SANDBOX="empty" 127 | fi 128 | 129 | fi 130 | else 131 | return "$ANTIGEN_HS_SOURCE_RESULT" 132 | fi 133 | } 134 | 135 | _antigen-hs-compile () { 136 | local ANTIGEN_HS_COMPILE_CMD='runghc -i"$ANTIGEN_HS_HOME/" -- "$ANTIGEN_HS_MY"' 137 | 138 | case $_ANTIGEN_HS_SANDBOX in 139 | "stack" ) 140 | local ANTIGEN_HS_COMPILE_CMD='STACK_YAML="$ANTIGEN_HS_HOME"/stack.yaml stack exec -- '"$ANTIGEN_HS_COMPILE_CMD" 141 | ;; 142 | "cabal" ) 143 | local ANTIGEN_HS_COMPILE_CMD='CABAL_SANDBOX_CONFIG="$ANTIGEN_HS_HOME"/cabal.sandbox.config cabal exec -- '"$ANTIGEN_HS_COMPILE_CMD" 144 | ;; 145 | * ) 146 | ;; 147 | esac 148 | eval "$ANTIGEN_HS_COMPILE_CMD" 149 | local ANTIGEN_HS_COMPILE_RESULT=$? 150 | 151 | if [[ "$ANTIGEN_HS_COMPILE_RESULT" == 0 ]] ; then 152 | _antigen-hs-source 153 | else 154 | return "$ANTIGEN_HS_COMPILE_RESULT" 155 | fi 156 | } 157 | 158 | () { 159 | if ! _antigen-hs-init-source ; then 160 | ( cd $ANTIGEN_HS_HOME; 161 | autoload -U colors && colors 162 | _antigen-hs-ask "Try to setup?" antigen-hs-setup 163 | ) 164 | fi 165 | } 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NOTE of better alternatives as of 2024 2 | 3 | 2024 update: I still would never recommend [oh-my-zsh] or [antigen]. But this 4 | repo, antigen-hs, has also a big share of problems and I would not recommend 5 | anyone to use it either. You still may of course, especially if you like 6 | Haskell. :) 7 | 8 | Instead, I would consider using [zplug](https://github.com/zplug/zplug) which I 9 | use in [my own dotfiles](https://github.com/Tarrasch/dotfiles/tree/master/zsh). 10 | 11 | # REST OF README since 2016 12 | 13 | [![Build Status](https://travis-ci.org/Tarrasch/antigen-hs.svg?branch=master)](https://travis-ci.org/Tarrasch/antigen-hs) 14 | 15 | Once a zsh user realizes that [oh-my-zsh] and [antigen] actually is bloatware, 16 | they will rejoice in antigen-hs. Here's a quote from one content user. 17 | 18 | > Great work on antigen-hs, you just saved me more than a second *each time* I 19 | > split a tmux pane. Probably a month or two in some longer timespan. Never 20 | > thought about how much time oh-my-zsh took to run its stuff. 21 | > - [Mariano Casco](https://github.com/mdsn) 22 | 23 | # antigen-hs 24 | 25 | A replacement for [antigen] which has a low overhead when starting up the 26 | shell. I created this plugin because I realized that antigen were the main 27 | cause of slow zsh startup times. After the switch, I went from a loading time 28 | of 1.00 seconds to less than 0.10 seconds (wall clock). 29 | 30 | The performance improvement is achieved by *generating* a shell script 31 | consisting of commands to source your plugin scripts. The script contains 32 | full absolute paths of your plugins scripts. In antigen, the paths are 33 | calculated every time a new shell is opened. 34 | 35 | ## Installing 36 | 37 | Since this plugin is not entirely written in zsh (unlike antigen), it has a few 38 | more installation steps. In addition to this README, check out this [diff] to 39 | my dotfiles repository where I replace antigen with antigen-hs. 40 | 41 | ### Haskell and packages 42 | 43 | Since this plugin is written in Haskell, you have to download it: 44 | 45 | - installation through `cabal`: 46 | 47 | $ sudo apt-get install ghc cabal-install 48 | 49 | And if you won't use `cabal sandbox`: 50 | 51 | $ cabal update 52 | $ cabal install base text directory filepath process 53 | 54 | - installation through `stack`: 55 | 56 | Follow [Stack installation guide](https://github.com/commercialhaskell/stack/wiki/Downloads). 57 | 58 | ### Clone and source 59 | 60 | This plugin assumes that you put it in `~/.zsh/antigen-hs/`: 61 | 62 | git clone https://github.com/Tarrasch/antigen-hs.git ~/.zsh/antigen-hs/ 63 | 64 | ### Create plugins file 65 | 66 | touch ~/.zsh/MyAntigen.hs 67 | $EDITOR ~/.zsh/MyAntigen.hs 68 | 69 | And paste this example content 70 | 71 | ```haskell 72 | {-# LANGUAGE OverloadedStrings #-} 73 | module MyAntigen where 74 | 75 | import Antigen ( 76 | -- Rudimentary imports 77 | AntigenConfig (..) 78 | , defaultConfig 79 | , bundle 80 | , antigen 81 | -- If you want to source a bit trickier plugins 82 | , ZshPlugin (..) 83 | , antigenSourcingStrategy 84 | , filePathsSourcingStrategy 85 | ) 86 | 87 | bundles = 88 | [ bundle "Tarrasch/zsh-functional" 89 | , bundle "Tarrasch/zsh-bd" 90 | , bundle "zsh-users/zsh-syntax-highlighting" 91 | , (bundle "zsh-users/zsh-history-substring-search") { sourcingStrategy = antigenSourcingStrategy } 92 | 93 | -- If you use a plugin that doesn't have a *.plugin.zsh file. You can set a 94 | -- more liberal sourcing strategy. 95 | -- 96 | -- , (bundle "some/stupid-plugin") { sourcingStrategy = antigenSourcingStrategy } 97 | 98 | -- If you use a plugin that has sub-plugins. You can specify that as well 99 | -- 100 | -- NOTE: If you want to use oh-my-zsh for real (please don't), you still need 101 | -- to set the $ZSH env var manually. 102 | -- , (bundle "robbyrussell/oh-my-zsh") 103 | -- { sourcingLocations = [ "plugins/wd" 104 | -- , "plugins/colorize"] } 105 | 106 | -- Sourcing a list of files 107 | -- , (bundle "alfredodeza/zsh-plugins") 108 | -- { sourcingStrategy = filePathsSourcingStrategy 109 | -- [ "vi/zle_vi_visual.zsh" 110 | -- , "pytest/pytest.plugin.zsh" 111 | -- ] } 112 | 113 | -- Alternatively, this way will give you the same result 114 | -- , (bundle "alfredodeza/zsh-plugins") 115 | -- { sourcingStrategy = antigenSourcingStrategy 116 | -- , sourcingLocations = [ "vi" 117 | -- , "pytest" 118 | -- ] } 119 | 120 | -- vvv Add your plugins here vvv 121 | ] 122 | 123 | config = defaultConfig { plugins = bundles } 124 | 125 | main :: IO () 126 | main = antigen config 127 | ``` 128 | 129 | Now edit this file accordingly by adding your own plugins. Then you're done! 130 | You can get some inspiration from the author's 131 | [~/.zsh/MyAntigen.hs](https://github.com/Tarrasch/dotfiles/blob/master/zsh/MyAntigen.hs). 132 | 133 | Finally, source it from you zshrc: 134 | 135 | $ echo 'source ~/.zsh/antigen-hs/init.zsh' | tee -a ~/.zshrc | env zsh 136 | 137 | And let withard to help you setup and recompile your configuration: 138 | 139 | Didn't find file ~/.antigen-hs/antigen-hs.zsh 140 | Try to setup? [Y/n] Yes 141 | Use sandbox for haskell dependencies? [Y/n] Yes 142 | Stack executable found. Use it for sandbox? [Y/n] Yes 143 | 144 | ## Usage 145 | 146 | Each time you update `MyAntigen.hs` you have to run `antigen-hs-setup` 147 | 148 | ## Limitations 149 | 150 | The original [antigen] plugin does way more than it should (just look at its bloated 151 | command list). This plugin tries to be minimal, if you are enough experienced 152 | with computers to use a zsh plugin manager, you can delete and manage the 153 | repositories in `~/.antigen-hs/repos` manually yourself! 154 | 155 | [antigen]: https://github.com/zsh-users/antigen 156 | [oh-my-zsh]: https://github.com/robbyrussell/oh-my-zsh 157 | [diff]: https://github.com/Tarrasch/dotfiles/commit/00c3b34c1e1e13d9b0f634611e5bdb5e42211b22 158 | 159 | ## Configuration 160 | 161 | The following variables alter the behavior of antigen-hs, and must be set before 162 | sourcing init.zsh: 163 | 164 | ANTIGEN_HS_HOME # This is only set automatically, and is set to the location 165 | # of the antigen-hs repository (which by default is 166 | # $HOME/.zsh/antigen-hs) 167 | ANTIGEN_HS_OUT # Output directory for cloned repositories and generated 168 | # scripts, defaults to $HOME/.antigen-hs. 169 | ANTIGEN_HS_MY # Your configuration file, defaults to 170 | # $ANTIGEN_HS_HOME/../MyAntigen.hs 171 | 172 | 173 | ## FAQ 174 | 175 | **Is Prezto or oh-my-zsh supported in some way?** No, if you load them they 176 | will be treated like any other plugin. Remember that those two basically are 177 | just bundles of plugins, I recommend against using them because one should load 178 | the individual plugins through a plugin manager like antigen or antigen-hs. 179 | If there's anything inside those plugins that you want, just create a seperate 180 | repository for it [like I did][command-not-found]. 181 | 182 | **Why do I get `No *.plugin.zsh file` error?** Because all zsh plugins 183 | should have that file. I want to enforce it so that people start following this convention. 184 | The easiest way is to just 185 | [fork the plugin you want](https://github.com/Tarrasch/pure/) and/or 186 | [make them change it](https://github.com/sindresorhus/pure/pull/73) 187 | If you're interested in some discussion about this, take a look at 188 | and 189 | . 190 | 191 | **What is the relationship of antigen-hs with antigen?** antigen-hs can achieve 192 | 100% of antigen if the user is just willing to spend some time doing things 193 | right (like not using plugin bundles like oh-my-zsh). In reward they don't need 194 | to use software that is bloated. To be fair, many (including my good github 195 | friend @sharat87) might want to say that antigen is more featureful/convenient, 196 | not bloated. I'll let you choose side yourself. :) 197 | 198 | **What about the antigen commands like `theme` and others?** I do not found 199 | them useful or stable so I rather not have them. I rather rely on my own zsh fu 200 | when it comes to chores like updating the repositories (nuking the 201 | `~/.antigen-hs` folder) than relying on some shaky commands of antigen. 202 | 203 | **Can I only source GitHub repositories?** At the moment yes, patches are very welcome! :) 204 | 205 | **Where are some more plugins?** [awesome-zsh-plugins](https://github.com/unixorn/awesome-zsh-plugins) 206 | is a list of zsh plugins that you may find helpful. 207 | 208 | **Do it use sandbox for haskell dependencies?** 209 | Yes, it support installation by `stack` or in `cabal sandbox` as an option. 210 | 211 | **Why not write it in pure zsh?** 212 | [Trust](https://github.com/Tarrasch/zsh-bd) 213 | [me](https://github.com/Tarrasch/zsh-autoenv), [it](https://github.com/Tarrasch/zsh-mcd) 214 | [is](https://github.com/Tarrasch/zsh-colors) 215 | [a](https://github.com/Tarrasch/zsh-i-know) 216 | [horrible](https://github.com/Tarrasch/zsh-command-not-found) 217 | [language](https://github.com/Tarrasch/zsh-functional). 218 | 219 | **Why Haskell?** I love it. 220 | 221 | [command-not-found]: https://github.com/Tarrasch/zsh-command-not-found 222 | -------------------------------------------------------------------------------- /Antigen.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | {-# LANGUAGE NamedFieldPuns #-} 3 | {-# LANGUAGE OverloadedStrings #-} 4 | module Antigen 5 | ( antigen 6 | , AntigenConfig(..) 7 | , defaultConfig 8 | 9 | , ZshPlugin(..) 10 | , RepoStorage(..) 11 | , SourcingStrategy 12 | , defaultZshPlugin 13 | 14 | , bundle 15 | , local 16 | 17 | , strictSourcingStrategy 18 | , antigenSourcingStrategy 19 | , filePathsSourcingStrategy 20 | ) where 21 | 22 | 23 | #if __GLASGOW_HASKELL__ < 709 24 | import Control.Applicative 25 | #endif 26 | 27 | import Control.Exception (bracket_) 28 | import Control.Monad 29 | import Data.List (isSuffixOf) 30 | import Data.Maybe (fromMaybe) 31 | import Data.Monoid ((<>)) 32 | import Data.Text (Text) 33 | import System.Directory (createDirectoryIfMissing, doesDirectoryExist, 34 | getCurrentDirectory, getDirectoryContents, 35 | getHomeDirectory, setCurrentDirectory) 36 | import System.Environment (lookupEnv) 37 | import System.Exit (exitFailure) 38 | import System.FilePath (isRelative, normalise, ()) 39 | 40 | import qualified Data.Text as Text 41 | import qualified Data.Text.IO as Text 42 | import qualified System.Process as Proc 43 | 44 | -- | Configuration of Antigen. 45 | data AntigenConfig = AntigenConfig 46 | { plugins :: ![ZshPlugin] 47 | -- ^ List of plugins to install. 48 | , outputDirectory :: FilePath 49 | -- ^ Directory to which all generated files will be written. 50 | -- 51 | -- This may be overridden with the @ANTIGEN_HS_OUT@ environment variable. 52 | } 53 | 54 | -- | Get a default AntigenConfig. 55 | defaultConfig :: AntigenConfig 56 | defaultConfig = AntigenConfig 57 | { plugins = [] 58 | , outputDirectory = ".antigen-hs" 59 | } 60 | 61 | -- | Data type representing a zsh plugin 62 | data ZshPlugin = ZshPlugin 63 | { storage :: RepoStorage 64 | -- ^ Where can this plugin be found? 65 | -- 66 | -- If this is a git repository, it will automatically be checked out in a 67 | -- system-determined location. 68 | , sourcingStrategy :: SourcingStrategy 69 | -- ^ How to determine which scripts to source and in what order? 70 | , sourcingLocations :: [FilePath] 71 | -- ^ List of paths relative to the plugin root in which @sourcingStrategy@ 72 | -- will be executed. 73 | , fpathLocations :: [FilePath] 74 | -- ^ Paths relative to plugin root which will be added to @fpath@. 75 | } 76 | 77 | -- | The SourcingStrategy is executed inside the plugin's @sourcingLocations@, 78 | -- and the paths returned by it are sourced in-order. 79 | type SourcingStrategy = IO [FilePath] 80 | 81 | -- | A location where the plugin may be found. 82 | data RepoStorage 83 | = -- | The plugin is available in a GitHub repository. 84 | -- 85 | -- A local copy will be cloned. 86 | GitRepository 87 | { url :: !Text 88 | -- ^ Example: https://github.com/Tarrasch/zsh-functional 89 | } 90 | | -- | The plugin is available locally. 91 | Local 92 | { filePath :: !FilePath 93 | -- ^ See 'local' 94 | } 95 | deriving (Show, Eq) 96 | 97 | 98 | -- | Directory where the repositories will be stored. 99 | reposDirectory :: AntigenConfig -> FilePath 100 | reposDirectory config = outputDirectory config "repos" 101 | 102 | 103 | -- | The final output script. 104 | -- 105 | -- This is automatically sourced by init.zsh. 106 | outputFileToSource :: AntigenConfig -> FilePath 107 | outputFileToSource config = outputDirectory config "antigen-hs.zsh" 108 | 109 | 110 | -- | A default ZshPlugin 111 | -- 112 | -- The default plugin uses the 'strictSourcingStrategy' inside the plugin 113 | -- root, and adds the plugin root to @fpaths@. 114 | defaultZshPlugin :: ZshPlugin 115 | defaultZshPlugin = ZshPlugin 116 | { storage = error "Please specify a plugin storage." 117 | , sourcingStrategy = strictSourcingStrategy 118 | , sourcingLocations = ["."] 119 | , fpathLocations = [""] 120 | } 121 | 122 | -- | Like @antigen bundle@ from antigen. It assumes you want a GitHub 123 | -- repository. 124 | bundle :: Text -> ZshPlugin 125 | bundle repo = defaultZshPlugin 126 | { storage = GitRepository $ "https://github.com/" <> repo 127 | } 128 | 129 | 130 | -- | A local repository, useful when testing plugins 131 | -- 132 | -- > local "/home/arash/repos/zsh-snakebite-completion" 133 | local :: FilePath -> ZshPlugin 134 | local filePath = defaultZshPlugin 135 | { storage = Local filePath 136 | } -- TODO should resolve path to absolute 137 | 138 | 139 | -- | Get the folder in which the storage will be stored on disk 140 | storagePath :: AntigenConfig -> RepoStorage -> FilePath 141 | storagePath config storage = case storage of 142 | GitRepository repo -> 143 | reposDirectory config Text.unpack (santitize repo) 144 | Local path -> path 145 | where 146 | santitize = Text.concatMap aux 147 | aux ':' = "-COLON-" 148 | aux '/' = "-SLASH-" 149 | aux c = Text.singleton c 150 | 151 | 152 | -- | Clone the repository if it already doesn't exist. 153 | ensurePlugin :: AntigenConfig -> RepoStorage -> IO () 154 | ensurePlugin _ (Local path) = do 155 | exists <- doesDirectoryExist path 156 | unless exists $ 157 | die $ "Local plugin " ++ show path ++ " does not exist. " ++ 158 | "Make sure that the path is absolute." 159 | ensurePlugin config storage@(GitRepository url) = do 160 | exists <- doesDirectoryExist path 161 | unless exists $ 162 | gitClone url path 163 | where 164 | path = storagePath config storage 165 | 166 | -- TODO URL should be ByteString? 167 | 168 | gitClone :: Text -> FilePath -> IO () 169 | gitClone url path = 170 | Proc.callProcess "git" 171 | ["clone", "--recursive", "--", Text.unpack url, path] 172 | 173 | 174 | -- | Convert a relative path to absolute by prepending the current directory. 175 | -- 176 | -- This is available in directory >= 1.2.3, but GHC 7.8 uses 1.2.1.0. 177 | makeAbsolute :: FilePath -> IO FilePath 178 | makeAbsolute = (normalise <$>) . absolutize 179 | where 180 | absolutize path 181 | | isRelative path = ( path) <$> getCurrentDirectory 182 | | otherwise = return path 183 | 184 | withDirectory :: FilePath -> IO a -> IO a 185 | withDirectory p io = do 186 | old <- getCurrentDirectory 187 | bracket_ (setCurrentDirectory p) 188 | (setCurrentDirectory old) 189 | io 190 | 191 | -- | Gather scripts to source for each sourcing location of the plugin. 192 | findPluginZshs :: AntigenConfig -> ZshPlugin -> IO [FilePath] 193 | findPluginZshs config plugin = 194 | withDirectory (storagePath config (storage plugin)) $ 195 | fmap concat . forM (sourcingLocations plugin) $ \loc -> 196 | withDirectory loc $ 197 | sourcingStrategy plugin 198 | 199 | 200 | -- | Get full paths to all files in the given directory. 201 | listFiles :: FilePath -> IO [FilePath] 202 | listFiles p 203 | = map (p ) 204 | . filter (`notElem` [".", ".."]) 205 | <$> getDirectoryContents p 206 | 207 | 208 | die :: String -> IO a 209 | die msg = putStrLn msg >> exitFailure 210 | 211 | 212 | -- | Match for one single *.plugin.zsh file 213 | strictSourcingStrategy :: SourcingStrategy 214 | strictSourcingStrategy = do 215 | directory <- getCurrentDirectory 216 | files <- listFiles directory 217 | let matches = filter (".plugin.zsh" `isSuffixOf`) files 218 | case matches of 219 | [file] -> return [file] 220 | [] -> die $ 221 | "No *.plugin.zsh file in " ++ 222 | directory ++ "! " ++ 223 | "See antigenSourcingStrategy example in README " ++ 224 | "on how to configure this." 225 | _ -> die ("Too many *.plugin.zsh files in " ++ directory ++ "!") 226 | 227 | 228 | -- | Find what to source, using the strategy described here: 229 | -- 230 | -- https://github.com/zsh-users/antigen#notes-on-writing-plugins 231 | antigenSourcingStrategy :: SourcingStrategy 232 | antigenSourcingStrategy = do 233 | files <- getCurrentDirectory >>= listFiles 234 | let candidatePatterns = [".plugin.zsh", "init.zsh", ".zsh", ".sh"] 235 | filesMatching pat = filter (pat `isSuffixOf`) files 236 | filteredResults = map filesMatching candidatePatterns 237 | results = filter (not . null) filteredResults 238 | case results of 239 | (matchedFiles:_) -> return matchedFiles 240 | [] -> die $ "No files to source among " ++ show files 241 | 242 | 243 | -- | Source all files in the given order. Currently does no file existence 244 | -- check or anything. 245 | filePathsSourcingStrategy :: [FilePath] -> SourcingStrategy 246 | filePathsSourcingStrategy paths = do 247 | cwd <- getCurrentDirectory 248 | return $ map (cwd ) paths 249 | 250 | 251 | getAbsoluteFpaths :: AntigenConfig -> ZshPlugin -> IO [FilePath] 252 | getAbsoluteFpaths config plugin = do 253 | let path = storagePath config (storage plugin) 254 | mapM (makeAbsolute . (path )) (fpathLocations plugin) 255 | 256 | -- | Get the content that will be put in the file to source. 257 | fileToSourceContent 258 | :: AntigenConfig 259 | -> [FilePath] -- ^ List of all the *.plugin.zsh files 260 | -> IO Text -- ^ What the file should contain 261 | fileToSourceContent config@AntigenConfig{plugins} pluginZshs = do 262 | pluginZshsAbs <- mapM makeAbsolute pluginZshs 263 | fpaths <- concat <$> mapM (getAbsoluteFpaths config) plugins 264 | return $ Text.unlines 265 | $ "# THIS FILE IS GENERATED BY antigen-hs!!!!\n" 266 | : map (("source " <>) . Text.pack) pluginZshsAbs 267 | ++ map (("fpath+=" <>) . Text.pack) fpaths 268 | 269 | 270 | -- | Do an action inside the home directory 271 | inHomeDir :: IO a -> IO a 272 | inHomeDir io = do 273 | home <- getHomeDirectory 274 | withDirectory home io 275 | 276 | 277 | -- | The main function that will clone all the repositories and create the 278 | -- file to be sourced by the user 279 | antigen :: AntigenConfig -> IO () 280 | antigen config'@AntigenConfig{plugins} = inHomeDir $ do 281 | hsOut <- lookupEnv "ANTIGEN_HS_OUT" 282 | let config = config' 283 | { outputDirectory = fromMaybe (outputDirectory config') hsOut } 284 | 285 | createDirectoryIfMissing True (reposDirectory config) 286 | mapM_ (ensurePlugin config . storage) plugins 287 | pluginZshs <- concat <$> mapM (findPluginZshs config) plugins 288 | contents <- fileToSourceContent config pluginZshs 289 | Text.writeFile (outputFileToSource config) contents 290 | --------------------------------------------------------------------------------