├── .gitignore ├── CHANGES.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── doc └── DOCUMENTATION.md ├── project.clj ├── src └── leiningen │ └── shell.clj └── test └── leiningen └── test └── shell.clj /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /lib 3 | /classes 4 | /checkouts 5 | pom.xml 6 | pom.xml.asc 7 | *.jar 8 | *.class 9 | .lein-deps-sum 10 | .lein-failures 11 | .lein-plugins 12 | .lein-repl-history 13 | *~ 14 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # lein-shell changelog 2 | 3 | ## 0.5.0 [`tag`][0.5.0-tag] 4 | 5 | * Fixed a bug where non-string were not converted to strings before sent to the 6 | command, causing NullPointerExceptions, cast errors and other problems. 7 | * Implemented parameter expansion. You can specify them on the form `${:foo}` 8 | and on the form `${[:foo :bar]}`. They can also contain default strings. 9 | 10 | ## 0.4.2 [`tag`][0.4.2-tag] 11 | 12 | * Fixed a bug where lein-shell itself could not be used as a higher order alias 13 | in certain scenarios. 14 | 15 | ## 0.4.1 [`tag`][0.4.1-tag] 16 | 17 | * Custom `:shell :commands` can now be vectors instead of strings only, acting 18 | more or less like Lein aliases. 19 | 20 | ## 0.4.0 [`tag`][0.4.0-tag] 21 | 22 | * By default, stdin is piped in to shell commands. You can now specify that 23 | lein-shell doesn't pipe stdin to the shell command. 24 | * A `:default-command` option can be specified as a catch-all for os-specific 25 | commands, which can be used to create aliases. 26 | 27 | ## 0.3.0 [`tag`][0.3.0-tag] 28 | 29 | * Exit-code handling, directory specification and environment settings can now 30 | be set on a per-command basis. 31 | * It is now possible to ignore exit codes from commands, if wanted. 32 | * Fixed a bug where reading from Stdin resulted in only partial input 33 | redirection. 34 | 35 | ## 0.2.0 [`tag`][0.2.0-tag] 36 | 37 | * Fixed a bug where the exit-code of a process was ignored. 38 | * It is now possible to specify a directory in which to start commands. This is 39 | by default the root folder of a project. 40 | * Added possibility to add/replace environment variable settings. 41 | * Implemented functionality to specify aliases for commands based on which 42 | operative system you are using. 43 | 44 | ## 0.1.0 [`tag`][0.1.0-tag] 45 | 46 | * First release! 47 | 48 | [0.5.0-tag]: https://github.com/hyPiRion/lein-shell/tree/0.5.0 49 | [0.4.2-tag]: https://github.com/hyPiRion/lein-shell/tree/0.4.2 50 | [0.4.1-tag]: https://github.com/hyPiRion/lein-shell/tree/0.4.1 51 | [0.4.0-tag]: https://github.com/hyPiRion/lein-shell/tree/0.4.0 52 | [0.3.0-tag]: https://github.com/hyPiRion/lein-shell/tree/0.3.0 53 | [0.2.0-tag]: https://github.com/hyPiRion/lein-shell/tree/0.2.0 54 | [0.1.0-tag]: https://github.com/hyPiRion/lein-shell/tree/0.1.0 55 | 56 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Hello reader! 4 | 5 | We would love to get contributors for lein-shell, and all the guidelines below 6 | is not an attempt to scare you away. As long as you're interested in 7 | contributing, we would love to hear from you. The reason we have these 8 | guidelines in place is to have more efficient communication between 9 | contributors, which means that we can merge in your patch faster, or understand 10 | and fix the bug you found more rapidly. 11 | 12 | If you're unsure whether you follow the guidelines or not, just send it. We're 13 | not picky, and as mentioned earlier, these guidelines is not there to scare you 14 | away. 15 | 16 | ## Issues 17 | 18 | If you found a bug, have an idea for improvement, a question or something else 19 | you believe consitutes to an issue, we'd love it if you report this on the 20 | GitHub issue tracker. Sending bug reports to personal email addresses is 21 | inappropriate. 22 | 23 | If you think you've hit on a bug, it would be great if you could include the 24 | following information (if it makes sense to include it): 25 | 26 | * What (small set of) steps will reproduce the problem? 27 | * What is the expected output? What do you see instead? 28 | * What version are you using? 29 | 30 | ## Patches 31 | 32 | Patches are preferred as Github pull requests. Use topic branches instead of 33 | commiting directly to master, to avoid unnecessary merge clutter. It is 34 | preferred that commit messages keeps the following style: 35 | 36 | * First line is 50 characters or less 37 | * Then a blank line 38 | * Remaining text should be wrapped at 72 characters 39 | 40 | As an example, this would be preferable: 41 | 42 | ```bash 43 | # Fork the project off Github 44 | $ git clone git@github.com:your-username/lein-shell.git 45 | $ cd lein-shell 46 | $ git checkout -b my-patch 47 | # Do your changes now, and stage them 48 | $ lein test 49 | $ git commit -m "I've fixed this and that, fixes #42." 50 | $ git push 51 | # Submit a pull request 52 | ``` 53 | 54 | ## Code style 55 | 56 | Try to be aware of the conventions in the existing code. Make a reasonable 57 | attempt to avoid lines longer than 80 columns or function bodies longer than 20 58 | lines. Don't use `when` unless it's for side-effects. 59 | 60 | ## Testing 61 | 62 | Before you're asking for a pull request, we would be very happy if you ensure 63 | that the changes you've done doesn't break any of the existing test cases. 64 | Patches which add test coverage for the functionality they change are especially 65 | welcome, but this is not necessary. 66 | 67 | To run the test cases, run `lein test` in the root directory. 68 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Eclipse Public License - v 1.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 4 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 5 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 6 | 7 | 1. DEFINITIONS 8 | 9 | "Contribution" means: 10 | 11 | a) in the case of the initial Contributor, the initial code and documentation 12 | distributed under this Agreement, and 13 | b) in the case of each subsequent Contributor: 14 | i) changes to the Program, and 15 | ii) additions to the Program; 16 | 17 | where such changes and/or additions to the Program originate from and are 18 | distributed by that particular Contributor. A Contribution 'originates' from 19 | a Contributor if it was added to the Program by such Contributor itself or 20 | anyone acting on such Contributor's behalf. Contributions do not include 21 | additions to the Program which: (i) are separate modules of software 22 | distributed in conjunction with the Program under their own license 23 | agreement, and (ii) are not derivative works of the Program. 24 | 25 | "Contributor" means any person or entity that distributes the Program. 26 | 27 | "Licensed Patents" mean patent claims licensable by a Contributor which are 28 | necessarily infringed by the use or sale of its Contribution alone or when 29 | combined with the Program. 30 | 31 | "Program" means the Contributions distributed in accordance with this Agreement. 32 | 33 | "Recipient" means anyone who receives the Program under this Agreement, 34 | including all Contributors. 35 | 36 | 2. GRANT OF RIGHTS 37 | a) Subject to the terms of this Agreement, each Contributor hereby grants 38 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 39 | reproduce, prepare derivative works of, publicly display, publicly perform, 40 | distribute and sublicense the Contribution of such Contributor, if any, and 41 | such derivative works, in source code and object code form. 42 | b) Subject to the terms of this Agreement, each Contributor hereby grants 43 | Recipient a non-exclusive, worldwide, royalty-free patent license under 44 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 45 | transfer the Contribution of such Contributor, if any, in source code and 46 | object code form. This patent license shall apply to the combination of the 47 | Contribution and the Program if, at the time the Contribution is added by 48 | the Contributor, such addition of the Contribution causes such combination 49 | to be covered by the Licensed Patents. The patent license shall not apply 50 | to any other combinations which include the Contribution. No hardware per 51 | se is licensed hereunder. 52 | c) Recipient understands that although each Contributor grants the licenses to 53 | its Contributions set forth herein, no assurances are provided by any 54 | Contributor that the Program does not infringe the patent or other 55 | intellectual property rights of any other entity. Each Contributor 56 | disclaims any liability to Recipient for claims brought by any other entity 57 | based on infringement of intellectual property rights or otherwise. As a 58 | condition to exercising the rights and licenses granted hereunder, each 59 | Recipient hereby assumes sole responsibility to secure any other 60 | intellectual property rights needed, if any. For example, if a third party 61 | patent license is required to allow Recipient to distribute the Program, it 62 | is Recipient's responsibility to acquire that license before distributing 63 | the Program. 64 | d) Each Contributor represents that to its knowledge it has sufficient 65 | copyright rights in its Contribution, if any, to grant the copyright 66 | license set forth in this Agreement. 67 | 68 | 3. REQUIREMENTS 69 | 70 | A Contributor may choose to distribute the Program in object code form under its 71 | own license agreement, provided that: 72 | 73 | a) it complies with the terms and conditions of this Agreement; and 74 | b) its license agreement: 75 | i) effectively disclaims on behalf of all Contributors all warranties and 76 | conditions, express and implied, including warranties or conditions of 77 | title and non-infringement, and implied warranties or conditions of 78 | merchantability and fitness for a particular purpose; 79 | ii) effectively excludes on behalf of all Contributors all liability for 80 | damages, including direct, indirect, special, incidental and 81 | consequential damages, such as lost profits; 82 | iii) states that any provisions which differ from this Agreement are offered 83 | by that Contributor alone and not by any other party; and 84 | iv) states that source code for the Program is available from such 85 | Contributor, and informs licensees how to obtain it in a reasonable 86 | manner on or through a medium customarily used for software exchange. 87 | 88 | When the Program is made available in source code form: 89 | 90 | a) it must be made available under this Agreement; and 91 | b) a copy of this Agreement must be included with each copy of the Program. 92 | Contributors may not remove or alter any copyright notices contained within 93 | the Program. 94 | 95 | Each Contributor must identify itself as the originator of its Contribution, if 96 | any, in a manner that reasonably allows subsequent Recipients to identify the 97 | originator of the Contribution. 98 | 99 | 4. COMMERCIAL DISTRIBUTION 100 | 101 | Commercial distributors of software may accept certain responsibilities with 102 | respect to end users, business partners and the like. While this license is 103 | intended to facilitate the commercial use of the Program, the Contributor who 104 | includes the Program in a commercial product offering should do so in a manner 105 | which does not create potential liability for other Contributors. Therefore, if 106 | a Contributor includes the Program in a commercial product offering, such 107 | Contributor ("Commercial Contributor") hereby agrees to defend and indemnify 108 | every other Contributor ("Indemnified Contributor") against any losses, damages 109 | and costs (collectively "Losses") arising from claims, lawsuits and other legal 110 | actions brought by a third party against the Indemnified Contributor to the 111 | extent caused by the acts or omissions of such Commercial Contributor in 112 | connection with its distribution of the Program in a commercial product 113 | offering. The obligations in this section do not apply to any claims or Losses 114 | relating to any actual or alleged intellectual property infringement. In order 115 | to qualify, an Indemnified Contributor must: a) promptly notify the Commercial 116 | Contributor in writing of such claim, and b) allow the Commercial Contributor to 117 | control, and cooperate with the Commercial Contributor in, the defense and any 118 | related settlement negotiations. The Indemnified Contributor may participate in 119 | any such claim at its own expense. 120 | 121 | For example, a Contributor might include the Program in a commercial product 122 | offering, Product X. That Contributor is then a Commercial Contributor. If that 123 | Commercial Contributor then makes performance claims, or offers warranties 124 | related to Product X, those performance claims and warranties are such 125 | Commercial Contributor's responsibility alone. Under this section, the 126 | Commercial Contributor would have to defend claims against the other 127 | Contributors related to those performance claims and warranties, and if a court 128 | requires any other Contributor to pay any damages as a result, the Commercial 129 | Contributor must pay those damages. 130 | 131 | 5. NO WARRANTY 132 | 133 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN 134 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR 135 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, 136 | NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each 137 | Recipient is solely responsible for determining the appropriateness of using and 138 | distributing the Program and assumes all risks associated with its exercise of 139 | rights under this Agreement , including but not limited to the risks and costs 140 | of program errors, compliance with applicable laws, damage to or loss of data, 141 | programs or equipment, and unavailability or interruption of operations. 142 | 143 | 6. DISCLAIMER OF LIABILITY 144 | 145 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 146 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 147 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST 148 | PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 149 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 150 | OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS 151 | GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 152 | 153 | 7. GENERAL 154 | 155 | If any provision of this Agreement is invalid or unenforceable under applicable 156 | law, it shall not affect the validity or enforceability of the remainder of the 157 | terms of this Agreement, and without further action by the parties hereto, such 158 | provision shall be reformed to the minimum extent necessary to make such 159 | provision valid and enforceable. 160 | 161 | If Recipient institutes patent litigation against any entity (including a 162 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 163 | (excluding combinations of the Program with other software or hardware) 164 | infringes such Recipient's patent(s), then such Recipient's rights granted under 165 | Section 2(b) shall terminate as of the date such litigation is filed. 166 | 167 | All Recipient's rights under this Agreement shall terminate if it fails to 168 | comply with any of the material terms or conditions of this Agreement and does 169 | not cure such failure in a reasonable period of time after becoming aware of 170 | such noncompliance. If all Recipient's rights under this Agreement terminate, 171 | Recipient agrees to cease use and distribution of the Program as soon as 172 | reasonably practicable. However, Recipient's obligations under this Agreement 173 | and any licenses granted by Recipient relating to the Program shall continue and 174 | survive. 175 | 176 | Everyone is permitted to copy and distribute copies of this Agreement, but in 177 | order to avoid inconsistency the Agreement is copyrighted and may only be 178 | modified in the following manner. The Agreement Steward reserves the right to 179 | publish new versions (including revisions) of this Agreement from time to time. 180 | No one other than the Agreement Steward has the right to modify this Agreement. 181 | The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation 182 | may assign the responsibility to serve as the Agreement Steward to a suitable 183 | separate entity. Each new version of the Agreement will be given a 184 | distinguishing version number. The Program (including Contributions) may always 185 | be distributed subject to the version of the Agreement under which it was 186 | received. In addition, after a new version of the Agreement is published, 187 | Contributor may elect to distribute the Program (including its Contributions) 188 | under the new version. Except as expressly stated in Sections 2(a) and 2(b) 189 | above, Recipient receives no rights or licenses to the intellectual property of 190 | any Contributor under this Agreement, whether expressly, by implication, 191 | estoppel or otherwise. All rights in the Program not expressly granted under 192 | this Agreement are reserved. 193 | 194 | This Agreement is governed by the laws of the State of New York and the 195 | intellectual property laws of the United States of America. No party to this 196 | Agreement will bring a legal action under this Agreement more than one year 197 | after the cause of action arose. Each party waives its rights to a jury trial in 198 | any resulting litigation. 199 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lein-shell 2 | 3 | A Leiningen plugin for calling shell commands. lein-shell is an active, stable 4 | project. 5 | 6 | ## Installation 7 | 8 | Put `[lein-shell "0.5.0"]` into the `:plugins` vector of your `:user` profile 9 | inside `~/.lein/profiles.clj` if you want to use lein shell on a per user basis 10 | (this doesn't *really* make much sense, but you're allowed to if you want to!). 11 | 12 | To explicitly say that this project needs lein-shell to be built, put 13 | `[lein-shell "0.5.0"]` into the `:plugins` vector of your `project.clj`. If you 14 | have no `:plugins` vector in your `project.clj`, it should look like this: 15 | 16 | ```clj 17 | (defproject your-project-here "version" 18 | ... 19 | :plugins [[lein-shell "0.5.0"]] 20 | ...) 21 | ``` 22 | 23 | ## Quickstart 24 | 25 | It is very straightforward to use lein-shell: lein-shell will call the shell 26 | command with eventual parameters you include. For instance, if you want your 27 | favourite cow to say hello to you from Leiningen, the following will be printed 28 | within your shell: 29 | 30 | $ lein shell cowsay 'Hello from Leiningen!' 31 | _______________________ 32 | < Hello from Leiningen! > 33 | ----------------------- 34 | \ ^__^ 35 | \ (oo)\_______ 36 | (__)\ )\/\ 37 | ||----w | 38 | || || 39 | 40 | Now, this may look rather useless as you can just omit `lein shell` and get the 41 | exact same result in less time. However, it may be of value if you're using 42 | `make` or `ANTLR` to generate files for you, needed by your Clojure project. For 43 | example, to automatically call `make` before running tasks, add this to your 44 | `project.clj` map: 45 | 46 | ```clj 47 | :prep-tasks [["shell" "make"] "javac" "compile"] 48 | ``` 49 | 50 | If the command exits with a nonzero exit code, shell will (attempt to) exit 51 | Leiningen with the same exit code. This functionality can be overridden if 52 | desired, and many other settings can be modified as well. 53 | 54 | ## Documentation 55 | 56 | For more information, have a look at [the documentation][documentation]. It 57 | contains a lot of examples, some which hopefully are useful to you. 58 | 59 | [documentation]: https://github.com/hyPiRion/lein-shell/blob/stable/doc/DOCUMENTATION.md 60 | 61 | ## License 62 | 63 | Copyright © 2013-2015 Jean Niklas L'orange and [contributors][]. 64 | 65 | [contributors]: https://github.com/hyPiRion/lein-shell/contributors 66 | 67 | Distributed under the Eclipse Public License, the same as Clojure. 68 | -------------------------------------------------------------------------------- /doc/DOCUMENTATION.md: -------------------------------------------------------------------------------- 1 | # Documentation for lein-shell 2 | 3 | lein-shell is a plugin for running shell commands. Sometimes, you just need to 4 | be able to run some sort of setup tool not available from the JVM, or which does 5 | not (yet?) have a Leiningen plugin which does the job. 6 | 7 | Generally speaking, shell commands are straightforward. However, sometimes you 8 | would like to tweak the setup a bit. Perhaps you would like to change the 9 | working directory, or maybe you want to ignore the exit code. lein-shell aims to 10 | be configurable enough that such things can be done through modifications in the 11 | `project.clj` only. 12 | 13 | ## Installation 14 | 15 | Put `[lein-shell "0.5.0"]` into the `:plugins` vector of your `:user` profile 16 | inside `~/.lein/profiles.clj` if you want to use lein shell on a per user basis 17 | (this doesn't *really* make much sense, but you're allowed to if you want to!). 18 | 19 | To explicitly say that this project needs lein-shell to be built, putt 20 | `[lein-shell "0.5.0"]` into the `:plugins` vector of your `project.clj`. If you 21 | have no `:plugins` vector in your `project.clj`, it should look like this: 22 | 23 | ```clj 24 | (defproject your-project-here "version" 25 | ... 26 | :plugins [[lein-shell "0.5.0"]] 27 | ...) 28 | ``` 29 | 30 | ## Basic Usage 31 | 32 | lein-shell is very straightforward to use. Just prepend the shell command you'd 33 | like to call with `lein shell`, and you're ready to go. For instance, if I 34 | wanted my favourite duck to tell me `Hello from Leiningen!`, I could just do 35 | this: 36 | 37 | lein shell cowsay -f duck 'Hello from Leiningen!' 38 | _______________________ 39 | < Hello from Leiningen! > 40 | ----------------------- 41 | \ 42 | \ 43 | \ >()_ 44 | (__)__ _ 45 | 46 | lein-shell doesn't need to be run inside a project, but it is usually the 47 | sensible place to use it. 48 | 49 | ## Parameter Expansion 50 | 51 | As with common Unix shells, lein-shell supports variable/parameter expansion in 52 | arguments. Whenever lein-shell sees `${:foo}` in a string passed in, it will 53 | expand that to the value of the key `:foo` in the project map, if it exists. If 54 | you pass in a vector instead, such as `${[:foo :bar]}`, then the vector will be 55 | used as if used by `get-in` on the project map. 56 | 57 | As an example, this shell call will cause a cute bunny to print the information 58 | about the project (note the single quotes!): 59 | 60 | lein shell cowsay -f bunny 'This is ${:name}, version ${:version}. It is licensed under the ${[:license :name]}' 61 | 62 | For lein shell 1.0.0-SNAPSHOT, this will print the following to your terminal: 63 | 64 | ______________________________________ 65 | / This is lein-shell, version \ 66 | | 1.0.0-SNAPSHOT. It is licensed under | 67 | \ the Eclipse Public License / 68 | -------------------------------------- 69 | \ 70 | \ \ 71 | \ /\ 72 | ( ) 73 | .( o ). 74 | 75 | lein shell also allows you to specify a default string if the values do not 76 | exist. This is done by appending `:-` after the clojure value provided, then the 77 | default string, then the closing brace. As an example, many projects will print 78 | out the following when you run lein shell with this command: 79 | 80 | lein shell echo '${:not-defined:-this value is not defined!}' 81 | this value is not defined! 82 | 83 | Note that you can insert parameter expansions inside the default string. The 84 | following example will attempt to find :foo, then :bar, then :baz, then fall 85 | back to quux for charging some lasers: 86 | 87 | lein shell echo 'Charging the lasers with ${:foo:-${:bar:-${:baz:-quux (:baz not available)} (:bar not available)} (:foo not available)}' 88 | Charging the lasers with quux (:baz not available) (:bar not available) (:foo not available) 89 | 90 | If need be, you can escape the $ to avoid to treat it as a parameter expansion: 91 | 92 | lein shell echo 'clojure.core\$apply.invoke is in the stacktrace' 93 | clojure.core$apply.invoke is in the stacktrace 94 | 95 | If you specify this inside project.clj, remember to escape the escape character; 96 | the previous call would be equal to 97 | `["shell" "echo" "clojure.core\\$apply.invoke is in the stacktrace"]`. 98 | 99 | Note that parameter expansion is not implemented to support Clojure values that 100 | contain `}` or `:-` in them. This is to some extent intentional: If you need 101 | such complex expansion, then it seems better to have this in a plugin or as a 102 | separate value you expand. It is also not possible to expand environment 103 | variables. This is to avoid people from making accidental nonrepeatable builds. 104 | If you need access to environment variables, you can make a bash script that is 105 | called with lein-shell instead. 106 | 107 | ## (Example) Usage 108 | 109 | lein-shell can be used for many things, but it is commonly used for preparation 110 | tasks and inside aliases. It is not limited to this, of course, but usage 111 | outside this scope is somewhat obscure. 112 | 113 | ### As a Preparation Task 114 | 115 | In some projects, you would have to build files from scratch. For instance, if 116 | you have a web server and some `.less` files you'd like to convert to `.css`, 117 | that could be done by lein-shell. If you have an ANTLR grammar you'd like to 118 | compile before you compile your Clojure code, using lein-shell as a preparation 119 | task would also work. As an example, consider a project where I have some 120 | generated code my clojure code depends on. To generate that code before 121 | uberjaring, testing and repl'ing, I can do this: 122 | 123 | ```clj 124 | (defproject my-project "0.1.0-SNAPSHOT" 125 | ... 126 | :prep-tasks [["shell" "generator" "--in" "build/in.grammar" 127 | "--out" "src/out.clj"] 128 | "javac" "compile"] 129 | :plugins [[lein-shell "0.5.0"]]) 130 | ``` 131 | 132 | Now, the command `generator --in build/in.grammar --out src/out.clj` will always 133 | be called before any in-project evaluation. 134 | 135 | Mind you, lein-shell is not a replacement for `make`. A shell command will 136 | always be executed, no matter the circumstances. However, lein-shell can call 137 | `make`, which should solve that issue nicely. 138 | 139 | #### A Word About `:prep-tasks` 140 | 141 | So, what is `:prep-tasks` really? How does it work? 142 | 143 | All core tasks in Leiningen that must be run inside a project will activate 144 | `:prep-tasks`, which are Leiningen tasks run before the actual task you run. By 145 | default, `:prep-tasks` equals to `["javac" "compile"]`, but this can be 146 | modified. However, once you start modifying it, the default won't activate. So, 147 | if you're in need of `javac` and `compile`, you must put them before or after 148 | the modifications you've done. In the example above, the shell command may 149 | generate code needed for the javac task or the compile task. This is usually the 150 | case, but you can put the shell command at the end if you need the compiled 151 | java/clojure code. 152 | 153 | ### As an Alias 154 | 155 | lein-shell can also be used as an alias. I tend to use the 1.7 javadoc for java 156 | documentation, but the 1.6 jvm for backwards compatibility reasons. As such, I 157 | have to call the shell because I cannot use the 1.6 API. Here's how I handle 158 | that: 159 | 160 | ```clj 161 | (defproject package.name/project "0.1.0-SNAPSHOT" 162 | ... 163 | :plugins [[lein-shell "0.5.0"]] 164 | :aliases {"javadoc" ["shell" "javadoc" "-d" "javadoc" 165 | "-sourcepath" "src/" "package.name"] 166 | "jar" ["do" "javadoc," "jar"]}}}) 167 | ``` 168 | 169 | Whenever I want to deploy, I also generate the javadoc and include it within the 170 | jar file generated (through some inclusion filters). In that way, I don't mess 171 | up and include old javadoc files. 172 | 173 | If building larger stuff takes time, it possible to set up aliases for the 174 | building tasks so that you do it manually once. While this could be added in by 175 | a shell file, the project may look cleaner if you don't have shell scripts 176 | sprinkled around everywhere. 177 | 178 | ## Configuration 179 | 180 | Generally, the default setup should suffice for most use cases. However, there 181 | are times when the defaults are not what you want, or where you would like to 182 | configure the environment or the directory you're running from. Here's how you 183 | would do that. 184 | 185 | ### Setting Configurations and their Priority 186 | 187 | Options are specified inside the project as follows: 188 | 189 | ```clj 190 | (defproject ... 191 | ... 192 | :shell {:option1 choice 193 | :option2 other-choice 194 | :commands {"foo" {:option1 choice-for-foo}}}) 195 | ``` 196 | 197 | Configuration options can currently be set as a default setting for all shell 198 | commands, or as an option for a specific shell command. Whenever a setting for a 199 | specific shell command is given, then the default setting is ignored. In the 200 | example above, all shell commands will have `option2` set to `other-choice`. The 201 | command `foo` will have set `option1` to `choice-for-foo`, whereas all other 202 | commands have `choice` set for `option1`. 203 | 204 | ### Environment Variables 205 | 206 | To configure the environment variables, you set `:env` to a map with the 207 | environment variables you want to set. The default value is `{}`. 208 | 209 | As an example, let's see how we can change the default java command, `JAVA_CMD`: 210 | 211 | ```clj 212 | (defproject ... 213 | ... 214 | :shell {:env {"JAVA_CMD" "java42"}) 215 | ``` 216 | 217 | We can see that it's working by using `printenv`. `printenv JAVA_CMD` usually 218 | prints out `java` or nothing, but `lein shell printenv JAVA_CMD` should now 219 | print out `java42`. 220 | 221 | ### Directory Specification 222 | 223 | To configure the working directory (which directory you're calling the command 224 | from), set the `:dir` to a string which specifies the directory to work from. 225 | The default value is the root directory of the project. The directory can be set 226 | relative to the root directory of the project. 227 | 228 | As an example, see the setup below. 229 | 230 | ```clj 231 | (defproject my-project "0.1.0-SNAPSHOT" 232 | ... 233 | :shell {:dir "src"}) 234 | ``` 235 | 236 | Assume that the project is placed within `/home/pir/workspace/my-project`. 237 | Calling `lein shell pwd` would then print out 238 | `/home/pir/workspace/my-project/src`, and if we changed `"src"` to e.g. 239 | `"/home/pir"`, it would print `/home/pir` instead. 240 | 241 | ### Exit Codes 242 | 243 | If a shell command returns an exit code different from 0, then lein-shell will 244 | try to exit Leiningen with the same exit code by default. This behaviour can be 245 | overridden by setting the `:exit-code` option to `:ignore`. lein-shell will then 246 | completely ignore the exit code and continue as if it was 0. By default is this 247 | option set to `:default`. 248 | 249 | As an example, see this setup below. 250 | 251 | ```clj 252 | (defproject my-project "0.1.0-SNAPSHOT" 253 | ... 254 | :shell {:commands {"false" {:exit-code :ignore}}}) 255 | ``` 256 | 257 | `lein shell false` will then happily return 0, whereas all other commands return 258 | their true exit code. 259 | 260 | ### OS-Specific Subprocess Call 261 | 262 | Different operating systems may use different commands for equivalent 263 | functionality. When such issues arises, it would be convenient if you could 264 | somehow specify this. This is possible with lein-shell: Say you have a command 265 | named `foo` in Linux, but `bar` in Windows, and you want to run this command as 266 | a prepared task before compiling and similar. To enable auto-preparation for 267 | such a task, a setup like this should suffice: 268 | 269 | ```clj 270 | (defproject ... 271 | ... 272 | :prep-tasks [["shell" "foo" "arg1" "arg2"] "javac" "compile"] 273 | :shell {:commands {"foo" {:windows "bar"}}}) 274 | ``` 275 | 276 | Here, `lein` will run `foo arg1 arg2` on any non-Windows system and `bar arg1 277 | arg2` on Windows, and this will happen before any task within this given 278 | project. 279 | 280 | As may be evident, this replacement option is only possible for specific 281 | commands, and is not something you can set in general. 282 | 283 | If you need to run several commands on one OS to achieve equivalent results 284 | on other platforms, then you can provide multiple commands in a vector: 285 | 286 | ```clj 287 | (defproject ... 288 | ... 289 | :shell {:commands {"foo" {:windows ["bar" "baz" "bat"]}}}) 290 | ``` 291 | 292 | The (currently) different detectable oses are `:freebsd`, `:linux`, `:macosx`, 293 | `:openbsd`, `:solaris` and `:windows`, but this may automatically increase with 294 | newer leiningen releases. 295 | 296 | ### Default-command and aliasing 297 | 298 | In addition to the OS-specific subprocess commands, you can also use 299 | `:default-command` as a catch-all. This may seem pointless, as you can just 300 | change the default command call instead. However, this functionality makes it 301 | possible to make aliases for a single command. 302 | 303 | Why are aliases valuable? Mainly because you may want to specify different 304 | command options for different invokations. Maybe you want to run the same 305 | command in different default directories, or maybe you want the same command to 306 | call different os-specific subcalls for some reason or another. 307 | 308 | Here, for example, we alias `echo` to `true`, in order to silence the output 309 | from `echo`. Additionally, we add the alias `shout` to `echo`, regardless of the 310 | original silencing. 311 | 312 | ```clj 313 | (defproject my-project "0.1.0-SNAPSHOT" 314 | ... 315 | :shell {:commands {"echo" {:default-command "true"} 316 | "shout" {:default-command "echo" 317 | :env {"DEBUG" "t"}}}}) 318 | ``` 319 | 320 | Now, calling `lein shell echo foo` will do nothing, whereas `lein shell shout 321 | foo` will print out `foo` as expected, with the `DEBUG` variable set in its 322 | environment. 323 | 324 | Note that the `:default-command` will only override calls from lein-shell in 325 | this project, and will not apply these aliases for anything else. 326 | 327 | ### Ignoring stdin 328 | 329 | The fact that the JVM is not equivalent with a shell language may confuse some. 330 | If we send in some input, to which command is it sent to? By default, lein-shell 331 | detects and sends input to the first command able to receive it. This means 332 | that, if we've made a `cat` function in Clojure 333 | 334 | ```bash 335 | echo 'foo' | lein do shell echo 'bar', run 336 | # can be 337 | (echo 'foo' | echo 'bar') && cat 338 | # which only prints out bar 339 | 340 | # but it can also be 341 | echo 'foo' | (echo 'bar' && cat) 342 | # which prints out foo, followed by bar on a new line 343 | ``` 344 | 345 | The functionality is in a race condition, because the input to lein-shell can 346 | come after `shell echo 'bar'` has run, but also before. 347 | 348 | How do we solve this? Well, we as users of lein-shell knows that the command 349 | `echo` doesn't use stdin data at all, and simply ignores it. This doesn't 350 | lein-shell know, but we can tell it to not send stdin data to `echo` through the 351 | following `project.clj` setup: 352 | 353 | ```clj 354 | (defproject my-project "0.1.0-SNAPSHOT" 355 | ... 356 | :shell {:commands {"echo" {:use-stdin? false}}}) 357 | ``` 358 | 359 | Now, the `lein shell echo ...` won't even attempt to read from stdin in this 360 | specific project. 361 | 362 | #### Multiple stdin consumers 363 | 364 | As mentioned earlier, lein-shell is unable to detect whether the command needs 365 | input from stdin. This also means that lein-shell is unable to detect how much 366 | the specific command needs. When performing multiple shell commands in the same 367 | lein invocation, data **may** be sent to the wrong command. I would argue that 368 | it's generally safe for multiple commands to read input from e.g. a keyboard, 369 | but dangerous to read from a pipe. Timing is the issue: If you know that the 370 | first command has finished before data to the second command comes through 371 | stdin, things will be safe. 372 | 373 | I have not considered to find a solution for this specific problem, but if there 374 | is a desire to have this solved, please ask by requesting it in an issue. 375 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject lein-shell "1.0.0-SNAPSHOT" 2 | :description "Call shell from within Leiningen." 3 | :url "http://www.github.com/hyPiRion/lein-shell" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :deploy-branches ["stable"] 7 | :eval-in-leiningen true) 8 | -------------------------------------------------------------------------------- /src/leiningen/shell.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.shell 2 | (:require [clojure.java.io :as io] 3 | [clojure.string :as s] 4 | [leiningen.core.eval :as eval] 5 | [leiningen.core.main :as main] 6 | [leiningen.core.utils :as utils])) 7 | 8 | (defn- failf [fmt-string & args] 9 | (throw (Exception. ^String (apply format fmt-string args)))) 10 | 11 | (declare read-expansion) 12 | 13 | (defn- read-default 14 | "read-default works like replace-values, except that it will stop 15 | reading when it finds a }. It returns a tuple [val end-pos], where 16 | val is a string of what was read." 17 | [project ^String s orig-start start] 18 | (let [sb (StringBuilder.)] 19 | (loop [i start 20 | quoted false] 21 | (if (>= i (.length s)) 22 | (failf "Unexpected end of argument '%s'. Opening ${ starts at %d, but is not closed" 23 | s (dec orig-start)) 24 | (let [c (.charAt s i)] 25 | (if quoted 26 | (do (.append sb c) 27 | (recur (inc i) false)) 28 | (case c 29 | \$ (let [[val end-pos] (read-expansion project s (inc i))] 30 | (.append sb (str val)) 31 | (recur (inc end-pos) false)) 32 | \} [(.toString sb) i] 33 | \\ (recur (inc i) true) 34 | (do (.append sb c) 35 | (recur (inc i) false))))))))) 36 | 37 | (defn- lookup-vector 38 | "Given a string and a start/stop position, returns the Clojure value it 39 | represents. If the value is not a vector, it is wrapped in one." 40 | [^String s start end] 41 | (let [lookup-str (subs s start end) 42 | lookup-val (read-string lookup-str)] 43 | (if-not (vector? lookup-val) 44 | [lookup-val] 45 | lookup-val))) 46 | 47 | (defn- read-expansion 48 | "read-expansion reads a parameter expansion. It does not perform any kind of 49 | quoting, and Clojure values that contain :- or } will error out the expansion 50 | mechanism. For information on how the parameter expansion works, look at 51 | replace-values." 52 | [project ^String s start] 53 | (if (not= (.charAt s start) \{) 54 | (failf "Expected { after $ at position %d in argument '%s', but was %c" 55 | start s (.charAt s start)) 56 | (loop [i (inc start) 57 | colon false] 58 | (if (>= i (.length s)) 59 | (failf "Unexpected end of argument '%s'. Opening ${ starts at %d, but is not closed" 60 | s (dec start)) 61 | (let [c (.charAt s i)] 62 | (cond (and colon (= c \-)) 63 | ;; ":-" -> we're done then. No smart syntactic trick here, so if you 64 | ;; have a Clojure value that contains :- then you have a problem. 65 | (let [lookup-vec (lookup-vector s (inc start) (dec i)) 66 | [default end-pos] (read-default project s start (inc i))] 67 | [(get-in project lookup-vec default) end-pos]) 68 | (= c \}) ;; no support for values containing } either 69 | (let [lookup-vec (lookup-vector s (inc start) i)] 70 | [(get-in project lookup-vec) i]) 71 | :otherwise 72 | (recur (inc i) (= c \:)))))))) 73 | 74 | (defn- replace-values 75 | "replace-values takes a project and a string s. Parameter expansion is applied 76 | to forms in the shape ${xxx}, where xxx is a clojure value or a vector of 77 | clojure values. If the shape looks like ${xxx:-yyy}, then the string yyy will 78 | be the default value if xxx does not exist in the project map. yyy will be 79 | expanded recursively, i.e. the form ${xxx:-${xxx2:-yyy}} is legal, and will 80 | look for xxx, then xxx2 if not found, then yyy if not found, in that order. 81 | 82 | replace-values will also unquote backslashed values. \"\\${:foo}\" 83 | will be translated to the string \"${:foo}\", and \"\\\\${:foo}\" 84 | will be translated to \"\\[expansion]\". 85 | 86 | The form ${} is illegal." 87 | [project ^String s] 88 | (let [sb (StringBuilder. (.length s))] 89 | (loop [i (int 0) 90 | quoted false] 91 | (if (>= i (.length s)) 92 | (.toString sb) 93 | (let [c (.charAt s i)] 94 | (if quoted 95 | (do (.append sb c) 96 | (recur (inc i) false)) 97 | (case c 98 | \$ (let [[val end-pos] (read-expansion project s (inc i))] 99 | (.append sb (str val)) 100 | (recur (int (inc end-pos)) false)) 101 | \\ (recur (inc i) true) 102 | (do (.append sb c) 103 | (recur (inc i) false))))))))) 104 | 105 | (defn- param-expand 106 | [project s] 107 | (if-not (string? s) 108 | (str s) 109 | (replace-values project s))) 110 | 111 | (defmacro ^:private get-setting-fn 112 | "Returns a function which returns the highest priority setting when called 113 | with a project and a command. It is a macro because dynamic variables will get 114 | caught and dereferenced if this was a function. Will return falsey values." 115 | ([kw] `(get-setting-fn ~kw nil)) 116 | ([kw default] 117 | `(let [gsym# (gensym "not-found")] 118 | (fn [project# [command# & args#]] 119 | (first 120 | (remove #(= gsym# %) 121 | [(get-in project# [:shell :commands command# ~kw] gsym#) 122 | (get-in project# [:shell ~kw] gsym#) 123 | ~default])))))) 124 | 125 | (def ^:private get-environment 126 | (get-setting-fn :env eval/*env*)) 127 | 128 | (def ^:private get-directory 129 | (get-setting-fn :dir eval/*dir*)) 130 | 131 | (def ^:private get-exit-code 132 | (get-setting-fn :exit-code :default)) 133 | 134 | (def ^:private get-pipe-stdin? 135 | (get-setting-fn :pipe-stdin? true)) 136 | 137 | (defn- lookup-command 138 | "Looks up the first part of command, and replaces it with an os-specific 139 | version if there is one." 140 | [project cmd] 141 | (let [command (first cmd) 142 | os (eval/get-os)] 143 | (if-let [os-cmd (or (get-in project [:shell :commands command os]) 144 | (get-in project [:shell :commands command :default-command]))] 145 | (let [normalized-cmd (if (string? os-cmd) [os-cmd] os-cmd)] 146 | (main/debug (format "[shell] Replacing command %s with %s. (os is %s)" 147 | command normalized-cmd os)) 148 | (concat normalized-cmd (rest cmd))) 149 | cmd))) 150 | 151 | (defn- shell-with-project [project cmd] 152 | (binding [eval/*dir* (get-directory project cmd) 153 | eval/*env* (get-environment project cmd) 154 | eval/*pump-in* (get-pipe-stdin? project cmd)] 155 | (let [cmd (lookup-command project cmd)] 156 | (main/debug "[shell] Calling the shell with" cmd) 157 | (apply eval/sh cmd)))) 158 | 159 | (defn ^:no-project-needed shell 160 | "For shelling out from Leiningen. Useful for adding stuff to prep-tasks like 161 | `make` or similar commands, which currently has no Leiningen plugin. If the 162 | process returns a nonzero exit code, this command will force Leiningen to exit 163 | with the same exit code. 164 | 165 | Call through `lein shell cmd arg1 arg2 ... arg_n`." 166 | [project & cmd] 167 | (let [cmd (mapv #(param-expand project %) cmd) 168 | exit-code (shell-with-project project cmd) 169 | exit-code-action (get-exit-code project cmd)] 170 | (case exit-code-action 171 | :ignore (main/debug (format "[shell] Ignoring exit code (is %d)" 172 | exit-code)) 173 | :default (if-not (zero? exit-code) 174 | (main/exit exit-code))))) 175 | -------------------------------------------------------------------------------- /test/leiningen/test/shell.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.test.shell 2 | (:use [clojure.test]) 3 | (:require [leiningen.shell :as shell])) 4 | 5 | (def ^:private replace-values @#'leiningen.shell/replace-values) 6 | 7 | (deftest test-replacement 8 | (let [p {:a {:a 'a :b 1 :c "2"} 9 | :foo "banana" 10 | :bar {:baz "zap"}}] 11 | (is (= (replace-values p "foo bar baz") "foo bar baz")) 12 | (is (= (replace-values p "${:foo}") "banana")) 13 | (is (= (replace-values p "${:baz:-not-found}") "not-found")) 14 | (is (= (replace-values p "${:baz:-foo: ${:foo}}") "foo: banana")) 15 | (is (= (replace-values p "${:recursive:-10 ${:thing:-delicious ${:foo}}s}") 16 | "10 delicious bananas")) 17 | 18 | (is (= (replace-values p "${[:a :b]}") "1")) 19 | (is (= (replace-values p "${[:a :c]}") "2")) 20 | (is (= (replace-values p "${[:a :a]}") "a")) 21 | 22 | (is (thrown-with-msg? Exception #"Unexpected end of argument" 23 | (replace-values p "${:a"))) 24 | (is (thrown-with-msg? Exception #"Unexpected end of argument" 25 | (replace-values p "${:a:-Only partially closed: ${:b}"))) 26 | 27 | (is (thrown-with-msg? Exception #"Expected \{ after \$" 28 | (replace-values p "$foo"))))) 29 | --------------------------------------------------------------------------------