├── .github └── workflows │ └── test.yml ├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cask ├── LICENSE ├── README.md ├── flex-compile-base.el ├── flex-compile-choice-program.el ├── flex-compile-cli.el ├── flex-compile-clojure.el ├── flex-compile-comint.el ├── flex-compile-command.el ├── flex-compile-config.el ├── flex-compile-ess.el ├── flex-compile-lisp.el ├── flex-compile-make.el ├── flex-compile-manage.el ├── flex-compile-org-export.el ├── flex-compile-python.el ├── flex-compile-repl.el ├── flex-compile-script.el ├── flex-compile-single-buffer.el ├── flex-compile-xml-validate.el ├── flex-compile.el ├── flex-compiler.el ├── makefile └── test └── flex-compile-test.el /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | paths-ignore: 7 | - '**.md' 8 | - 'doc/*' 9 | branches: 10 | - master 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | emacs_version: 18 | - 27.1 19 | steps: 20 | - name: Checkout reposistory 21 | uses: actions/checkout@v2.4.0 22 | with: 23 | submodules: recursive 24 | 25 | - uses: actions/setup-python@v2 26 | with: 27 | python-version: '3.10' 28 | architecture: 'x64' 29 | 30 | - uses: purcell/setup-emacs@master 31 | with: 32 | version: ${{ matrix.emacs_version }} 33 | 34 | - name: Setup Cask 35 | uses: conao3/setup-cask@master 36 | with: 37 | version: 'snapshot' 38 | 39 | - name: Run tests 40 | run: 'make test' 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.elc 2 | index.text 3 | archive 4 | reference 5 | attic 6 | patches 7 | extra 8 | /.cask 9 | /dist 10 | /elpa 11 | /doc 12 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "zenbuild"] 2 | path = zenbuild 3 | url = https://github.com/plandes/zenbuild 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/). 6 | 7 | 8 | ## [Unreleased] 9 | 10 | 11 | ## [1.6] - 2025-04-11 12 | ### Added 13 | - Script compiler: add Clojure mode for babashka. 14 | 15 | ### Changed 16 | - Compatibility updates for customization. 17 | 18 | 19 | ## [1.5] - 2024-10-18 20 | ### Added 21 | - Support for LaTeX PDF export in the `org-export` flex compiler. 22 | 23 | 24 | ## [1.4] - 2023-08-05 25 | ### Changed 26 | - Better Common Lisp REPL startup flow. 27 | - Integrate [zensols.showfile] into Org Mode export. 28 | 29 | 30 | ## [1.3] - 2023-05-23 31 | ### Changed 32 | - Rename `slime` compiler to `lisp` for comparability with other compilers. 33 | For better or worse, compilers are named after the languages the compile 34 | rather than the system they use. 35 | 36 | 37 | ## [1.2] - 2023-05-22 38 | ### Changed 39 | - Add Lisp with Slime interaction flex compiler. 40 | 41 | 42 | ## [1.1] - 2023-01-16 43 | ### Changed 44 | - Bug fixes. 45 | - Comint compiler uses faster make compiler like content prompting. 46 | - Make compiler toggles previous target. 47 | 48 | 49 | ## [1.0] - 2022-02-04 50 | ### Added 51 | - A Feature to the make compiler that will display the compilation buffer only 52 | on errors when configured to do so. 53 | 54 | 55 | ## [0.10] - 2021-12-03 56 | ### Added 57 | - A *clean all* functionality for flex compilers (like `make`) that support 58 | it. For the `make` compiler, this calls the `cleanall` target. 59 | 60 | ### Changed 61 | - Version bump. 62 | 63 | 64 | ## [0.9] - 2021-09-14 65 | ### Added 66 | - CLI compiler help and usage. 67 | 68 | ### Changed 69 | - Refactor CLI compiler property lists. 70 | 71 | 72 | ## [0.8] - 2021-09-13 73 | ### Added 74 | - Added interactive [Zensols action command line interface] `cli` compiler 75 | (`flex-compile-cli.el`). 76 | 77 | ### Changed 78 | - Better markdown compiler documentation generation. 79 | 80 | 81 | ## [0.7] - 2020-12-17 82 | Major refactoring: cleaned up compilation and *package-lint* warnings. 83 | 84 | ### Changed 85 | - Upgraded to Emacs [zenbuild]. 86 | - Compat with recent *flycheck* and `package-lint`. 87 | - Minor bug fixes. 88 | - Script compiler now allows no arguments. 89 | 90 | 91 | ## [0.6] - 2020-05-06 92 | ### Added 93 | - Adding [zenbuild]. 94 | - Add secondary run target to the make compiler. 95 | 96 | ### Changed 97 | - Clear up `package-lint` warnings. 98 | - Rename top level interactive functions to include `flex-compiler-do` as part 99 | of effort to reduce warnings. 100 | 101 | ### Warnings 102 | - Check `custom.el` for old names and use `flex-compile-key-bindings` to bind 103 | keys to the correct functions. See the `README.md` for more information. 104 | 105 | 106 | ## [0.5] - 2019-06-20 107 | ### Added 108 | - New simple `comint' compiler. 109 | 110 | ### Changed 111 | - Python and Clojure flex compiler bug fixes. 112 | 113 | 114 | ## [0.4] - 2019-06-17 115 | ### Added 116 | - New configuration system that moves the responsibility out of the specific 117 | compilers for configuration. Configuration meta data has been added to 118 | [Buffer Manage] and *flex compile* extends that frame work to configure each 119 | compiler. 120 | 121 | 122 | ## [0.3] - 2019-06-15 123 | ### Added 124 | - Option to start REPLs and script/argument based compilers in a configured 125 | directory. 126 | 127 | ### Changed 128 | - Refactoring of persistent framework to be more inline with `config-manage` 129 | dependency. 130 | 131 | ### Removed 132 | - Support for Scala. After the major refactoring it's non-trivial to retrofit 133 | it to the new framework and I'm currently no longer writing Scala. I'm glad 134 | to work with anyone willing to do this work. 135 | 136 | 137 | ## [0.2] - 2017-11-07 138 | ### Added 139 | - Added [Choice Program] compiler. 140 | 141 | ### Changed 142 | - Fixed `buffer-manage` clobbering with compile warning issues. 143 | 144 | 145 | ## [0.1] - 2017 146 | 147 | ### Added 148 | - Choice program compiler. 149 | - Fixed compiler configuration merge with new registered compilers bug. 150 | 151 | ### Changed 152 | - More documentation. 153 | - Optionally prompt for evaluate form using `flex-compile-eval`. Before this 154 | function would obligate the user to provide a confirmed for to execute. 155 | - Fix native order for the Python compiler setup on compile. 156 | 157 | ## [0.1] - 2017-09-17 158 | First major release. 159 | 160 | 161 | [Unreleased]: https://github.com/plandes/flex-compile/compare/v1.6...HEAD 162 | [1.6]: https://github.com/plandes/flex-compile/compare/v1.5...v1.6 163 | [1.5]: https://github.com/plandes/flex-compile/compare/v1.4...v1.5 164 | [1.4]: https://github.com/plandes/flex-compile/compare/v1.3...v1.4 165 | [1.3]: https://github.com/plandes/flex-compile/compare/v1.2...v1.3 166 | [1.2]: https://github.com/plandes/flex-compile/compare/v1.1...v1.2 167 | [1.1]: https://github.com/plandes/flex-compile/compare/v1.0...v1.1 168 | [1.0]: https://github.com/plandes/flex-compile/compare/v0.10...v1.0 169 | [0.10]: https://github.com/plandes/flex-compile/compare/v0.9...v0.10 170 | [0.9]: https://github.com/plandes/flex-compile/compare/v0.8...v0.9 171 | [0.8]: https://github.com/plandes/flex-compile/compare/v0.7...v0.8 172 | [0.7]: https://github.com/plandes/flex-compile/compare/v0.6...v0.7 173 | [0.6]: https://github.com/plandes/flex-compile/compare/v0.5...v0.6 174 | [0.5]: https://github.com/plandes/flex-compile/compare/v0.4...v0.5 175 | [0.4]: https://github.com/plandes/flex-compile/compare/v0.3...v0.4 176 | [0.3]: https://github.com/plandes/flex-compile/compare/v0.2...v0.3 177 | [0.2]: https://github.com/plandes/flex-compile/compare/v0.1...v0.2 178 | [0.1]: https://github.com/plandes/flex-compile/compare/772d70f...v0.1 179 | 180 | 181 | [Choice Program]: https://github.com/plandes/choice-program 182 | [Buffer Manage]: https://github.com/plandes/buffer-manage 183 | [zenbuild]: https://github.com/plandes/zenbuild 184 | [Zensols action command line interface]: https://plandes.github.io/util/doc/command-line.html 185 | [zensols.showfile]: https://github.com/plandes/showfile 186 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | If you discover issues, have ideas for improvements or new features, please 4 | report them to the [issue tracker][1] of the repository or submit a pull 5 | request. Please, try to follow these guidelines when you do so. 6 | 7 | 8 | ## Issue reporting 9 | 10 | * Check that the issue has not already been reported. 11 | * Check that the issue has not already been fixed in the latest code 12 | (a.k.a. `master`). 13 | * Be clear, concise and precise in your description of the problem. 14 | * Open an issue with a descriptive title and a summary in grammatically correct, 15 | complete sentences. 16 | * Mention your Emacs version version and operating system. 17 | * Include any relevant code to the issue summary. 18 | 19 | 20 | ### Reporting bugs 21 | 22 | Adding information like the backtrace and the REPL messages to the bug report 23 | makes it easier to track down bugs. Some steps to reproduce a bug reliably 24 | would also make a huge difference. 25 | 26 | 27 | ## Pull requests 28 | 29 | * Read [how to properly contribute to open source projects on Github][2]. 30 | * Use a topic branch to easily amend a pull request later, if necessary. 31 | * Use the same coding conventions as the rest of the project. 32 | * Make sure that the unit tests are passing (`make test`). 33 | * Write [good commit messages][3]. 34 | * Mention related tickets in the commit messages (e.g. `[Fix #N] Add command ...`). 35 | * Update the [changelog][6]. 36 | * [Squash related commits together][5]. 37 | * Open a [pull request][4] that relates to *only* one subject with a clear title 38 | and description in grammatically correct, complete sentences. 39 | 40 | 41 | [1]: https://github.com/plandes/someproj/issues 42 | [2]: http://gun.io/blog/how-to-github-fork-branch-and-pull-request 43 | [3]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html 44 | [4]: https://help.github.com/articles/using-pull-requests 45 | [5]: http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html 46 | [6]: https://github.com/plandes/someproj/blob/master/CHANGELOG.md 47 | -------------------------------------------------------------------------------- /Cask: -------------------------------------------------------------------------------- 1 | ;; -*- emacs-lisp -*- 2 | (source gnu) 3 | (source melpa-stable) 4 | 5 | (depends-on "dash" "2.17.0") 6 | (depends-on "buffer-manage" "1.1") 7 | 8 | (package-file "flex-compile.el") 9 | 10 | (files "*.el" "doc/*.texi" (:exclude ".dir-locals.el")) 11 | 12 | (development 13 | (depends-on "package-lint") 14 | (depends-on "ert-runner")) 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flexible Evaluation and Compilation 2 | 3 | [![MELPA badge][melpa-badge]][melpa-link] 4 | [![MELPA stable badge][melpa-stable-badge]][melpa-stable-link] 5 | [![Build Status][build-badge]][build-link] 6 | 7 | 8 | Run, evaluate and compile functionality for a variety of different languages 9 | and modes. The specific "compilation" method is different across each add-on 10 | library, which are called *flexible compilers*. For example, for ESS and 11 | Clojure you can evaluate a specific file and/or evaluate a specfic expression 12 | via a REPL. For running a script or starting a `make` an async process is 13 | started. 14 | 15 | 16 | 17 | ## Table of Contents 18 | 19 | - [Introduction](#introduction) 20 | - [Motivation](#motivation) 21 | - [Configuration](#configuration) 22 | - [Usage](#usage) 23 | - [Special Cases](#special-cases) 24 | - [Compiler Configuration](#compiler-configuration) 25 | - [Revealing The Interactive Buffer](#revealing-the-interactive-buffer) 26 | - [Compilers](#compilers) 27 | - [Choice program](#choice-program) 28 | - [CLI Python](#cli-python) 29 | - [Clojure](#clojure) 30 | - [Comint](#comint) 31 | - [Command](#command) 32 | - [Do nothing](#do-nothing) 33 | - [Emacs speaks statistics](#emacs-speaks-statistics) 34 | - [Lisp](#lisp) 35 | - [Make](#make) 36 | - [Org mode](#org-mode) 37 | - [Python](#python) 38 | - [Script](#script) 39 | - [XML](#xml) 40 | - [Invoking a Compiler Programmatically](#invoking-a-compiler-programmatically) 41 | - [Writing Your Own Compiler](#writing-your-own-compiler) 42 | - [Changelog](#changelog) 43 | - [License](#license) 44 | 45 | 46 | 47 | ## Introduction 48 | 49 | The general idea is the keybindings to compile you get use to are "switched" to 50 | whatever specific problem you're working on. For example, if you're compiling 51 | with a makefile, use the *make* compiler to initiate an make process. If 52 | you're working with a REPL based langauage (i.e. [flex-compile-python], 53 | [flex-compile-clojure] etc) instead a buffer or expression is evaluated. 54 | 55 | 56 | ### Motivation 57 | 58 | Many of the "compilers" (i.e. the [flex-compile-command] and 59 | [flex-compile-script]) don't do much more than invoke a function or execute a 60 | script. However, when jumping between several development tasks the strength 61 | of the library comes from easily switching between compilers using the same 62 | keybindings and finger muscle memory to invoke them. 63 | 64 | 65 | ## Configuration 66 | 67 | To install the package from [MELPA], add the following to your `~/.emacs` file: 68 | ```emacs-lisp 69 | (unless (assoc-default "melpa-stable" package-archives) 70 | (add-to-list 'package-archives 71 | '("melpa-stable" . "https://stable.melpa.org/packages/") t)) 72 | 73 | (use-package flex-compile 74 | :ensure t 75 | ;; recommended global key bindings 76 | :bind (("C-x C-p" . flex-compiler-do-activate) 77 | ("C-x C-u" . flex-compiler-do-compile) 78 | ("C-x C-y" . flex-compiler-do-clean) 79 | ("C-x C-i" . flex-compiler-do-run-or-set-config) 80 | ;; clobbers `delete-blank-lines' 81 | ("C-x C-o" . flex-compiler-do-eval))) 82 | ``` 83 | This loads the library and creates global key bindings. 84 | 85 | 86 | ## Usage 87 | 88 | Most flexible compilers (any subclass of `config-flex-compiler`) define a 89 | specific source file called the *config* file. 90 | 91 | There are the operations (also included are the [given](#key-bindings) key 92 | bindings): 93 | * **Choose a Compiler** (`C-x C-p` or `M-x flex-compiler-do-activate`): 94 | select/activate a flex compiler. 95 | * **Set Configuration** (`C-u C-x C-u` or `C-u M-x flex-compiler-do-compile`): 96 | This allows the configuration of the compiler. In most compilers the first 97 | step is to select the property, then the configuration for that property is 98 | prompted. In some cases a specific property is set, like the [Make](#make) 99 | compiler jumps to the target. All 100 | properties are always available for configuration with `C-u 0 C-x C-u`. 101 | * **Select or Get Information** (`M-x flex-compiler-do-list`): This displays a 102 | list of available compilers that are selectable with RET or provide 103 | documentation and configuration with `?`. In addition, `e` configures the 104 | compiler. 105 | * **Prompt for Config File** (`C-u 1 C-x C-u` or `C-u 1 M-x 106 | flex-compiler-do-compile`): This the file the compiler will use. For example, 107 | the [Script](#script) compiler will run selected script file and display the 108 | output on compile. For REPL compilers, like the [Python](#python) copiler, 109 | the starting directory can also be set (see **Set Configuration**).. 110 | * **Set Config File** (`C-u 1 C-x C-i` or `C-u 1 111 | flex-compiler-do-run-or-set-config`): This sets the *config* file to compile, run 112 | or interpret. The term *config* is a nomenclature and examples include 113 | `Makefile`, `.clj`, `.r`, `.sh` files. 114 | * **Go to Config File** (`C-u C-x C-i` or `C-u M-x 115 | flex-compiler-do-run-or-set-config`): This displays the *config* file/buffer to 116 | the current buffer. For some compilers, this also displays the interactive 117 | (i.e. REPL) buffer. See the [reveal 118 | buffer](#revealing-the-interactive-buffer) section for more information, and 119 | specifically how to force show the buffer. 120 | * **Compile** (`C-x C-u` or `M-x flex-compiler-do-compile`): This is the default 121 | *make something* command. For make it invokes the first target, for REPL 122 | languages like Clojure and ESS it evaluates a `.clj` or `.r` file. 123 | * **Run** (`C-x C-i` or `M-x flex-compiler-do-run-or-set-config`): This starts 124 | invokes a `run` target for make, starts the REPL for REPL type languages. 125 | * **Evaluate** (`C-x C-o` or `M-x flex-compiler-do-eval`): This invokes the 126 | compiler's evaluation functionality. For REPL based languages, this 127 | evaluates the current form and stores the result in the kill buffer. 128 | * **Clean** (`C-x C-y` or `M-x flex-compiler-do-clean`): This invokes the `clean` 129 | target for make and kills the REPL for REPL based compilers. 130 | * **Generate Compiler Docs** (`M-x flex-compiler-doc-show`): this generates the 131 | documentation given in the [compilers](#compilers) section (verbatim). 132 | 133 | Each compiler also has configuration setting ability, which is invoked with the 134 | `C-u` universal argument to the compile `C-x C-u` invocation per the 135 | aforementioned `flex-compiler-do-run-or-set-config`. 136 | 137 | 138 | ### Special Cases 139 | 140 | Some compilers are configured differently for the default configuration key 141 | binding. For example the [make](#make) compiler sets the make target defined 142 | in the make file, and in this case uses `C-u 0 C-u` to set configuration 143 | properties. 144 | 145 | 146 | ## Compiler Configuration 147 | 148 | This package uses a configuration system that moves the responsibility out of 149 | the specific compilers for configuration. This package extends from the 150 | `config-manage` framework in the [buffer manage] library by extending and 151 | building configuration meta data. For example, the `M-x flex-compiler-list` 152 | lists available compiler with `?` providing information and `e` configuring the 153 | compiler. 154 | 155 | 156 | ### Revealing The Interactive Buffer 157 | 158 | One of the features of the *flex compiler* library is it provides specific 159 | behavior on example where and how to display buffers, which is a compiler 160 | configuration. These buffers are usually interactive buffers, i.e. REPL 161 | buffers. Any compiler that extends `single-buffer-flex-compiler` has this 162 | capability, which include the following: 163 | 164 | * [flex-compile-make] 165 | * [flex-compile-command] 166 | * [flex-compile-cli] 167 | * [flex-compile-script] 168 | * [flex-compile-clojure] 169 | * [flex-compile-python] 170 | * [flex-compile-ess] 171 | * [flex-compile-xml-validate] 172 | * [flex-compile-choice-program] 173 | 174 | 175 | The two properties for these compilers include: 176 | 177 | * **Buffer New Mode**: used when the interactive buffer is created. 178 | * **Buffer Exists Mode**: used when the interactive buffer is already exists. 179 | 180 | 181 | These can be set to the *Global* settings, which means to take it from the 182 | customzied variables `flex-compile-single-buffer-display-buffer-new-mode` and 183 | `flex-compile-single-buffer-display-buffer-exists-mode`. These variables and 184 | the compiler level properties can be set to one of: 185 | 186 | * **Switch to Buffer** means to first pop then switch to the buffer. 187 | * **Display Buffer** means to show the buffer in a different window. 188 | * **Next Frame Otherwise Switch** means to use the next frame if there are 189 | multiple frames, otherwise pop and switch to the buffer. 190 | * **Next Frame Otherwise Display** means to use the next frame if there are 191 | multiple frames, otherwise show buffer. 192 | * **Next Frame Skip Switch** means to do nothing there are multiple frames, 193 | otherwise pop and switch to the buffer. 194 | * **Next Frame Skip Display** means to do nothing there are multiple frames, 195 | otherwise display the buffer. 196 | * **Never** means to never show the buffer. 197 | * **Only If Error** means to display the buffer if at least one error was 198 | detected such a compilation error with the [make](#make) compiler. **Note**: 199 | and not all compilers support this option. 200 | 201 | The decision of where to show a buffer (or not) happens either when the 202 | interative buffer is created or during a compilation. In many cases you might 203 | not want to ever show the buffer, so you could set both buffer `new` and 204 | `exist` properties to *never*. In this case, you still force the interactive 205 | buffer with `C-u 2 C-x C-i` or `C-u 2 M-x flex-compiler-run-or-set-config`. 206 | 207 | 208 | ## Compilers 209 | 210 | Concrete instances of *flexible* compilers that provide a common interface. 211 | Each is an implementation of glue code to the respective compilation method. 212 | 213 | Note that all compilers that extend from `conf-file-flex-compiler`, which 214 | include `make`, `script`, `xml-validate`, `org-mode`, `python`, `clojure`, and 215 | `ess` have their `start-directory` property unset each time the `config-file` 216 | is set. 217 | 218 | This documentation was generated with `M-x flex-compiler-doc-show`. 219 | 220 | 221 | ### Choice program 222 | 223 | Prompt and more easily invoke choice/action based programs using the 224 | [Choice Program](https://github.com/plandes/choice-program) Emacs library. 225 | 226 | Properties: 227 | * Program: An instance of `choice-program`. 228 | * Action: The action to invoke on the program. 229 | * Buffer Exists Mode: Compiler instance of `flex-compile-single-buffer-display-buffer-exists-mode`. 230 | * Buffer New Mode: Compiler instance of `flex-compile-single-buffer-display-buffer-new-mode`. 231 | * Kill Buffer Clean: If non-nil kill the buffer on clean. 232 | 233 | 234 | ### CLI Python 235 | 236 | Provides support for user input for action mnemonics and options using Python 237 | programs that use the 238 | [Zensols action CLI](https://plandes.github.io/util/doc/command-line.html). 239 | 240 | This compiler gets the command line metadata as a list of actions and their 241 | respective positional and option arguments. It this prompts the user with 242 | documentation associated, first the action, then the action's arguments. 243 | Finally, it constructs the command line and executes the Python program with 244 | the arguments. 245 | 246 | Properties: 247 | * Action: The action to invoke on the program. 248 | * Config File: The file to use for *configuring* the compiler. 249 | * Arguments: The arguments to give to the script. 250 | * Cache Metadata: 251 | Whether or not to cache the Python program's CLI metadata. 252 | * Buffer Exists Mode: Compiler instance of `flex-compile-single-buffer-display-buffer-exists-mode`. 253 | * Buffer New Mode: Compiler instance of `flex-compile-single-buffer-display-buffer-new-mode`. 254 | * Kill Buffer Clean: If non-nil kill the buffer on clean. 255 | * Start Directory: The directory for starting the compilation. 256 | 257 | 258 | ### Clojure 259 | 260 | This is a REPL based compiler that allows for evaluation Clojure 261 | buffers, expressions and starting the REPL using 262 | [Cider](https://github.com/clojure-emacs/cider). 263 | 264 | The Clojure compiler connects using two Cider modes: the default `jack-in` 265 | mode or connecting to a host and port remotely with `cider-connect`. You can 266 | switch betwee these two methods with the [given keybindings](#key-bindings): 267 | 268 | `M-x 1 C-u C-x C-u` 269 | 270 | See documetation with `M-h f flex-compiler-query-eval` method for more 271 | inforamtion (and current binding). 272 | 273 | Todo: support multiple Cider buffers as this implementation currently does 274 | not. 275 | 276 | Properties: 277 | * Config File: The file to use for *configuring* the compiler. 278 | * Buffer Exists Mode: Compiler instance of `flex-compile-single-buffer-display-buffer-exists-mode`. 279 | * Buffer New Mode: Compiler instance of `flex-compile-single-buffer-display-buffer-new-mode`. 280 | * Kill Buffer Clean: If non-nil kill the buffer on clean. 281 | * Connect Mode: Defines how to connect to a Clojure REPL. 282 | * Repl Port: The port running the REPL; default: 32345 283 | * Output Clear: Whether or not to clear comint buffer after a compilation. 284 | * Prompt Kill Repl Buffer: If non-`nil` then prompt to kill a REPL buffer on clean. 285 | * Repl Buffer Start Timeout: Number of seconds as an integer to wait to start before giving up (and not 286 | displaying). 287 | * Repl Buffer Start Wait: Number of seconds (as a float) to wait before issuing any first command to the 288 | REPL. 289 | * Start Directory: The directory for starting the compilation. 290 | 291 | 292 | ### Comint 293 | 294 | Send text to any running `comint` buffer. 295 | This is useful for entering a command in a shell, SQL etc buffer that otherwise 296 | requires switching back and forth between buffers, which is a hassle. 297 | 298 | Properties: 299 | * Config File: The file to use for *configuring* the compiler. 300 | * Content: The string to insert in the buffer referred by the `buffer` slot. 301 | * Buffer: The buffer to insert the `content` slot. 302 | * Start Directory: The directory for starting the compilation. 303 | 304 | 305 | ### Command 306 | 307 | This "compiler" is more of a convenience to invoke an Emacs Lisp function or 308 | form. This is handy for functions that you end up invoking over and over with 309 | `M-x` (i.e. `cider-test-run-ns-tests`). See [motivation](#motivation). 310 | 311 | Properties: 312 | * Sexp: The symbol expression to evaluate. 313 | 314 | 315 | ### Do nothing 316 | 317 | A no-op compiler for the disabled state. 318 | 319 | 320 | ### Emacs speaks statistics 321 | 322 | This is a REPL based compiler to evaluate R code with 323 | [Emacs Speaks Statistics](https://ess.r-project.org) . 324 | 325 | Properties: 326 | * Config File: The file to use for *configuring* the compiler. 327 | * Buffer Exists Mode: Compiler instance of `flex-compile-single-buffer-display-buffer-exists-mode`. 328 | * Buffer New Mode: Compiler instance of `flex-compile-single-buffer-display-buffer-new-mode`. 329 | * Kill Buffer Clean: If non-nil kill the buffer on clean. 330 | * Output Clear: Whether or not to clear comint buffer after a compilation. 331 | * Prompt Kill Repl Buffer: If non-`nil` then prompt to kill a REPL buffer on clean. 332 | * Repl Buffer Start Timeout: Number of seconds as an integer to wait to start before giving up (and not 333 | displaying). 334 | * Repl Buffer Start Wait: Number of seconds (as a float) to wait before issuing any first command to the 335 | REPL. 336 | * Start Directory: The directory for starting the compilation. 337 | 338 | 339 | ### Lisp 340 | 341 | This is a REPL based compiler that allows for evaluation Lisp buffers and 342 | expressions using [slime](https://github.com/slime/slime). 343 | 344 | Properties: 345 | * Config File: The file to use for *configuring* the compiler. 346 | * Buffer Exists Mode: Compiler instance of `flex-compile-single-buffer-display-buffer-exists-mode`. 347 | * Buffer New Mode: Compiler instance of `flex-compile-single-buffer-display-buffer-new-mode`. 348 | * Kill Buffer Clean: If non-nil kill the buffer on clean. 349 | * Compile On Load: Whether to also compile when loading the source file. 350 | * Output Clear: Whether or not to clear comint buffer after a compilation. 351 | * Prompt Kill Repl Buffer: If non-`nil` then prompt to kill a REPL buffer on clean. 352 | * Repl Buffer Start Timeout: Number of seconds as an integer to wait to start before giving up (and not 353 | displaying). 354 | * Repl Buffer Start Wait: Number of seconds (as a float) to wait before issuing any first command to the 355 | REPL. 356 | * Start Directory: The directory for starting the compilation. 357 | 358 | 359 | ### Make 360 | 361 | This compiler invokes make as an asynchronous process in a 362 | buffer. The first target, `run` target, and `clean` target are 363 | invoked respectfully with *compile*, *run* and *clean* Emacs 364 | commands (see [usage](#usage)). 365 | 366 | This is a special compiler in it's configuration. Instead of 367 | setting properties, the default configuration mechanism is to set 368 | the make target instead. If you want to set a flex compiler 369 | property, use `\C-u 0 \C-u`. 370 | 371 | When setting the configuration file the target property is unset. 372 | 373 | Properties: 374 | * Config File: The file to use for *configuring* the compiler. 375 | * Target: The make file target to satisfy. 376 | * Run Target: The target used to run or test as the secondary compilation functionality. 377 | * Buffer Exists Mode: Compiler instance of `flex-compile-single-buffer-display-buffer-exists-mode`. 378 | * Buffer New Mode: Compiler instance of `flex-compile-single-buffer-display-buffer-new-mode`. 379 | * Kill Buffer Clean: If non-nil kill the buffer on clean. 380 | * Start Directory: The directory for starting the compilation. 381 | 382 | 383 | ### Org mode 384 | 385 | This compiler exports [Org mode](https://orgmode.org) to external formats and 386 | then shows the output in the browser. Only HTML is currently supported. 387 | 388 | Properties: 389 | * Config File: The file to use for *configuring* the compiler. 390 | * Export Fn: The Org mode export function. 391 | * Open File: Whether to open the file after exported. 392 | * Output Directory: The output directory. 393 | * Frame Focus Command: 394 | The command to refocus the Emacs frame after rendering the output (browser). 395 | * Frame Focus Delay: Seconds before refocusing the Emacs frame after redering the output. 396 | * Start Directory: The directory for starting the compilation. 397 | 398 | 399 | ### Python 400 | 401 | This is a REPL based compiler that allows for evaluation Python buffers and 402 | expressions using [python mode](https://github.com/fgallina/python.el). 403 | 404 | Properties: 405 | * Config File: The file to use for *configuring* the compiler. 406 | * Buffer Exists Mode: Compiler instance of `flex-compile-single-buffer-display-buffer-exists-mode`. 407 | * Buffer New Mode: Compiler instance of `flex-compile-single-buffer-display-buffer-new-mode`. 408 | * Kill Buffer Clean: If non-nil kill the buffer on clean. 409 | * Output Clear: Whether or not to clear comint buffer after a compilation. 410 | * Prompt Kill Repl Buffer: If non-`nil` then prompt to kill a REPL buffer on clean. 411 | * Repl Buffer Start Timeout: Number of seconds as an integer to wait to start before giving up (and not 412 | displaying). 413 | * Repl Buffer Start Wait: Number of seconds (as a float) to wait before issuing any first command to the 414 | REPL. 415 | * Start Directory: The directory for starting the compilation. 416 | 417 | 418 | ### Script 419 | 420 | This compiler runs a script with optional arguments in an async buffer. 421 | See [motivation](#motivation). 422 | 423 | Properties: 424 | * Config File: The file to use for *configuring* the compiler. 425 | * Buffer Exists Mode: Compiler instance of `flex-compile-single-buffer-display-buffer-exists-mode`. 426 | * Buffer New Mode: Compiler instance of `flex-compile-single-buffer-display-buffer-new-mode`. 427 | * Kill Buffer Clean: If non-nil kill the buffer on clean. 428 | * Arguments: The arguments to give to the script. 429 | * Start Directory: The directory for starting the compilation. 430 | 431 | 432 | ### XML 433 | 434 | Implementation compiler for XML validation using command line 435 | [xmllint](http://xmlsoft.org/xmllint.html) command line tool. 436 | 437 | Properties: 438 | * Config File: The file to use for *configuring* the compiler. 439 | * Schema File: Location of the schema file to validate against. 440 | * Buffer Exists Mode: Compiler instance of `flex-compile-single-buffer-display-buffer-exists-mode`. 441 | * Buffer New Mode: Compiler instance of `flex-compile-single-buffer-display-buffer-new-mode`. 442 | * Kill Buffer Clean: If non-nil kill the buffer on clean. 443 | * Start Directory: The directory for starting the compilation. 444 | 445 | 446 | ## Invoking a Compiler Programmatically 447 | 448 | There might be instances where it's necessary to execute other actions along 449 | with a compilation. If you know Emacs Lisp, you can create your own function 450 | to invoke other compilers, and then use the [Command](#command) compiler to 451 | invoke it as a command (an interactive function), such as: 452 | 453 | ```lisp 454 | (defun clean-and-call-python-compiler () 455 | "Invoke `make clean', then call the Python compiler." 456 | (interactive) 457 | (shell-command "make clean") 458 | (let ((this (flex-compiler-by-name "python"))) 459 | (flex-compiler-compile this))) 460 | ``` 461 | 462 | Other Emacs Lisp snippets that do things with compilers: 463 | ```lisp 464 | ;; names of compilers 465 | (let* ((this flex-compile-manage-inst) 466 | (names (config-manager-entry-names this))) 467 | names) 468 | 469 | ;; the currently set compiler's name and descriptor 470 | (let* ((this flex-compile-manage-inst) 471 | (active (flex-compile-manager-active this))) 472 | (format "Current compiler: %s (desc: %s)" 473 | (slot-value active 'object-name) 474 | (slot-value active 'description))) 475 | 476 | ;; get a compiler by name and set its configuration file 477 | (let ((this (flex-compiler-by-name "python"))) 478 | (with-slots (config-file start-directory) this 479 | (setq config-file "~/work/src/example.py" 480 | start-directory "~/work"))) 481 | 482 | ;; run the compiler 483 | (let ((this (flex-compiler-by-name "python"))) 484 | (flex-compiler-compile this)) 485 | 486 | ;; set the active compilerby name 487 | (let ((this flex-compile-manage-inst)) 488 | (config-manager-activate this "python")) 489 | ``` 490 | 491 | 492 | ## Writing Your Own Compiler 493 | 494 | Writing your own flexible compiler is pretty easy if you know how to write 495 | Emacs object oriented programs. Even if you don't, you should be able to find 496 | a compiler already written that follows your use case among those concerete 497 | (see [compilers](#compilers)) that are already given in this package. You can 498 | also submit an issue (see the [contributing instructions](CONTRIBUTING.md)). 499 | 500 | 501 | ## Changelog 502 | 503 | An extensive changelog is available [here](CHANGELOG.md). 504 | 505 | 506 | ## License 507 | 508 | Copyright © 2017-2025 Paul Landes 509 | 510 | GNU Lesser General Public License, Version 2.0 511 | 512 | 513 | 514 | [flex-compile-make]: #make 515 | [flex-compile-command]: #command 516 | [flex-compile-cli]: #cli-python-file 517 | [flex-compile-script]: #script 518 | [flex-compile-clojure]: #clojure 519 | [flex-compile-python]: #python 520 | [flex-compile-ess]: #ess 521 | [flex-compile-xml-validate]: #xml-validation 522 | [flex-compile-choice-program]: #choice-program 523 | 524 | [melpa-link]: https://melpa.org/#/flex-compile 525 | [melpa-stable-link]: https://stable.melpa.org/#/flex-compile 526 | [melpa-badge]: https://melpa.org/packages/flex-compile-badge.svg 527 | [melpa-stable-badge]: https://stable.melpa.org/packages/flex-compile-badge.svg 528 | [build-badge]: https://github.com/plandes/flex-compile/workflows/CI/badge.svg 529 | [build-link]: https://github.com/plandes/flex-compile/actions 530 | 531 | [MELPA]: https://melpa.org/ 532 | 533 | [buffer manage]: https://github.com/plandes/buffer-manage 534 | -------------------------------------------------------------------------------- /flex-compile-base.el: -------------------------------------------------------------------------------- 1 | ;;; flex-compile-base.el --- Manager for flexible compilers -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2015 - 2025 Paul Landes 4 | 5 | ;; Author: Paul Landes 6 | ;; Maintainer: Paul Landes 7 | ;; Keywords: compilation integration processes 8 | ;; URL: https://github.com/plandes/flex-compile 9 | ;; Package-Requires: ((emacs "26.1")) 10 | ;; Package-Version: 0 11 | 12 | ;; This file is not part of GNU Emacs. 13 | 14 | ;; This program is free software; you can redistribute it and/or modify 15 | ;; it under the terms of the GNU General Public License as published by 16 | ;; the Free Software Foundation; either version 2, or (at your option) 17 | ;; any later version. 18 | 19 | ;; This program is distributed in the hope that it will be useful, 20 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | ;; GNU General Public License for more details. 23 | 24 | ;; You should have received a copy of the GNU General Public License 25 | ;; along with this program; if not, write to the Free Software 26 | ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, 27 | ;; Boston, MA 02110-1301, USA. 28 | 29 | ;;; Commentary: 30 | 31 | ;; This file contains the library Emacs group definitions and the base class 32 | ;; compiler. 33 | 34 | ;;; Code: 35 | 36 | (require 'eieio) 37 | (require 'config-manage) 38 | (require 'config-manage-prop) 39 | 40 | (defgroup flex-compile nil 41 | "Compile Helper Functions." 42 | :group 'tools 43 | :group 'compilation) 44 | 45 | (defclass flex-compiler (config-prop-entry) 46 | () 47 | :abstract true 48 | :method-invocation-order :c3 49 | :documentation "Base class for compilation executors (do the work). 50 | Instances of this class are also persistable and their state is stored in a 51 | configuration file.") 52 | 53 | (cl-defmethod initialize-instance ((this flex-compiler) &optional slots) 54 | "Initialize instance THIS with arguments SLOTS." 55 | (if (null (plist-get slots :description)) 56 | (setq slots (plist-put slots :description 57 | (capitalize (plist-get slots :object-name))))) 58 | (cl-call-next-method this slots)) 59 | 60 | (cl-defmethod flex-compiler-load-libraries ((this flex-compiler)) 61 | "Call back for to load and require libraries needed by THIS compiler." 62 | (ignore this)) 63 | 64 | (cl-defmethod flex-compiler-save-config ((this flex-compiler)) 65 | "Tell the compiler manager to persist the configuration of all compilers. 66 | THIS is the object instance." 67 | (with-slots (manager) this 68 | (unless manager 69 | (error "No manager set in compiler: %S" 70 | (with-temp-buffer 71 | (cl-print-object this (current-buffer))))) 72 | (config-persistable-save manager))) 73 | 74 | (cl-defmethod flex-compiler-reset-state ((this flex-compiler)) 75 | "Reset all persistable slots to initial state. 76 | This implementation sets all slots to nil. 77 | THIS is the object instance." 78 | (config-persistent-reset this)) 79 | 80 | (cl-defmethod flex-compiler-run ((this flex-compiler)) 81 | "Invoke the run functionality of the compiler. 82 | THIS is the object instance." 83 | (config-persistent--unimplemented this "run")) 84 | 85 | (cl-defmethod flex-compiler-compile ((this flex-compiler)) 86 | "Invoke the compile functionality of the compiler. 87 | THIS is the object instance." 88 | (config-persistent--unimplemented this "compile")) 89 | 90 | (cl-defmethod flex-compiler-clean ((this flex-compiler) &optional allp) 91 | "Invoke the clean functionality of the compiler. 92 | if ALLP is non-nil, then invoke a more destructive cleaning when supported. 93 | THIS is the object instance." 94 | (ignore allp) 95 | (config-persistent--unimplemented this "clean")) 96 | 97 | (cl-defmethod config-entry-set-name ((this flex-compiler) name) 98 | "Disallow renaming with NAME from `config-manage-mode' as it is nonsensical. 99 | THIS is the object instance." 100 | (ignore this name) 101 | (config-persistent--unimplemented this "set-name")) 102 | 103 | (cl-defmethod flex-compiler-display-buffer ((this flex-compiler) 104 | &optional compile-def) 105 | "Called to display the compilation buffer \(if any) for THIS compiler. 106 | 107 | COMPILE-DEF is the compilation definition, which is usually an 108 | alist of having an alist with `newp' indicating if the buffer is 109 | new and `buffer' of the buffer just created. This is also called 110 | for clean invocations, in which case the value is nil. The 111 | value (when non-nil) is dependent on the flex-compiler." 112 | (ignore this compile-def)) 113 | 114 | (cl-defmethod flex-compiler-display-buffer-alist ((this flex-compiler)) 115 | "Return a value that will be bound to `display-buffer-alist' in THIS compiler. 116 | 117 | This suggests to Emacs libraries to not display buffers via 118 | `display-buffer'. This is so a `flex-compiler' can explictly 119 | control buffer display with `flex-compiler-display-buffer' \(if 120 | it chooses).." 121 | (ignore this) 122 | ;; `list' takes any number of arguments and has no side effects 123 | '((list . (list)))) 124 | 125 | 126 | 127 | (defclass no-op-flex-compiler (flex-compiler) 128 | () 129 | :documentation "A no-op compiler for the disabled state.") 130 | 131 | (cl-defmethod initialize-instance ((this no-op-flex-compiler) &optional slots) 132 | "Initialize instance THIS with arguments SLOTS." 133 | (setq slots (plist-put slots :object-name "disable") 134 | slots (plist-put slots :description "Do nothing")) 135 | (cl-call-next-method this slots)) 136 | 137 | (cl-defmethod config-persistent--unimplemented ((this no-op-flex-compiler) 138 | method) 139 | "Message that THIS compiler is disabled for persistance \(if tried). 140 | METHOD is the EIEIO method called that has no implementation." 141 | (ignore this) 142 | (message "Compiler is disabled for %S" method)) 143 | 144 | (cl-defmethod config-prop-entry-configure ((this no-op-flex-compiler) _) 145 | "Raise error for THIS no-op implementation." 146 | (config-persistent--unimplemented this "configure")) 147 | 148 | (provide 'flex-compile-base) 149 | 150 | ;;; flex-compile-base.el ends here 151 | -------------------------------------------------------------------------------- /flex-compile-choice-program.el: -------------------------------------------------------------------------------- 1 | ;;; flex-compile-choice-program.el --- Compile functions -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2015 - 2025 Paul Landes 4 | 5 | ;; Author: Paul Landes 6 | ;; Maintainer: Paul Landes 7 | ;; Keywords: choice-program compile flexible processes 8 | ;; URL: https://github.com/plandes/flex-compile 9 | ;; Package-Requires: ((emacs "26.1")) 10 | ;; Package-Version: 0 11 | 12 | ;; This file is not part of GNU Emacs. 13 | 14 | ;; This program is free software; you can redistribute it and/or modify 15 | ;; it under the terms of the GNU General Public License as published by 16 | ;; the Free Software Foundation; either version 2, or (at your option) 17 | ;; any later version. 18 | 19 | ;; This program is distributed in the hope that it will be useful, 20 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | ;; GNU General Public License for more details. 23 | 24 | ;; You should have received a copy of the GNU General Public License 25 | ;; along with this program; if not, write to the Free Software 26 | ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, 27 | ;; Boston, MA 02110-1301, USA. 28 | 29 | ;;; Commentary: 30 | 31 | ;; Implementation compiler for choice based programs. See: 32 | ;; https://github.com/plandes/choice-program 33 | 34 | ;;; Code: 35 | 36 | (require 'cl-lib) 37 | (require 'dash) 38 | (require 'choice-program-complete) 39 | (require 'flex-compile-manage) 40 | 41 | ;;; choice-program file compiler 42 | (defclass choice-program-flex-compiler (single-buffer-flex-compiler 43 | conf-flex-compiler) 44 | ((program :initarg :program 45 | :initform nil 46 | :documentation "An instance of `choice-program'.") 47 | (action :initarg :action 48 | :initform nil 49 | :documentation "The action to invoke on the program.")) 50 | :method-invocation-order :c3 51 | :documentation "\ 52 | Prompt and more easily invoke choice/action based programs using the 53 | \[Choice Program](https://github.com/plandes/choice-program) Emacs library.") 54 | 55 | (cl-defmethod initialize-instance ((this choice-program-flex-compiler) 56 | &optional slots) 57 | "Initialize the THIS instance with SLOTS." 58 | (let* ((read-prog (lambda (this compiler default prompt history) 59 | (ignore this) 60 | (flex-compiler-choice-program-read-program 61 | compiler default prompt history))) 62 | (read-action (lambda (this compiler default prompt history) 63 | (ignore this prompt) 64 | (-> (flex-compiler-choice-program-program compiler t) 65 | (choice-program-read-option default history)))) 66 | (props (list (config-eval-prop :object-name 'program 67 | :prompt "Program" 68 | :func read-prog 69 | :prop-entry this 70 | :required t 71 | :input-type 'last 72 | :order 0) 73 | (config-eval-prop :object-name 'action 74 | :prompt "Action" 75 | :func read-action 76 | :prop-entry this 77 | :required t 78 | :input-type 'last 79 | :order 1)))) 80 | (setq slots (plist-put slots :object-name "choice-program") 81 | slots (plist-put slots :description "Choice program") 82 | slots (plist-put slots :buffer-name "Choice Program") 83 | slots (plist-put slots :kill-buffer-clean t) 84 | slots (plist-put slots 85 | :props (append (plist-get slots :props) props)))) 86 | (cl-call-next-method this slots)) 87 | 88 | (cl-defmethod flex-compiler-load-libraries 89 | ((this choice-program-flex-compiler)) 90 | "Load the `choice-program' library for THIS compiler." 91 | (ignore this) 92 | (require 'choice-program)) 93 | 94 | (cl-defmethod flex-compiler-choice-program-map 95 | ((this choice-program-flex-compiler)) 96 | "Return an alist of name to registered to `choice-program' instances. 97 | THIS is the instance." 98 | (ignore this) 99 | (->> (choice-program-instances) 100 | (-map (lambda (this) 101 | (cons (choice-program-name this) this))))) 102 | 103 | (cl-defmethod flex-compiler-choice-program-read-program 104 | ((this choice-program-flex-compiler) default prompt history) 105 | "Read a `choice-program' from the user. 106 | DEFAULT, PROMPT and HISTORY are used for user input and come from 107 | the `flex-compile' framework. 108 | THIS is the instance." 109 | (let ((choices (->> (flex-compiler-choice-program-map this) 110 | (-map #'car) 111 | (-map #'intern)))) 112 | (choice-program-complete prompt choices t t nil history default))) 113 | 114 | (cl-defmethod flex-compiler-choice-program-program 115 | ((this choice-program-flex-compiler) &optional expectp) 116 | "Read an action for the \(already) selected `choice-program'. 117 | THIS is the instance. 118 | EXPECTP, if non-nil, raise an exception if the program slot is nil." 119 | (with-slots (program) this 120 | (if (and (null program) expectp) 121 | (error "No program set")) 122 | (when program 123 | (let ((ret (->> (flex-compiler-choice-program-map this) 124 | (assoc program) 125 | cdr))) 126 | (unless ret 127 | (error "No such program: `%S'" program)) 128 | ret)))) 129 | 130 | (cl-defmethod config-prop-set ((this choice-program-flex-compiler) prop val) 131 | "Set property PROP to VAL for THIS compiler. 132 | 133 | This also resets the `action' property when setting the `program' property." 134 | (when (eq (config-prop-name prop) 'program) 135 | (setf (slot-value this 'action) nil) 136 | ;; we need to some how be able to nil out the history when changing the 137 | ;; program as currently the history carries forward to other programs 138 | (config-persistent-reset (config-prop-by-name this 'action))) 139 | (cl-call-next-method this prop val)) 140 | 141 | (cl-defmethod flex-compiler-buffer-name ((this choice-program-flex-compiler)) 142 | "Return the buffer name of THIS compiler. 143 | 144 | Use the `buffer-name' slot of the `flex-compiler-choice-program-program' if 145 | non-nil, otherwise use the default `buffer-name' with syntax of the super 146 | class." 147 | (let ((prog (flex-compiler-choice-program-program this))) 148 | (if prog 149 | (slot-value prog 'buffer-name) 150 | (cl-call-next-method this)))) 151 | 152 | (cl-defmethod flex-compiler-start-buffer ((this choice-program-flex-compiler) 153 | start-type) 154 | "Return a new buffer for THIS compiler with a processing compilation. 155 | See the `single-buffer-flex-compiler' implementation of 156 | `flex-compiler-start-buffer' for more information and START-TYPE." 157 | (cl-case start-type 158 | (compile (let ((prog (flex-compiler-choice-program-program this)) 159 | (action (slot-value this 'action))) 160 | (choice-program-exec prog action))) 161 | (run (config-prop-entry-show-configuration this)))) 162 | 163 | ;; register the compiler 164 | (flex-compile-manager-register flex-compile-manage-inst 165 | (choice-program-flex-compiler)) 166 | 167 | (provide 'flex-compile-choice-program) 168 | 169 | ;;; flex-compile-choice-program.el ends here 170 | -------------------------------------------------------------------------------- /flex-compile-cli.el: -------------------------------------------------------------------------------- 1 | ;;; flex-compile-cli.el --- Compile functions -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2015 - 2025 Paul Landes 4 | 5 | ;; Author: Paul Landes 6 | ;; Maintainer: Paul Landes 7 | ;; Keywords: command line compile flexible processes 8 | ;; URL: https://github.com/plandes/flex-compile 9 | ;; Package-Requires: ((emacs "26.1")) 10 | ;; Package-Version: 0 11 | 12 | ;; This file is not part of GNU Emacs. 13 | 14 | ;; This program is free software; you can redistribute it and/or modify 15 | ;; it under the terms of the GNU General Public License as published by 16 | ;; the Free Software Foundation; either version 2, or (at your option) 17 | ;; any later version. 18 | 19 | ;; This program is distributed in the hope that it will be useful, 20 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | ;; GNU General Public License for more details. 23 | 24 | ;; You should have received a copy of the GNU General Public License 25 | ;; along with this program; if not, write to the Free Software 26 | ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, 27 | ;; Boston, MA 02110-1301, USA. 28 | 29 | ;;; Commentary: 30 | 31 | ;; Implementation compiler for Zensols action based command line. 32 | ;; See: https://plandes.github.io/util/doc/command-line.html 33 | 34 | ;;; Code: 35 | 36 | (require 'cl-lib) 37 | (require 'json) 38 | (require 'dash) 39 | (require 'choice-program-complete) 40 | (require 'flex-compile-manage) 41 | 42 | (defclass flex-compile-cli-arg (config-prop-entry) 43 | ((value :initarg :value 44 | :initform nil 45 | :documentation "The value of the argument from user input.") 46 | (arg-name :initarg :arg-name 47 | :type string 48 | :documentation "The long argument name.") 49 | (type :initarg :type 50 | :type symbol 51 | :documentation "Either `option' or `positional'.")) 52 | :documentation "\ 53 | Contains user provided arguments that is given to the command line.") 54 | 55 | (cl-defmethod config-prop-save-config ((this flex-compile-cli-arg)) 56 | "Does nothing as all data is in-memory ephemeral for THIS compiler." 57 | (ignore this)) 58 | 59 | ;;; cli file compiler 60 | (defclass cli-flex-compiler (single-buffer-flex-compiler 61 | conf-file-flex-compiler) 62 | ((cli-metadata :initarg :cli-metadata 63 | :initform nil 64 | :documentation "The CLI action metadata.") 65 | (action :initarg :action 66 | :initform nil 67 | :documentation "The action to invoke on the program.") 68 | (arg-properties :initarg :arg-properties 69 | :initform nil 70 | :documentation 71 | "Properties used to read command line arguments.") 72 | (arguments :initarg :arguments 73 | :initform nil 74 | :documentation "The arguments to give to the script.") 75 | (cache-metadata :initarg :cache-metadata 76 | :initform t 77 | :documentation " 78 | Whether or not to cache the Python program's CLI metadata.")) 79 | :method-invocation-order :c3 80 | :documentation "\ 81 | Provides support for user input for action mnemonics and options using Python 82 | programs that use the 83 | \[Zensols action CLI]\(https://plandes.github.io/util/doc/command-line.html). 84 | 85 | This compiler gets the command line metadata as a list of actions and their 86 | respective positional and option arguments. It this prompts the user with 87 | documentation associated, first the action, then the action's arguments. 88 | Finally, it constructs the command line and executes the Python program with 89 | the arguments.") 90 | 91 | (cl-defmethod initialize-instance ((this cli-flex-compiler) 92 | &optional slots) 93 | "Initialize the THIS instance with SLOTS." 94 | (let* ((read-action (lambda (this compiler default prompt history) 95 | (ignore this) 96 | (flex-compiler-cli-read-action 97 | compiler default prompt history))) 98 | (read-args (lambda (this compiler &rest args) 99 | (ignore this args) 100 | (flex-compiler-cli-read-arguments compiler t))) 101 | (props (list (config-eval-prop :object-name 'action 102 | :prompt "Action" 103 | :func read-action 104 | :prop-entry this 105 | :required t 106 | :input-type 'last 107 | :order 0) 108 | (config-eval-prop :object-name 'arguments 109 | :prompt "Arguments" 110 | :func read-args 111 | :prop-entry this 112 | :required nil 113 | :input-type 'last 114 | :order 1) 115 | (config-boolean-prop :object-name 'cache-metadata 116 | :prompt "Cache metadata" 117 | :prop-entry this 118 | :initial-value t 119 | :required nil 120 | :input-type 'toggle 121 | :order 2)))) 122 | (setq slots (plist-put slots :object-name "cli") 123 | slots (plist-put slots :description "CLI Python") 124 | slots (plist-put slots :buffer-name "Command Line Interface") 125 | slots (plist-put slots :kill-buffer-clean t) 126 | slots (plist-put slots :validate-modes '(python-mode)) 127 | slots (plist-put slots 128 | :props (append (plist-get slots :props) props)))) 129 | (cl-call-next-method this slots)) 130 | 131 | (cl-defmethod flex-compiler-load-libraries 132 | ((this cli-flex-compiler)) 133 | "Load the `json' library for THIS compiler." 134 | (ignore this) 135 | (require 'json)) 136 | 137 | (cl-defmethod config-prop-set ((this cli-flex-compiler) prop val) 138 | "Set property PROP to VAL on THIS compiler." 139 | (let (wipes) 140 | (cond ((eq (config-prop-name prop) 'config-file) 141 | (config-persistent-reset (config-prop-by-name this 'action)) 142 | (setq wipes '(cli-metadata arg-properties arguments action))) 143 | ((eq (config-prop-name prop) 'action) 144 | (setq wipes 145 | (append wipes '(cli-metadata arg-properties arguments))))) 146 | (dolist (slot wipes) 147 | (setf (slot-value this slot) nil))) 148 | (cl-call-next-method this prop val)) 149 | 150 | (cl-defmethod flex-compiler-cli-metadata ((this cli-flex-compiler) 151 | &optional action) 152 | "Read the CLI metadata from the command line on THIS compiler. 153 | If ACTION is non-nil, then return only the metadata for the \(symbol) action." 154 | ;; make sure the action is set first 155 | (config-prop-entry-set-required this 'config-file) 156 | (with-slots (config-file cli-metadata cache-metadata) this 157 | (when (null config-file) 158 | (error "Must first set the configuration file")) 159 | (setq cli-metadata 160 | (or (and cache-metadata cli-metadata) 161 | (let* ((json (->> (format "%s list --lstfmt json" config-file) 162 | shell-command-to-string))) 163 | (condition-case err 164 | (json-read-from-string json) 165 | (error "Could not parse <%s>: %s" json err))))) 166 | (if action 167 | (cdr (assq action cli-metadata)) 168 | cli-metadata))) 169 | 170 | (cl-defmethod flex-compiler-cli-has-positional-p ((this cli-flex-compiler) 171 | action) 172 | "Return if THIS compiler's ACTION program has positional arguments." 173 | (->> (flex-compiler-cli-metadata this) 174 | (assq action) 175 | cdr 176 | (assq 'positional) 177 | cdr 178 | length 179 | (< 0))) 180 | 181 | (cl-defmethod flex-compiler-cli-read-action ((this cli-flex-compiler) 182 | default prompt history) 183 | "Read the action for THIS compiler using DEFAULT, PROMPT and HISTORY." 184 | (let ((action 185 | (->> (flex-compiler-cli-metadata this) 186 | (-map (lambda (action) 187 | (let ((name (cdr (assq 'name action))) 188 | (doc (cdr (assq 'doc action)))) 189 | (format "%s: %s" name doc)))) 190 | (funcall (lambda (actions) 191 | (choice-program-complete 192 | prompt actions t nil nil history default t)))))) 193 | (unless (string-match "^\\([^:]+\\)" action) 194 | (error "Could not parse action from desc: %s" action)) 195 | (let ((action (intern (match-string 1 action))) 196 | (arg-prop (config-prop-by-name this 'arguments))) 197 | (oset arg-prop :required 198 | (flex-compiler-cli-has-positional-p this action)) 199 | action))) 200 | 201 | (cl-defmethod flex-compiler-cli--arg-properties ((this cli-flex-compiler)) 202 | "Create the argument properties for THIS compiler." 203 | ;; make sure the action is set first 204 | (config-prop-entry-set-required this 'action) 205 | (cl-flet ((to-config-class 206 | (elt) 207 | (let ((regex "\\(?:directory\\|folder\\)") 208 | (type (intern (cdr (assq 'dtype elt))))) 209 | (cl-case type 210 | (bool 'config-boolean-prop) 211 | (int 'config-number-prop) 212 | (float 'config-number-prop) 213 | (str 'config-prop) 214 | (path (let* ((doc (cdr (assq 'doc elt))) 215 | (is-dir (string-match regex doc))) 216 | (if is-dir 217 | 'config-directory-prop 218 | 'config-file-prop))) 219 | (t (error "Unknown type: %s" type))))) 220 | (to-arg 221 | (key elt) 222 | (cdr (assq key elt))) 223 | (to-doc 224 | (elt) 225 | (let* ((doc (cdr (assq 'doc elt))) 226 | (first-char (substring doc nil 1)) 227 | (rest-str (substring doc 1))) 228 | (concat (capitalize first-char) rest-str)))) 229 | (with-slots (action) this 230 | ;; return a config-prop for each command line argument/option 231 | (let* ((meta (flex-compiler-cli-metadata this action)) 232 | ;; collect required positional arguments for the action 233 | (positional (->> (cdr (assq 'positional meta)) 234 | (-map (lambda (elt) 235 | `((prop . (,(to-config-class elt) 236 | :required t 237 | :object-name 'value 238 | :prompt ,(to-doc elt))) 239 | (arg-name . ,(to-arg 'name elt)) 240 | (type . position)))))) 241 | ;; collect optional arguments for the action 242 | (options (->> (cdr (assq 'options meta)) 243 | (-map (lambda (elt) 244 | `((prop . (,(to-config-class elt) 245 | :required t 246 | :initial-value ,(assq 'defautl elt) 247 | :object-name 'value 248 | :prompt ,(to-doc elt))) 249 | (arg-name . ,(to-arg 'long_name elt)) 250 | (type . option)))))) 251 | (order 0)) 252 | ;; instantiate a new config-prop by type for each 253 | (->> (append positional options) 254 | (-map (lambda (elt) 255 | (let ((prop (cdr (assq 'prop elt))) 256 | (arg-name (cdr (assq 'arg-name elt)))) 257 | (append 258 | prop 259 | `(:prop-entry 260 | ,(flex-compile-cli-arg :arg-name arg-name 261 | :type (cdr (assq 'type elt))) 262 | :order ,(cl-incf order) 263 | :input-type 'last))))) 264 | (-map #'eval) 265 | (-map (lambda (prop) 266 | (let ((container (slot-value prop 'prop-entry))) 267 | (oset container :props (list prop)) 268 | prop)))))))) 269 | 270 | (cl-defmethod flex-compiler-cli-argument-plist ((this cli-flex-compiler) 271 | &optional resetp) 272 | "Read the arguments and return them as a list of strings for THIS compiler. 273 | If RESETP is non-nil, reset all previously set configuration to force the user 274 | to add again." 275 | (with-slots (arg-properties) this 276 | (setq arg-properties 277 | (or arg-properties (flex-compiler-cli--arg-properties this))) 278 | (when resetp 279 | (dolist (prop arg-properties) 280 | (oset (slot-value prop 'prop-entry) :value nil))) 281 | ;; create a string command line parameter for each argument (except false 282 | ;; booleans as they are set as flags/store true) 283 | (->> arg-properties 284 | (-map (lambda (prop) 285 | (let ((container (slot-value prop 'prop-entry)) 286 | (value-type (config-prop-type prop))) 287 | ;; get the user input now 288 | (config-prop-entry-set-required container) 289 | (with-slots (arg-name type value) container 290 | (list :value-type value-type 291 | :arg-name arg-name 292 | :arg-type type 293 | :value value 294 | :str-value 295 | (cond ((eq value-type 'boolean) 296 | (if value "true" "false")) 297 | ((or (null value) (stringp value)) value) 298 | (t (prin1-to-string value))))))))))) 299 | 300 | (cl-defmethod flex-compiler-cli-read-arguments ((this cli-flex-compiler) 301 | &optional resetp) 302 | "Read the arguments and return them as a list of strings for THIS compiler. 303 | If RESETP is non-nil, reset all previously set configuration to force the user 304 | to add again." 305 | ;; create a string command line parameter for each argument (except false 306 | ;; booleans as they are set as flags/store true) 307 | (->> (flex-compiler-cli-argument-plist this resetp) 308 | (-map (lambda (pl) 309 | (let ((arg-name (plist-get pl :arg-name)) 310 | (value (plist-get pl :value)) 311 | (str-value (plist-get pl :str-value)) 312 | (value-type (plist-get pl :value-type)) 313 | (arg-type (plist-get pl :arg-type))) 314 | ;; positional arguments have no (option) long name 315 | (if (eq arg-type 'position) 316 | (cons str-value nil) 317 | (if (eq value-type 'boolean) 318 | ;; add just the optiona name as flags for booleans 319 | (if value 320 | (list (format "--%s" arg-name))) 321 | ;; add the option name and value 322 | (list (format "--%s" arg-name) str-value)))))) 323 | ;; aggregate the list of argument lists in to a single list 324 | (apply #'append))) 325 | 326 | (cl-defmethod flex-compiler-config-help ((this cli-flex-compiler)) 327 | "Return the command line help from the Python program for THIS compiler." 328 | (with-slots (config-file start-directory) this 329 | (let ((default-directory start-directory) 330 | (cmd (format "%s --help" config-file))) 331 | (shell-command-to-string cmd)))) 332 | 333 | (cl-defmethod config-prop-entry-write-configuration ((this cli-flex-compiler) 334 | &optional level header) 335 | "Add the command line argument metadata and values to the output for THIS. 336 | LEVEL is the indentation level. 337 | HEADER is a string written to describe the property, otherise the description 338 | is used." 339 | (cl-call-next-method this level header) 340 | (setq level (or level 0)) 341 | (with-slots (props) this 342 | (let ((space (make-string (* 2 level) ? )) 343 | (space2 (make-string (* 2 (1+ level)) ? ))) 344 | (insert (format "%sarguments:\n" space)) 345 | (->> (flex-compiler-cli-argument-plist this) 346 | (-map (lambda (plist) 347 | (apply 348 | #'format "%s%s: \"%s\" (%s, %S)" space2 349 | (-map (lambda (k) 350 | (plist-get plist k)) 351 | '(:arg-name :str-value :value-type :arg-type))))) 352 | (funcall (lambda (args) 353 | (mapconcat #'identity args "\n"))) 354 | insert) 355 | (newline) 356 | (insert (format "%s[Usage]\n" space)) 357 | (->> (flex-compiler-config-help this) 358 | (replace-regexp-in-string "[']" "’") 359 | (replace-regexp-in-string "[\"]" "”") 360 | insert) 361 | (newline)))) 362 | 363 | (cl-defmethod flex-compiler-start-buffer ((this cli-flex-compiler) 364 | start-type) 365 | "Return a new buffer for THIS compiler with a processing compilation. 366 | See the `single-buffer-flex-compiler' implementation of 367 | `flex-compiler-start-buffer' for more information and START-TYPE." 368 | (cl-case start-type 369 | (compile 370 | (with-slots (config-file start-directory action arguments) this 371 | (let ((default-directory start-directory) 372 | (buffer-name (flex-compiler-buffer-name this)) 373 | (cmd (concat 374 | ;; the python entry point script file name 375 | config-file " " 376 | ;; action mnemonic 377 | (symbol-name action) " " 378 | ;; any arguments separated with a space 379 | (mapconcat #'identity arguments " "))) 380 | buf) 381 | (with-current-buffer 382 | (setq buf 383 | (compilation-start cmd nil (lambda (_) buffer-name)))) 384 | buf))) 385 | (run (config-prop-entry-show-configuration this)))) 386 | 387 | ;; register the compiler 388 | (flex-compile-manager-register flex-compile-manage-inst 389 | (cli-flex-compiler)) 390 | 391 | (provide 'flex-compile-cli) 392 | 393 | ;;; flex-compile-cli.el ends here 394 | -------------------------------------------------------------------------------- /flex-compile-clojure.el: -------------------------------------------------------------------------------- 1 | ;;; flex-compile-clojure.el --- Clojure compile functions -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2015 - 2025 Paul Landes 4 | 5 | ;; Author: Paul Landes 6 | ;; Maintainer: Paul Landes 7 | ;; Keywords: clojure cider compilation processes 8 | ;; URL: https://github.com/plandes/flex-compile 9 | ;; Package-Requires: ((emacs "26.1")) 10 | ;; Package-Version: 0 11 | 12 | ;; This file is not part of GNU Emacs. 13 | 14 | ;; This program is free software; you can redistribute it and/or modify 15 | ;; it under the terms of the GNU General Public License as published by 16 | ;; the Free Software Foundation; either version 2, or (at your option) 17 | ;; any later version. 18 | 19 | ;; This program is distributed in the hope that it will be useful, 20 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | ;; GNU General Public License for more details. 23 | 24 | ;; You should have received a copy of the GNU General Public License 25 | ;; along with this program; if not, write to the Free Software 26 | ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, 27 | ;; Boston, MA 02110-1301, USA. 28 | 29 | ;;; Commentary: 30 | 31 | ;; Implementation compiler for Clojure with Cider integration 32 | 33 | ;;; Code: 34 | 35 | (require 'dash) 36 | (require 'flex-compile-manage) 37 | 38 | (declare-function cider-repl-return "cider") 39 | (declare-function cider-connect "cider") 40 | (declare-function cider-current-connection "cider") 41 | (declare-function cider-current-session "cider") 42 | (declare-function cider-current-ns "cider") 43 | (declare-function cider-load-file "cider") 44 | (declare-function cider-last-sexp "cider") 45 | (declare-function cider-quit "cider") 46 | (declare-function cider-jack-in "cider") 47 | (declare-function nrepl-dict-get "nrepl-dict") 48 | (declare-function nrepl-sync-request:eval "nrepl-client") 49 | 50 | (config-manage-declare-variables 51 | cider-repl-display-in-current-window) 52 | 53 | (defclass clojure-flex-compiler (repl-flex-compiler) 54 | ((repl-host :initarg :repl-host 55 | :type string 56 | :initform "localhost" 57 | :documentation "The host running the REPL; default: localhost") 58 | (repl-port :initarg :repl-port 59 | :type integer 60 | :initform 32345 61 | :documentation "The port running the REPL; default: 32345") 62 | (connect-mode :initarg :connect-mode 63 | :type symbol 64 | :initform 'jack-in 65 | :documentation "\ 66 | Defines how to connect to a Clojure REPL. 67 | The connection modes include: 68 | - 'jack-in local mode, which invokes `cider-jack-in' 69 | - 'connect remote mode, which invokes `cider-connect' using slots: 70 | `repl-host' and `repl-port'")) 71 | :method-invocation-order :c3 72 | :documentation "\ 73 | This is a REPL based compiler that allows for evaluation Clojure 74 | buffers, expressions and starting the REPL using 75 | \[Cider](https://github.com/clojure-emacs/cider). 76 | 77 | The Clojure compiler connects using two Cider modes: the default `jack-in' 78 | mode or connecting to a host and port remotely with `cider-connect'. You can 79 | switch betwee these two methods with the [given keybindings](#key-bindings): 80 | 81 | `M-x 1 C-u C-x C-u' 82 | 83 | See documetation with `M-h f flex-compiler-query-eval' method for more 84 | inforamtion (and current binding). 85 | 86 | Todo: support multiple Cider buffers as this implementation currently does 87 | not.") 88 | 89 | (cl-defmethod initialize-instance ((this clojure-flex-compiler) &optional slots) 90 | "Initialize THIS instance using SLOTS as initial values." 91 | (let ((props (list (config-choice-prop :object-name 'connect-mode 92 | :prop-entry this 93 | :prompt "Connection mode" 94 | :choices '(jack-in connect) 95 | :input-type 'toggle) 96 | (config-number-prop :object-name 'repl-port 97 | :prop-entry this 98 | :prompt "REPL port" 99 | :input-type 'last)))) 100 | (setq slots (plist-put slots :object-name "clojure") 101 | slots (plist-put slots :validate-modes '(clojure-mode)) 102 | slots (plist-put slots :repl-buffer-regexp 103 | "^\\(\\*cider-repl \\|nrepl-server\\)") 104 | ;; also see `nrepl-sync-request-timeout' 105 | slots (plist-put slots :repl-buffer-start-timeout 0) 106 | slots (plist-put slots 107 | :props (append (plist-get slots :props) props)))) 108 | (cl-call-next-method this slots)) 109 | 110 | (cl-defmethod flex-compiler-load-libraries ((this clojure-flex-compiler)) 111 | "Load the `cider' library for THIS compiler. 112 | 113 | This also sets `cider-repl-display-in-current-window' to nil" 114 | (ignore this) 115 | (require 'cider) 116 | ;; allow flex-compile to manage windows and frame 117 | (setq cider-repl-display-in-current-window nil)) 118 | 119 | (cl-defmethod flex-compiler-send-input ((this clojure-flex-compiler) 120 | &optional command) 121 | "Send a COMMAND (input) to THIS compiler’s Cider REPL." 122 | (ignore this) 123 | (goto-char (point-max)) 124 | (insert command) 125 | (cider-repl-return)) 126 | 127 | (cl-defmethod flex-compiler-eval-form-impl ((this clojure-flex-compiler) form) 128 | "Evaluate the FORM and return the Cider REPL's response for THIS compiler." 129 | (ignore this) 130 | (let* ((res (nrepl-sync-request:eval 131 | form 132 | (cider-current-connection) 133 | (cider-current-ns)))) 134 | (or (nrepl-dict-get res "err") 135 | (nrepl-dict-get res "value")))) 136 | 137 | (cl-defmethod flex-compiler-repl-compile ((this clojure-flex-compiler) file) 138 | "Send the contents of FILE to the Cider REPL buffer of THIS compiler." 139 | (ignore this) 140 | (with-current-buffer (find-file-noselect file) 141 | (cider-load-file file))) 142 | 143 | (cl-defmethod flex-compiler-eval-initial-at-point 144 | ((this clojure-flex-compiler)) 145 | "Return the Clojure form at the current point to the REPL for THIS compiler." 146 | (ignore this) 147 | (cider-last-sexp)) 148 | 149 | (cl-defmethod flex-compiler-kill-repl ((this clojure-flex-compiler)) 150 | "Use `cider-quit' to stop the Cider REPL for THIS compiler." 151 | (condition-case err 152 | (cider-quit t) 153 | (error "Warning: %S" err)) 154 | (cl-call-next-method this)) 155 | 156 | (cl-defmethod flex-compiler-repl-start ((this clojure-flex-compiler)) 157 | "Start the Cider REPL using THIS compiler." 158 | (with-slots (connect-mode repl-host repl-port) this 159 | (with-current-buffer (flex-compiler-conf-file-buffer this) 160 | (cl-case connect-mode 161 | ;; hack to fix some pathology with nrepl window management and custom 162 | ;; `display-buffer-alists' settings 163 | (connect (let ((wc (current-window-configuration))) 164 | (save-excursion 165 | (cider-connect (list :host repl-host :port repl-port))) 166 | (set-window-configuration wc))) 167 | (jack-in (cider-jack-in nil)) 168 | (t (error "No such connection mode supported: %S" connect-mode)))))) 169 | 170 | (flex-compile-manager-register flex-compile-manage-inst 171 | (clojure-flex-compiler)) 172 | 173 | (provide 'flex-compile-clojure) 174 | 175 | ;;; flex-compile-clojure.el ends here 176 | -------------------------------------------------------------------------------- /flex-compile-comint.el: -------------------------------------------------------------------------------- 1 | ;;; flex-compile-comint.el --- Comint compile functions -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2015 - 2025 Paul Landes 4 | 5 | ;; Author: Paul Landes 6 | ;; Maintainer: Paul Landes 7 | ;; Keywords: comint integration compilation processes 8 | ;; URL: https://github.com/plandes/flex-compile 9 | ;; Package-Requires: ((emacs "26.1")) 10 | ;; Package-Version: 0 11 | 12 | ;; This file is not part of GNU Emacs. 13 | 14 | ;; This program is free software; you can redistribute it and/or modify 15 | ;; it under the terms of the GNU General Public License as published by 16 | ;; the Free Software Foundation; either version 2, or (at your option) 17 | ;; any later version. 18 | 19 | ;; This program is distributed in the hope that it will be useful, 20 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | ;; GNU General Public License for more details. 23 | 24 | ;; You should have received a copy of the GNU General Public License 25 | ;; along with this program; if not, write to the Free Software 26 | ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, 27 | ;; Boston, MA 02110-1301, USA. 28 | 29 | ;;; Commentary: 30 | 31 | ;;; Implementation compiler for comint integration. See class 32 | ;;; `comint-flex-compiler' documentation for more information and motivation. 33 | 34 | ;;; Code: 35 | 36 | (require 'subr-x) 37 | (require 'dash) 38 | (require 'flex-compile-manage) 39 | 40 | (eval-when-compile (require 'subr-x)) 41 | 42 | (defclass comint-flex-compiler (conf-file-flex-compiler) 43 | ((buffer :initarg :buffer 44 | :initform nil 45 | :type (or null buffer) 46 | :documentation "The buffer to insert the `content' slot.") 47 | (content :initarg :content 48 | :initform nil 49 | :type (or null string) 50 | :documentation "\ 51 | The string to insert in the buffer referred by the `buffer' slot.")) 52 | :method-invocation-order :c3 53 | :documentation "Send text to any running `comint' buffer. 54 | This is useful for entering a command in a shell, SQL etc buffer that otherwise 55 | requires switching back and forth between buffers, which is a hassle.") 56 | 57 | (defun flex-compile-comint-grab-line () 58 | "Get the text of the last prompt in the comint buffer. 59 | If there is no last prompt found, return the command line at the current point 60 | as a string" 61 | (interactive) 62 | (save-excursion 63 | (or (let ((prev-prompt-point (comint-previous-prompt 1)) 64 | (end (progn (end-of-line) (point)))) 65 | (when prev-prompt-point 66 | (goto-char prev-prompt-point) 67 | (buffer-substring-no-properties (point) end))) 68 | (save-excursion 69 | (beginning-of-line) 70 | (buffer-substring-no-properties 71 | (point) 72 | (progn 73 | (end-of-line) 74 | (point))))))) 75 | 76 | (cl-defmethod initialize-instance ((this comint-flex-compiler) &optional slots) 77 | "Initialize THIS instance using SLOTS as initial values." 78 | (let ((props (list (config-prop :object-name 'content 79 | :prop-entry this 80 | :required t 81 | :prompt "Command or SPACE for config file" 82 | :input-type 'flex-compile-comint-grab-line) 83 | (config-buffer-prop :object-name 'buffer 84 | :prop-entry this 85 | :required t 86 | :transient t 87 | :prompt "Buffer" 88 | :input-type 'last)))) 89 | (setq slots (plist-put slots :object-name "comint") 90 | slots (plist-put slots 91 | :props (append (plist-get slots :props) props))) 92 | (cl-call-next-method this slots) 93 | ;; TODO: pass these as properties to config-file-flex-compiler instead of 94 | ;; having to clobber its defaults 95 | (let ((prop (config-prop-by-name this 'config-file))) 96 | (setf (slot-value prop 'prompt) "Insert file" 97 | (slot-value prop 'required) nil)))) 98 | 99 | (cl-defmethod flex-compiler-load-libraries ((this comint-flex-compiler)) 100 | "Load the `comint' library for THIS compiler." 101 | (ignore this) 102 | (require 'comint)) 103 | 104 | (cl-defmethod flex-compiler-eval-form-impl ((this comint-flex-compiler) form) 105 | "Evaluate the FORM and return the response of the REPL for THIS compiler." 106 | (ignore this) 107 | (goto-char (point-max)) 108 | (insert form) 109 | (comint-send-input)) 110 | 111 | (cl-defmethod config-prop-set ((this comint-flex-compiler) prop val) 112 | "Set property PROP to VAL on THIS compiler. 113 | 114 | This resets the target when changing the file." 115 | (when (and (eq (config-prop-name prop) 'content) 116 | (eq (derived-mode-p 'comint-mode) 'comint-mode)) 117 | (setf (slot-value this 'buffer) (current-buffer)) 118 | (message "Setting buffer %s" (buffer-name))) 119 | (cl-call-next-method this prop val)) 120 | 121 | (cl-defmethod config-prop-entry-configure ((this comint-flex-compiler) 122 | config-options) 123 | "Special behavior when configuring THIS compiler. 124 | 125 | CONFIG-OPTIONS: 126 | - when -1 (using \\[universal-argument] with 0) prompt for the content 127 | - when nil short cut to setting the make content" 128 | (->> (cond ((eq config-options -1) nil) ; unversal arg with 0 129 | ;; shortcut to setting the make content 130 | ((null config-options) '(prop-name content)) 131 | (t config-options)) 132 | (cl-call-next-method this))) 133 | 134 | (cl-defmethod flex-compiler-compile ((this comint-flex-compiler)) 135 | "Insert the `content' slot value set on THIS compiler in comint buffer. 136 | 137 | After the value is set, use `comint-send-input' to have `comint' process it." 138 | (with-slots (buffer) this 139 | (when (not (buffer-live-p buffer)) 140 | (setq buffer nil)) 141 | (config-prop-entry-set-required this) 142 | (with-slots (config-file content) this 143 | (if (and (null config-file) (null content)) 144 | (error "Either property `config-file' or `content' needs to be set")) 145 | (let ((contents (->> (or (and (> (length (string-trim content)) 0) 146 | content) 147 | (with-temp-buffer 148 | (insert-file-contents config-file) 149 | (buffer-string))) 150 | string-trim))) 151 | (with-current-buffer buffer 152 | (goto-char (point-max)) 153 | (insert contents) 154 | (comint-send-input)))))) 155 | 156 | (cl-defmethod flex-compiler-run ((this comint-flex-compiler)) 157 | "Show the comint buffer set in THIS compiler. 158 | 159 | See `flex-compiler-display-buffer', which does the work to show the buffer." 160 | (with-slots (buffer) this 161 | `((newp . nil) 162 | (buffer . ,buffer)))) 163 | 164 | (cl-defmethod flex-compiler-display-buffer ((this comint-flex-compiler) 165 | &optional compile-def) 166 | "Display the comint buffer using `display-buffer' for THIS compiler. 167 | 168 | See the `single-buffer-flex-compiler' implementation of 169 | `flex-compiler-display-buffer' for COMPILE-DEF and more information." 170 | (ignore this) 171 | (with-slots (buffer) this 172 | (let ((buf (or (cdr (assq 'buffer compile-def)) buffer))) 173 | (when (not (buffer-live-p buf)) 174 | (error "Buffer not active")) 175 | (display-buffer buf) 176 | (with-current-buffer buf 177 | (goto-char (point-max))) 178 | (message "Displayed buffer %S" buf)))) 179 | 180 | (flex-compile-manager-register flex-compile-manage-inst (comint-flex-compiler)) 181 | 182 | (provide 'flex-compile-comint) 183 | 184 | ;;; flex-compile-comint.el ends here 185 | -------------------------------------------------------------------------------- /flex-compile-command.el: -------------------------------------------------------------------------------- 1 | ;;; flex-compile-command.el --- Convenience compiler that evaluates Emacs Lisp -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2015 - 2025 Paul Landes 4 | 5 | ;; Author: Paul Landes 6 | ;; Maintainer: Paul Landes 7 | ;; Keywords: interactive function command compile flexible processes 8 | ;; URL: https://github.com/plandes/flex-compile 9 | ;; Package-Requires: ((emacs "26.1")) 10 | ;; Package-Version: 0 11 | 12 | ;; This file is not part of GNU Emacs. 13 | 14 | ;; This program is free software; you can redistribute it and/or modify 15 | ;; it under the terms of the GNU General Public License as published by 16 | ;; the Free Software Foundation; either version 2, or (at your option) 17 | ;; any later version. 18 | 19 | ;; This program is distributed in the hope that it will be useful, 20 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | ;; GNU General Public License for more details. 23 | 24 | ;; You should have received a copy of the GNU General Public License 25 | ;; along with this program; if not, write to the Free Software 26 | ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, 27 | ;; Boston, MA 02110-1301, USA. 28 | 29 | ;;; Commentary: 30 | 31 | ;; Compiler that evaluates Emacs Lisp. This is handy for invoking interactive 32 | ;; functions (commands) using the `flex-compiler' keybindings. 33 | 34 | ;;; Code: 35 | 36 | (require 'flex-compile-manage) 37 | 38 | (defclass config-sexp-prop (config-prop) 39 | () 40 | :method-invocation-order :c3 41 | :documentation "Property that prompts for a selection of a list of choices.") 42 | 43 | (cl-defmethod initialize-instance ((this config-sexp-prop) &optional slots) 44 | "Initialize instance THIS with arguments SLOTS." 45 | (setq slots (plist-put slots :transient t)) 46 | (cl-call-next-method this slots)) 47 | 48 | (cl-defmethod config-prop-read ((this config-sexp-prop)) 49 | "Read the user input for the property. 50 | The default reads a string using ‘config-prop-default’ and 51 | ‘config-prop-prompt’ with the history slot. 52 | THIS is the instance." 53 | (list (cl-flet ((read-sexp 54 | (prompt history) 55 | (let (sexp) 56 | (condition-case nil 57 | (progn 58 | (setq sexp (thing-at-point 'sexp)) 59 | ;; read as sexp and then back to string to make 60 | ;; into one line 61 | (if sexp (setq sexp (prin1-to-string (read sexp))))) 62 | (t)) 63 | (read (read-string prompt sexp history))))) 64 | (with-slots (history choices prompt) this 65 | (let* ((cmd (read-command (format "%s (or RET for sexp): " prompt))) 66 | (sexp-prompt (format "%s: " prompt))) 67 | (if (eq '## cmd) 68 | (let ((func (read-sexp sexp-prompt history))) 69 | (eval `(defun flex-compiler-function-invoke-command () 70 | (interactive) 71 | ,func))) 72 | cmd)))))) 73 | 74 | 75 | ;;; func file compiler 76 | (defclass command-flex-compiler (conf-flex-compiler) 77 | ((sexp :initarg :sexp 78 | :initform nil 79 | :documentation "The symbol expression to evaluate.")) 80 | :method-invocation-order :c3 81 | :documentation "\ 82 | This \"compiler\" is more of a convenience to invoke an Emacs Lisp function or 83 | form. This is handy for functions that you end up invoking over and over with 84 | `M-x` (i.e. `cider-test-run-ns-tests`). See [motivation](#motivation).") 85 | 86 | (cl-defmethod initialize-instance ((this command-flex-compiler) 87 | &optional slots) 88 | "Initialize instance THIS with arguments SLOTS." 89 | (let ((props (list (config-sexp-prop :object-name 'sexp 90 | :prop-entry this 91 | :prompt "Expression" 92 | :input-type 'last)))) 93 | (setq slots (plist-put slots :object-name "command") 94 | slots (plist-put slots 95 | :props (append (plist-get slots :props) props)))) 96 | (cl-call-next-method this slots)) 97 | 98 | (cl-defmethod flex-compiler-display-buffer-alist ((this command-flex-compiler)) 99 | "Return a do-nothing configuration to allow the function to display bufferes. 100 | THIS is the object instance." 101 | (ignore this)) 102 | 103 | (cl-defmethod flex-compiler-compile ((this command-flex-compiler)) 104 | "Evalute the function or symbolic expression stored in THIS instance. 105 | The result of the evaulation is given to `message' and returned." 106 | (unless (slot-value this 'sexp) 107 | (config-prop-entry-configure this nil)) 108 | (with-slots (sexp) this 109 | (let ((res (apply sexp))) 110 | (message "%S "res) 111 | res))) 112 | 113 | (flex-compile-manager-register flex-compile-manage-inst 114 | (command-flex-compiler)) 115 | 116 | (provide 'flex-compile-command) 117 | 118 | ;;; flex-compile-command.el ends here 119 | -------------------------------------------------------------------------------- /flex-compile-config.el: -------------------------------------------------------------------------------- 1 | ;;; flex-compile-config.el --- Configuration based compiler for flex-compile -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2015 - 2025 Paul Landes 4 | 5 | ;; Author: Paul Landes 6 | ;; Maintainer: Paul Landes 7 | ;; Keywords: compilation integration processes 8 | ;; URL: https://github.com/plandes/flex-compile 9 | ;; Package-Requires: ((emacs "26.1")) 10 | ;; Package-Version: 0 11 | 12 | ;; This file is not part of GNU Emacs. 13 | 14 | ;; This program is free software; you can redistribute it and/or modify 15 | ;; it under the terms of the GNU General Public License as published by 16 | ;; the Free Software Foundation; either version 2, or (at your option) 17 | ;; any later version. 18 | 19 | ;; This program is distributed in the hope that it will be useful, 20 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | ;; GNU General Public License for more details. 23 | 24 | ;; You should have received a copy of the GNU General Public License 25 | ;; along with this program; if not, write to the Free Software 26 | ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, 27 | ;; Boston, MA 02110-1301, USA. 28 | 29 | ;;; Commentary: 30 | 31 | ;; This file contains an abstract compiler that provides configuration 32 | ;; functionality (minibuffer and persistance to the configuraiton file) and 33 | ;; basic meta data properties for configuration. 34 | 35 | ;;; Code: 36 | 37 | (require 'dash) 38 | (require 'eieio) 39 | (require 'choice-program-complete) 40 | (require 'config-manage-prop) 41 | (require 'flex-compile-base) 42 | 43 | (defcustom flex-compile-conf-file-disply-mode 'pop-to-buffer 44 | "The function used to display the config file. 45 | This is used by `flex-compiler-conf-file-display'." 46 | :type '(choice :tag "Display mode" 47 | (const :tag "Pop Buffer" pop-to-buffer) 48 | (const :tag "Display Buffer" display-buffer) 49 | (const :tag "Switch Buffer" switch-to-buffer)) 50 | :group 'flex-compile) 51 | 52 | (defclass conf-flex-compiler (flex-compiler) 53 | () 54 | :abstract true 55 | :method-invocation-order :c3 56 | :documentation "A property based configurable compiler. 57 | All properties are added in each sub class's `initialize-instance' method as 58 | the :props plist argument in SLOTS. 59 | 60 | Important: Extend from this class _last_ so that it captures all proprties 61 | since this class sets :pslots in the `config-persistent' subclass.") 62 | 63 | (cl-defmethod flex-compile-clear ((this conf-flex-compiler)) 64 | "Wipe all values for THIS compiler." 65 | (dolist (prop (config-prop-by-order this)) 66 | (config-prop-set this prop nil)) 67 | (dolist (prop (config-prop-by-order this)) 68 | (flex-compile-clear prop)) 69 | (message "Cleared %s configuration" (config-entry-name this))) 70 | 71 | 72 | ;; configuration based compiler 73 | (defclass conf-file-flex-compiler (conf-flex-compiler) 74 | ((config-file :initarg :config-file 75 | :initform nil 76 | :type (or null string) 77 | :documentation "\ 78 | The file to use for *configuring* the compiler.") 79 | (start-directory :initarg :start-directory 80 | :initform nil 81 | :type (or null string) 82 | :documentation "\ 83 | The directory for starting the compilation. The `default-directory' is set to 84 | this when the compile starts")) 85 | :abstract true 86 | :method-invocation-order :c3 87 | :documentation "A configurable compiler with a configuration file. 88 | 89 | All subclass compilers have a confiugration file slot that determines what file 90 | is compiled, evaluated etc. When this slot is set, the `start-directory' slot, 91 | which is used as the `default-directory', is unset.") 92 | 93 | (cl-defmethod initialize-instance ((this conf-file-flex-compiler) 94 | &optional slots) 95 | "Initialize instance THIS with arguments SLOTS." 96 | (let* ((modes (plist-get slots :validate-modes)) 97 | (desc (plist-get slots :description)) 98 | (name (plist-get slots :object-name)) 99 | (prompt (format "%s file" (or desc (capitalize name)))) 100 | (props (list (config-directory-prop :object-name 'start-directory 101 | :prop-entry this 102 | :prompt "Start directory" 103 | :input-type 'last) 104 | (config-file-prop :object-name 'config-file 105 | :prompt prompt 106 | :prop-entry this 107 | :validate-modes modes 108 | :input-type 'last 109 | :required t 110 | :order 0)))) 111 | (setq slots (plist-put slots :props 112 | (append (plist-get slots :props) props)))) 113 | (cl-remf slots :validate-modes) 114 | (cl-call-next-method this slots)) 115 | 116 | (cl-defmethod eieio-object-name-string ((this conf-file-flex-compiler)) 117 | "Return a string as a representation of the in memory instance of THIS." 118 | (->> (format " config-file=%s" (slot-value this 'config-file)) 119 | (concat (cl-call-next-method this)))) 120 | 121 | (cl-defmethod config-prop-entry-configure ((this conf-file-flex-compiler) 122 | config-options) 123 | "Configure the prop-entry of THIS instance with CONFIG-OPTIONS. 124 | 125 | See `config-manage-prop' class's method documentation." 126 | (if (eq config-options 'immediate) 127 | (setq config-options (list 'prop-name 'config-file (buffer-file-name)))) 128 | (cl-call-next-method this config-options)) 129 | 130 | (cl-defmethod config-prop-set ((this conf-file-flex-compiler) prop val) 131 | "Set property PROP to VAL on THIS compiler." 132 | (if (eq (config-prop-name prop) 'config-file) 133 | (with-slots (start-directory) this 134 | (config-prop-validate prop val) 135 | (setq start-directory (file-name-directory val)))) 136 | (cl-call-next-method this prop val)) 137 | 138 | (cl-defmethod flex-compiler-conf-file-buffer ((this conf-file-flex-compiler)) 139 | "Return a new buffer of the configuration file for THIS compiler." 140 | (config-prop-entry-set-required this) 141 | (find-file-noselect (slot-value this 'config-file))) 142 | 143 | (cl-defmethod flex-compiler-conf-file-display ((this conf-file-flex-compiler)) 144 | "Pop THIS compilers' configuration file buffer to the current buffer/window." 145 | (funcall flex-compile-conf-file-disply-mode 146 | (flex-compiler-conf-file-buffer this))) 147 | 148 | (provide 'flex-compile-config) 149 | 150 | ;;; flex-compile-config.el ends here 151 | -------------------------------------------------------------------------------- /flex-compile-ess.el: -------------------------------------------------------------------------------- 1 | ;;; flex-compile-ess.el --- Ess compile functions -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2015 - 2025 Paul Landes 4 | 5 | ;; Author: Paul Landes 6 | ;; Maintainer: Paul Landes 7 | ;; Keywords: ess Emacs Speaks Statistics compilation processes 8 | ;; URL: https://github.com/plandes/flex-compile 9 | ;; Package-Requires: ((emacs "26.1")) 10 | ;; Package-Version: 0 11 | 12 | ;; This file is not part of GNU Emacs. 13 | 14 | ;; This program is free software; you can redistribute it and/or modify 15 | ;; it under the terms of the GNU General Public License as published by 16 | ;; the Free Software Foundation; either version 2, or (at your option) 17 | ;; any later version. 18 | 19 | ;; This program is distributed in the hope that it will be useful, 20 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | ;; GNU General Public License for more details. 23 | 24 | ;; You should have received a copy of the GNU General Public License 25 | ;; along with this program; if not, write to the Free Software 26 | ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, 27 | ;; Boston, MA 02110-1301, USA. 28 | 29 | ;;; Commentary: 30 | 31 | ;; Implementation compiler for ess integration 32 | 33 | ;;; Code: 34 | 35 | (require 'flex-compile-manage) 36 | 37 | (declare-function R "ess") 38 | (declare-function ess-eval-region "ess") 39 | 40 | (defclass ess-flex-compiler (repl-flex-compiler) 41 | () 42 | :method-invocation-order :c3 43 | :documentation "\ 44 | This is a REPL based compiler to evaluate R code with 45 | [Emacs Speaks Statistics](https://ess.r-project.org) .") 46 | 47 | (cl-defmethod initialize-instance ((this ess-flex-compiler) &optional slots) 48 | "Initialize instance THIS with arguments SLOTS." 49 | (setq slots (plist-put slots :object-name "ess") 50 | slots (plist-put slots :description "Emacs speaks statistics") 51 | slots (plist-put slots :validate-modes '(ess-r-mode)) 52 | slots (plist-put slots :repl-buffer-regexp "^\\*R.*\\*$") 53 | slots (plist-put slots :repl-buffer-start-timeout 5)) 54 | (cl-call-next-method this slots)) 55 | 56 | (cl-defmethod flex-compiler-load-libraries ((this ess-flex-compiler)) 57 | "Load library `ess-site' for THIS compielr." 58 | (ignore this) 59 | (require 'ess) 60 | (require 'ess-site)) 61 | 62 | (cl-defmethod flex-compiler-repl-start ((this ess-flex-compiler)) 63 | "Start the REPL using THIS compiler." 64 | (let ((ess-ask-for-ess-directory nil)) 65 | (ignore ess-ask-for-ess-directory) 66 | (with-current-buffer (find-file-noselect (slot-value this 'config-file)) 67 | (R)))) 68 | 69 | (cl-defmethod flex-compiler-repl-compile ((this ess-flex-compiler) file) 70 | "Send the contents of source code FILE to the REPL buffer of THIS compiler." 71 | (ignore this) 72 | (with-current-buffer (find-file-noselect file) 73 | (ess-eval-region (point-min) (point-max) nil))) 74 | 75 | (flex-compile-manager-register flex-compile-manage-inst (ess-flex-compiler)) 76 | 77 | (provide 'flex-compile-ess) 78 | 79 | ;;; flex-compile-ess.el ends here 80 | -------------------------------------------------------------------------------- /flex-compile-lisp.el: -------------------------------------------------------------------------------- 1 | ;;; flex-compile-lisp.el --- Lisp compile functions -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2015 - 2025 Paul Landes 4 | 5 | ;; Author: Paul Landes 6 | ;; Maintainer: Paul Landes 7 | ;; Keywords: lisp integration compilation processes 8 | ;; URL: https://github.com/plandes/flex-compile 9 | ;; Package-Requires: ((emacs "26.1")) 10 | ;; Package-Version: 0 11 | 12 | ;; This file is not part of GNU Emacs. 13 | 14 | ;; This program is free software; you can redistribute it and/or modify 15 | ;; it under the terms of the GNU General Public License as published by 16 | ;; the Free Software Foundation; either version 2, or (at your option) 17 | ;; any later version. 18 | 19 | ;; This program is distributed in the hope that it will be useful, 20 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | ;; GNU General Public License for more details. 23 | 24 | ;; You should have received a copy of the GNU General Public License 25 | ;; along with this program; if not, write to the Free Software 26 | ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, 27 | ;; Boston, MA 02110-1301, USA. 28 | 29 | ;;; Commentary: 30 | 31 | ;;; Implementation compiler for Lisp (Emacs slime) integration 32 | 33 | ;;; Code: 34 | 35 | (require 'flex-compile-manage) 36 | (require 'flex-compile-repl) 37 | 38 | (config-manage-declare-functions 39 | slime 40 | slime-setup 41 | slime-interactive-eval 42 | slime-load-file 43 | slime-compile-and-load-file 44 | slime-last-expression 45 | slime-repl-clear-buffer 46 | slime-quit-lisp) 47 | 48 | (defvar flex-compile-lisp-window-context nil 49 | "Used to restore windows in `flex-compile-lisp-connected'.") 50 | 51 | (defclass lisp-flex-compiler (repl-flex-compiler) 52 | ((compile-on-load :initarg :compile-on-load 53 | :initform nil 54 | :type boolean 55 | :documentation "\ 56 | Whether to also compile when loading the source file.") 57 | (mode :initarg :mode 58 | :initform 'file 59 | :type symbol 60 | :documentation "\ 61 | Whether to run the test suite using the `slite' library (must be installed) 62 | instead of evaluate the `config-file'. Available at: 63 | https://github.com/tdrhq/slite") 64 | (test-file :initarg :test-file 65 | :initform nil 66 | :type (or null string))) 67 | :method-invocation-order :c3 68 | :documentation "\ 69 | This is a REPL based compiler that allows for evaluation Lisp buffers and 70 | expressions using [slime](https://github.com/slime/slime).") 71 | 72 | (cl-defmethod initialize-instance ((this lisp-flex-compiler) &optional slots) 73 | "Initialize instance THIS with arguments SLOTS." 74 | (let ((props 75 | (list 76 | (config-boolean-prop :object-name 'compile-on-load 77 | :prop-entry this 78 | :prompt "Compile when loading" 79 | :input-type 'toggle) 80 | (config-file-prop :object-name 'test-file 81 | :prompt "Test file" 82 | :prop-entry this 83 | :validate-modes (plist-get slots :validate-modes) 84 | :input-type 'last 85 | :required nil 86 | :order 0) 87 | (config-choice-prop :object-name 'mode 88 | :prompt "Run tests" 89 | :prop-entry this 90 | :input-type 'toggle 91 | :choices '(file test))))) 92 | (setq slots (plist-put slots :object-name "lisp") 93 | slots (plist-put slots :validate-modes '(lisp-mode)) 94 | slots (plist-put slots :repl-buffer-regexp "^\\**slime-repl .+\\*$") 95 | slots (plist-put slots :derived-buffer-names '("*inferior-lisp*")) 96 | slots (plist-put slots :repl-buffer-start-timeout 0) 97 | slots (plist-put slots 98 | :props (append (plist-get slots :props) props)))) 99 | (cl-call-next-method this slots)) 100 | 101 | (cl-defmethod flex-compiler-load-libraries ((this lisp-flex-compiler)) 102 | "Load the `slime' library for THIS compiler." 103 | (ignore this) 104 | (require 'slime) 105 | (condition-case nil 106 | (require 'slite) 107 | (error 108 | (message "Package `slite' is not found--skipping test functionality"))) 109 | (slime-setup)) 110 | 111 | (cl-defmethod flex-compiler-eval-form-impl ((this lisp-flex-compiler) form) 112 | "Evaluate the FORM and return the response of the REPL for THIS compiler." 113 | (ignore this) 114 | (slime-interactive-eval form)) 115 | 116 | (cl-defmethod flex-compiler-repl-compile ((this lisp-flex-compiler) file) 117 | "Send the contents of FILE to the Slime REPL buffer of THIS compiler." 118 | (ignore this) 119 | (with-slots (compile-on-load) this 120 | (save-excursion 121 | (apply #'set-buffer (list (find-file-noselect file))) 122 | (if compile-on-load 123 | (slime-compile-and-load-file) 124 | (slime-load-file file))))) 125 | 126 | (cl-defmethod flex-compiler-eval-initial-at-point ((this lisp-flex-compiler)) 127 | "Return the Lisp form at the current point to the REPL for THIS compiler." 128 | (ignore this) 129 | (slime-last-expression)) 130 | 131 | (cl-defmethod flex-compiler-clear-buffer ((this lisp-flex-compiler)) 132 | "Clear THIS compiler's REPL buffer." 133 | (with-current-buffer (flex-compiler-buffer this) 134 | ;; temporarily redefine `recenter' to avoid current window warnings 135 | (cl-letf (((symbol-function 'recenter) 136 | (lambda (&rest args) 137 | (ignore args) 138 | (goto-char (point-max))))) 139 | (slime-repl-clear-buffer)))) 140 | 141 | (cl-defmethod flex-compiler-repl-start ((this lisp-flex-compiler)) 142 | "Start the REPL using THIS compiler." 143 | (ignore this) 144 | (setq flex-compile-lisp-window-context 145 | `((this . ,this) 146 | (win-cfg . ,(current-window-configuration)))) 147 | (slime)) 148 | 149 | (cl-defmethod flex-compiler-start-buffer ((this lisp-flex-compiler) 150 | start-type) 151 | "Return a new buffer for THIS compiler with a processing compilation. 152 | START-TYPE is either symbols `compile', `run', `clean' depending 153 | if invoked by `flex-compiler-compile' or `flex-compiler-run'." 154 | (with-slots (mode test-file) this 155 | (let ((needs-run-p (and (eq start-type 'compile) 156 | (not (flex-compiler-repl-running-p this)))) 157 | (ret (cl-call-next-method this start-type))) 158 | ;; tell `flex-compile-lisp-connected' to invoke compilation after the 159 | ;; REPL has started if started as a compile rather than a run 160 | (if needs-run-p 161 | (setq flex-compile-lisp-window-context 162 | (append flex-compile-lisp-window-context 163 | '((needs-compile-p . t))))) 164 | ret))) 165 | 166 | (cl-defmethod flex-compiler-single-buffer--flex-comp-def 167 | ((this lisp-flex-compiler) start-type startp) 168 | "Return a default compilation definition for THIS compiler. 169 | 170 | START-TYPE is either symbols `compile', `run', `clean' depending if invoked by 171 | `flex-compiler-compile' or `flex-compiler-run'. 172 | 173 | If STARTP is non-nil, start the buffer using `flex-compiler-start-buffer'. 174 | 175 | Return an alist in the following form: 176 | ((newp . 177 | (buffer . )))" 178 | (with-slots (mode test-file) this 179 | ;; override for testing functionality 180 | (if (and (eq start-type 'compile) (eq mode 'test)) 181 | (when (fboundp 'slite-run) 182 | (message "Running tests...") 183 | (unless test-file 184 | (let ((prop (config-prop-by-name this 'test-file))) 185 | (config-prop-set this prop (config-prop-read prop)))) 186 | (flex-compiler-evaluate-form this (format "(load \"%s\")" test-file)) 187 | (call-interactively 'slite-run) 188 | '((newp . nil) 189 | (buffer . nil)))) 190 | (cl-call-next-method this start-type startp))) 191 | 192 | (cl-defmethod flex-compiler-kill-repl ((this lisp-flex-compiler)) 193 | "Use `cider-quit' to stop the Cider REPL for THIS compiler." 194 | (condition-case err 195 | (slime-quit-lisp t) 196 | (error "Warning: %S" err)) 197 | (dolist (buf (buffer-list)) 198 | (when (string-match "^\\**\\(?:slime-\\|sldb \\).+\\*$" (buffer-name buf)) 199 | (message "killing buffer %S" buf) 200 | (flex-compiler--kill-buffer this buf))) 201 | (cl-call-next-method this)) 202 | 203 | (defun flex-compile-lisp-connected () 204 | "Called by `slime-connected-hook' after the REPL has started. 205 | Because slime pops a new buffer after the REPL starts, the default buffer 206 | display logic isn't called after `flex-compiler-repl-start'. This is called 207 | by `slime-connected-hook' to execute the default buffer display behavior by 208 | calling `flex-compiler-display-buffer'." 209 | (unwind-protect 210 | (let* ((this (cdr (assq 'this flex-compile-lisp-window-context))) 211 | (cfg (cdr (assq 'win-cfg flex-compile-lisp-window-context))) 212 | (needs-compile-p (cdr (assq 'needs-compile-p 213 | flex-compile-lisp-window-context))) 214 | (compile-def `((newp . t) 215 | (buffer . ,(flex-compiler-buffer this))))) 216 | (set-window-configuration cfg) 217 | (flex-compiler-display-buffer this compile-def) 218 | (when needs-compile-p 219 | ;; put any output on a separate (new)line 220 | (flex-compiler-eval-form-impl this "(format t \"~%\")") 221 | (with-slots (config-file) this 222 | (flex-compiler-repl-compile this config-file)))) 223 | (setq flex-compile-lisp-window-context nil))) 224 | 225 | (add-hook 'slime-connected-hook #'flex-compile-lisp-connected 100) 226 | 227 | (flex-compile-manager-register flex-compile-manage-inst (lisp-flex-compiler)) 228 | 229 | (provide 'flex-compile-lisp) 230 | 231 | ;;; flex-compile-lisp.el ends here 232 | -------------------------------------------------------------------------------- /flex-compile-make.el: -------------------------------------------------------------------------------- 1 | ;;; flex-compile-make.el --- Compile functions -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2015 - 2025 Paul Landes 4 | 5 | ;; Author: Paul Landes 6 | ;; Maintainer: Paul Landes 7 | ;; Keywords: make compile flexible processes 8 | ;; URL: https://github.com/plandes/flex-compile 9 | ;; Package-Requires: ((emacs "26.1")) 10 | ;; Package-Version: 0 11 | 12 | ;; This file is not part of GNU Emacs. 13 | 14 | ;; This program is free software; you can redistribute it and/or modify 15 | ;; it under the terms of the GNU General Public License as published by 16 | ;; the Free Software Foundation; either version 2, or (at your option) 17 | ;; any later version. 18 | 19 | ;; This program is distributed in the hope that it will be useful, 20 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | ;; GNU General Public License for more details. 23 | 24 | ;; You should have received a copy of the GNU General Public License 25 | ;; along with this program; if not, write to the Free Software 26 | ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, 27 | ;; Boston, MA 02110-1301, USA. 28 | 29 | ;;; Commentary: 30 | 31 | ;; Implementation compiler for make(files). 32 | ;; Not customizing `compilation-always-kill' to t will result in windows 33 | ;; disappearing on a compilation interruption. 34 | 35 | ;;; Code: 36 | 37 | (require 'cl-lib) 38 | (require 'dash) 39 | (require 'compile) 40 | (require 'choice-program-complete) 41 | (require 'flex-compile-manage) 42 | 43 | ;;; make file compiler 44 | (defclass make-flex-compiler (single-buffer-flex-compiler 45 | conf-file-flex-compiler) 46 | ((target :initarg :target 47 | :initform nil 48 | :type (or null string) 49 | :documentation "The make file target to satisfy.") 50 | (run-target :initarg :run-target 51 | :initform nil 52 | :type (or null string) 53 | :documentation "\ 54 | The target used to run or test as the secondary compilation functionality.")) 55 | ;; see `flex-compiler::config-persistent-doc' 56 | :method-invocation-order :c3 57 | :documentation "\ 58 | This compiler invokes make as an asynchronous process in a 59 | buffer. The first target, `run' target, and `clean' target are 60 | invoked respectfully with *compile*, *run* and *clean* Emacs 61 | commands (see [usage](#usage)). 62 | 63 | This is a special compiler in it's configuration. Instead of 64 | setting properties, the default configuration mechanism is to set 65 | the make target instead. If you want to set a flex compiler 66 | property, use `\\C-u 0 \\C-u'. 67 | 68 | When setting the configuration file the target property is unset.") 69 | 70 | (cl-defmethod initialize-instance ((this make-flex-compiler) &optional slots) 71 | "Initialize THIS instance using SLOTS as initial values." 72 | (let* ((fn #'(lambda (this compiler &rest slots) 73 | (ignore slots) 74 | (flex-compiler-makefile-read compiler this))) 75 | (props (list (config-eval-prop :object-name 'target 76 | :prompt "Target" 77 | :func fn 78 | :prop-entry this 79 | :input-type 'last 80 | :order 1) 81 | (config-eval-prop :object-name 'run-target 82 | :prompt "Run target" 83 | :func fn 84 | :prop-entry this 85 | :input-type 'last 86 | :order 2)))) 87 | (setq slots (plist-put slots :object-name "make") 88 | slots (plist-put slots :description "Make") 89 | slots (plist-put slots :validate-modes '(makefile-gmake-mode)) 90 | slots (plist-put slots :buffer-name "compilation") 91 | slots (plist-put slots :kill-buffer-clean nil) 92 | slots (plist-put slots 93 | :props (append (plist-get slots :props) props)))) 94 | (cl-call-next-method this slots)) 95 | 96 | (defun flex-compiler-make-compiler-handle-finish (buffer status) 97 | "Notify the make flex compiler instance of completion of the make compile. 98 | The `flex-compiler-make-compiler-finish' is called with STATUS. 99 | 100 | BUFFER, ignored." 101 | (if (and (stringp status) 102 | (string-match "^exited abnormally with code \\([0-9]+\\)" status)) 103 | (setq status (string-to-number (match-string 1 status)))) 104 | (if (and (stringp status) 105 | (equal "finished" (string-trim status))) 106 | (setq status 0)) 107 | (flex-compiler-make-compiler-finish (flex-compiler-by-name "make") status)) 108 | 109 | (cl-defmethod flex-compiler-load-libraries ((this make-flex-compiler)) 110 | "Load library `compile' for THIS compiler." 111 | (ignore this) 112 | (require 'compile) 113 | (add-hook 'compilation-finish-functions 114 | 'flex-compiler-make-compiler-handle-finish)) 115 | 116 | (cl-defmethod flex-compiler-make-compiler-finish ((this make-flex-compiler) 117 | status) 118 | "Display the compliation buffer based on 0 a STATUS for THIS compiler. 119 | The compilation buffer is displayed when an error is detected, and the 120 | corresponding compiler display mode property `buffer-new-mode' 121 | `buffer-exists-mode' are set to `only-on-error'." 122 | (with-slots (buffer-new-mode buffer-exists-mode last-displayed-context) this 123 | (let* ((buf (cdr (assq 'buffer last-displayed-context))) 124 | (display-mode (-> (cdr (assq 'newp last-displayed-context)) 125 | (if 'new 'exists) 126 | (assq (flex-compiler-display-modes this)) 127 | cdr)) 128 | (pop-buffer (and (eq 'only-if-error display-mode) 129 | (null (get-buffer-window buf)) 130 | (not (eq status 0))))) 131 | (when pop-buffer 132 | (message "Compilation error with status: %S" status) 133 | (display-buffer buf))))) 134 | 135 | (cl-defmethod flex-compiler-run-make ((this make-flex-compiler) 136 | &optional target) 137 | "Invoke a make compilation in an async inferior buffer for THIS compiler. 138 | TARGET is the make file target to run/satisfy. 139 | This is done by creating a command with `make' found in the executable path." 140 | (let* ((makefile (slot-value this 'config-file)) 141 | (dir (file-name-directory makefile)) 142 | (dir-switch (if dir (format "-C %s" dir))) 143 | (command (concat "make -k " dir-switch " -f " 144 | (file-name-nondirectory makefile) 145 | (if target " ") target))) 146 | (setenv "EMACS" "emacs") 147 | ;; ignore annoying 'A compilation process is running; kill it? (yes or no)' 148 | ;; in latex override code eliminated in favor of `compilation-always-kill' 149 | (message "Compile command: %s" command) 150 | (compile command))) 151 | 152 | (cl-defmethod flex-compiler-makefile-targets ((this make-flex-compiler)) 153 | "Return all targets supported by the set makefile on THIS compiler." 154 | (let* ((makefile (slot-value this 'config-file)) 155 | (dir (file-name-directory makefile)) 156 | targets) 157 | (with-temp-buffer 158 | (insert (shell-command-to-string (format "make -prRn -C %s" dir))) 159 | (goto-char (point-min)) 160 | (while (re-search-forward "^\\([a-zA-Z0-9-]+\\):" nil t) 161 | (setq targets 162 | (->> (match-string 1) 163 | substring-no-properties 164 | list 165 | (append targets))))) 166 | (->> targets 167 | (cl-remove-if #'(lambda (elt) 168 | (member elt '("run" "clean"))))))) 169 | 170 | (cl-defmethod flex-compiler-makefile-read ((this make-flex-compiler) prop) 171 | "Read target used invoke a make compilation using THIS compiler. 172 | 173 | PROP is the `config-eval-prop' instance used to configure the run target. 174 | 175 | See the class's `initialize-instance', which is how this method is gets 176 | invoked." 177 | (config-prop-entry-set-required this) 178 | (let* ((none "") 179 | (targets (append (flex-compiler-makefile-targets this) (list none))) 180 | (history (slot-value prop 'history)) 181 | (init (or (->> history 182 | symbol-value 183 | delete-dups 184 | (-filter (lambda (elt) (not (equal none elt)))) 185 | cl-second) 186 | none))) 187 | (->> (choice-program-complete 188 | (format "Target or %s for none" none) 189 | targets t nil nil history init nil nil t) 190 | (funcall #'(lambda (elt) 191 | (if (equal none elt) nil elt)))))) 192 | 193 | (cl-defmethod config-prop-set ((this make-flex-compiler) prop val) 194 | "Set property PROP to VAL on THIS compiler. 195 | 196 | This resets the target when changing the file." 197 | (when (eq (config-prop-name prop) 'config-file) 198 | (setf (slot-value this 'target) nil)) 199 | (cl-call-next-method this prop val)) 200 | 201 | (cl-defmethod config-prop-entry-configure ((this make-flex-compiler) 202 | config-options) 203 | "Special behavior when configuring THIS compiler. 204 | 205 | CONFIG-OPTIONS: 206 | - when -1 (using \\[universal-argument] with 0) prompt for the target 207 | - when nil short cut to setting the make target" 208 | (->> (cond ((eq config-options -1) nil) ; unversal arg with 0 209 | ;; shortcut to setting the make target 210 | ((null config-options) '(prop-name target)) 211 | (t config-options)) 212 | (cl-call-next-method this))) 213 | 214 | (cl-defmethod flex-compiler-start-buffer ((this make-flex-compiler) 215 | start-type) 216 | "Return a new buffer for THIS compiler with a processing compilation. 217 | See the `single-buffer-flex-compiler' implementation of 218 | `flex-compiler-start-buffer' for more information and START-TYPE." 219 | (with-slots (target run-target start-directory) this 220 | (let ((default-directory start-directory)) 221 | (cl-case start-type 222 | (compile (flex-compiler-run-make this target)) 223 | (run (flex-compiler-run-make this (or run-target "run"))) 224 | (clean (flex-compiler-run-make this "clean")) 225 | (clean-all (flex-compiler-run-make this "cleanall")))))) 226 | 227 | ;; register the compiler 228 | (flex-compile-manager-register flex-compile-manage-inst (make-flex-compiler)) 229 | 230 | (provide 'flex-compile-make) 231 | 232 | ;;; flex-compile-make.el ends here 233 | -------------------------------------------------------------------------------- /flex-compile-manage.el: -------------------------------------------------------------------------------- 1 | ;;; flex-compile-manage.el --- Manager for flexible compilers -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2015 - 2025 Paul Landes 4 | 5 | ;; Author: Paul Landes 6 | ;; Maintainer: Paul Landes 7 | ;; Keywords: compilation integration processes 8 | ;; URL: https://github.com/plandes/flex-compile 9 | ;; Package-Requires: ((emacs "26.1")) 10 | ;; Package-Version: 0 11 | 12 | ;; This file is not part of GNU Emacs. 13 | 14 | ;; This program is free software; you can redistribute it and/or modify 15 | ;; it under the terms of the GNU General Public License as published by 16 | ;; the Free Software Foundation; either version 2, or (at your option) 17 | ;; any later version. 18 | 19 | ;; This program is distributed in the hope that it will be useful, 20 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | ;; GNU General Public License for more details. 23 | 24 | ;; You should have received a copy of the GNU General Public License 25 | ;; along with this program; if not, write to the Free Software 26 | ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, 27 | ;; Boston, MA 02110-1301, USA. 28 | 29 | ;;; Commentary: 30 | 31 | ;; This file contains the manager object and all requires for concrete compiler 32 | ;; implementations. For this reason, each concreate compiler should need to 33 | ;; only require this library for implementations. 34 | 35 | ;;; Code: 36 | 37 | (require 'cl-lib) 38 | (require 'comint) 39 | (require 'eieio) 40 | (require 'eieio-base) 41 | (require 'dash) 42 | (require 'time-stamp) 43 | (require 'config-manage) 44 | (require 'choice-program-complete) 45 | (require 'flex-compile-base) 46 | (require 'flex-compile-config) 47 | (require 'flex-compile-single-buffer) 48 | (require 'flex-compile-repl) 49 | 50 | ;;; compiler shared configuration 51 | (defcustom flex-compile-manage-load-libraries-entry 'activate 52 | "When to load the compiler's libraries. 53 | 54 | This invokes a method that `require's all the libraries needed for the compiler 55 | to run." 56 | :type '(choice (const :tag "When the compiler is activated" activate) 57 | (const :tag "When the compiler is accessed" assert-ready)) 58 | :group 'flex-compile) 59 | 60 | ;;; compiler manager/orchestration 61 | (defclass flex-compile-manager (config-manager config-persistable) 62 | () 63 | :documentation "\ 64 | Concrete instances of *flexible* compilers that provide a common interface. 65 | Each is an implementation of glue code to the respective compilation method. 66 | 67 | Note that all compilers that extend from `conf-file-flex-compiler', which 68 | include `make', `script', `xml-validate', `org-mode', `python', `clojure', and 69 | `ess' have their `start-directory' property unset each time the `config-file' 70 | is set.") 71 | 72 | (cl-defmethod initialize-instance ((this flex-compile-manager) &optional slots) 73 | "Initialize instance THIS with arguments SLOTS." 74 | (with-slots (entries) this 75 | (setq entries (list (no-op-flex-compiler nil)))) 76 | (cl-call-next-method this slots)) 77 | 78 | (cl-defmethod config-persistable-load ((this flex-compile-manager)) 79 | "Restore the state of THIS instance persistable object." 80 | (with-slots (entries) this 81 | (let ((old-entries entries) 82 | (old-emap (mapcar #'(lambda (entry) 83 | (cons (config-entry-name entry) entry)) 84 | entries))) 85 | (cl-call-next-method this) 86 | (let ((new-emap (mapcar #'(lambda (entry) 87 | (cons (config-entry-name entry) entry)) 88 | entries))) 89 | (setq entries 90 | (remove nil 91 | (mapcar #'(lambda (entry) 92 | (let* ((name (config-entry-name entry)) 93 | (new-entry (cdr (assoc name new-emap)))) 94 | (if new-entry 95 | (if (assoc name old-emap) 96 | new-entry) 97 | entry))) 98 | old-entries)))) 99 | (dolist (compiler entries) 100 | ;; since all compilers are persistable (via `config-entry inhertiance) 101 | ;; set the manager so we can save the state 102 | (setf (slot-value compiler 'manager) this))))) 103 | 104 | (cl-defmethod config-manager-entry-default-name ((this flex-compile-manager)) 105 | "Return `flexible-compiler'. 106 | THIS is the object instance." 107 | (ignore this) 108 | "flexible-compiler") 109 | 110 | (cl-defmethod flex-compile-manager-register ((this flex-compile-manager) 111 | compiler) 112 | "Register COMPILER instance with the manager \(compilation framework). 113 | THIS is the object instance." 114 | (with-slots (entries) this 115 | (setq entries 116 | (cl-delete compiler entries 117 | :test #'(lambda (a b) 118 | (equal (config-entry-name a) 119 | (config-entry-name b))))) 120 | (setq entries (append entries (cons compiler nil))) 121 | (setf (slot-value compiler 'manager) this) 122 | (message "Registered %s compiler" (config-entry-name compiler)))) 123 | 124 | (cl-defmethod config-manager-entry-names ((this flex-compile-manager)) 125 | "Return the names of all registered compilers. 126 | THIS is the object instance." 127 | (with-slots (entries) this 128 | (mapcar #'config-entry-name entries))) 129 | 130 | (cl-defmethod flex-compile-manager-active ((this flex-compile-manager)) 131 | "Return the currently selected or active manager. 132 | THIS is the object instance." 133 | (car (slot-value this 'entries))) 134 | 135 | (cl-defmethod config-manager-activate ((this flex-compile-manager) criteria) 136 | "Switch to a `flex-compiler’ in THIS manager. 137 | 138 | CRITERIA, see the `config-manager’ method ‘config-manager-activate’." 139 | (let ((compiler (cl-call-next-method this criteria))) 140 | (message "Active compiler is now %s" (config-entry-name compiler)) 141 | (when (eq flex-compile-manage-load-libraries-entry 'activate) 142 | (flex-compiler-load-libraries compiler)) 143 | compiler)) 144 | 145 | (cl-defmethod flex-compile-manager-assert-ready ((this flex-compile-manager)) 146 | "Make sure the active/selected compiler is ready and libraries loaded. 147 | THIS is the object instance." 148 | (let ((active (flex-compile-manager-active this))) 149 | (when (eq flex-compile-manage-load-libraries-entry 'assert-ready) 150 | (flex-compiler-load-libraries active)))) 151 | 152 | (cl-defmethod config-manager-remove-entry ((this flex-compile-manager) entry) 153 | "Disallow ENTRY deletion since it is nonsensical for this implementation. 154 | THIS is the object instance." 155 | (ignore this entry) 156 | (config-persistent--unimplemented this "remove-entry")) 157 | 158 | (cl-defmethod flex-compile-clear ((this flex-compile-manager)) 159 | "Clear all compiler's state. 160 | This is done by simply re-instantiating all current registered compilers. 161 | THIS is the object instance." 162 | (let ((entries (slot-value this 'entries))) 163 | (setf (slot-value this 'entries) nil) 164 | (dolist (compiler entries) 165 | (->> (eieio-object-class compiler) 166 | funcall 167 | (flex-compile-manager-register this))))) 168 | 169 | ;; library configuration 170 | (defvar flex-compile-manage-inst 171 | (flex-compile-manager :object-name "compiler") 172 | "The singleton manager instance.") 173 | 174 | (defcustom flex-compile-manage-persistency-file-name 175 | (expand-file-name "flex-compile" user-emacs-directory) 176 | "File containing the Flex compile configuration data." 177 | :type 'file 178 | :group 'flex-compile 179 | :set (lambda (sym val) 180 | (set-default sym val) 181 | (if (and (boundp 'flex-compile-manage-inst) 182 | flex-compile-manage-inst) 183 | (setf (slot-value flex-compile-manage-inst 'file) val)))) 184 | 185 | (provide 'flex-compile-manage) 186 | 187 | ;;; flex-compile-manage.el ends here 188 | -------------------------------------------------------------------------------- /flex-compile-org-export.el: -------------------------------------------------------------------------------- 1 | ;;; flex-compile-org-export.el --- Convenience compiler that evaluates Emacs Lisp -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2015 - 2025 Paul Landes 4 | 5 | ;; Author: Paul Landes 6 | ;; Maintainer: Paul Landes 7 | ;; Keywords: interactive function command compile flexible processes 8 | ;; URL: https://github.com/plandes/flex-compile 9 | ;; Package-Requires: ((emacs "26.1")) 10 | ;; Package-Version: 0 11 | 12 | ;; This file is not part of GNU Emacs. 13 | 14 | ;; This program is free software; you can redistribute it and/or modify 15 | ;; it under the terms of the GNU General Public License as published by 16 | ;; the Free Software Foundation; either version 2, or (at your option) 17 | ;; any later version. 18 | 19 | ;; This program is distributed in the hope that it will be useful, 20 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | ;; GNU General Public License for more details. 23 | 24 | ;; You should have received a copy of the GNU General Public License 25 | ;; along with this program; if not, write to the Free Software 26 | ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, 27 | ;; Boston, MA 02110-1301, USA. 28 | 29 | ;;; Commentary: 30 | 31 | ;; Compiler that exports Org mode to external formats and then shows the 32 | ;; output in the browser. Only HTML is currently supported. 33 | ;; 34 | ;; The `Render File' functionality for the `open-file' property needs the Python 35 | ;; program at `https://github.com/plandes/rend', which is installed with 36 | ;; `pip install zensols.rend'. 37 | 38 | ;;; Code: 39 | 40 | (require 'flex-compile-manage) 41 | 42 | (config-manage-declare-functions 43 | org-open-file 44 | org-twbs-export-to-html) 45 | 46 | ;;; func file compiler 47 | (defclass org-export-flex-compiler (conf-file-flex-compiler) 48 | ((export-format :initarg :export-format 49 | :initform 'org-twbs-export-to-html 50 | :documentation "The Org mode export function.") 51 | (open-file :initarg :open-file 52 | :initform 'default-org-mode 53 | :type symbol 54 | :documentation "The method to open the file after exported.") 55 | (output-directory :initarg :start-directory 56 | :initform nil 57 | :type (or null string) 58 | :documentation "The output directory.") 59 | (frame-focus-command 60 | :initarg :frame-focus-command 61 | :initform 62 | "emacsclient -n --eval \"(select-frame-set-input-focus (selected-frame))\"&" 63 | :type (or null string) 64 | :documentation " 65 | The command to refocus the Emacs frame after rendering the output \(browser).") 66 | (frame-focus-delay :initarg :frame-focus-delay 67 | :initform 1 68 | :type integer 69 | :documentation "\ 70 | Seconds before refocusing the Emacs frame after redering the output.")) 71 | :method-invocation-order :c3 72 | :documentation "\ 73 | This compiler exports [Org mode](https://orgmode.org) to external formats and 74 | then shows the output in the browser. Only HTML is currently supported.") 75 | 76 | (cl-defmethod initialize-instance ((this org-export-flex-compiler) 77 | &optional slots) 78 | "Initialize instance THIS with arguments SLOTS." 79 | (let* ((export-choices '(("Plain HTML" . org-html-export-to-html) 80 | ("Bootstrap HTML" . org-twbs-export-to-html) 81 | ("Latex PDF" . org-latex-export-to-pdf))) 82 | (open-file-choices '(("Default Org Mode" . default-org-mode) 83 | ("Render File" . render-file) 84 | ("Do not open file" . none))) 85 | (props (list 86 | (config-choice-description-prop 87 | :object-name 'export-format 88 | :prompt "Export format" 89 | :prop-entry this 90 | :choices export-choices 91 | :required t 92 | :input-type 'toggle) 93 | (config-choice-description-prop 94 | :object-name 'open-file 95 | :prompt "Open file after generated" 96 | :prop-entry this 97 | :choices open-file-choices 98 | :required t 99 | :input-type 'toggle) 100 | (config-directory-prop 101 | :object-name 'output-directory 102 | :prop-entry this 103 | :prompt "Output directory" 104 | :input-type 'last) 105 | (config-prop 106 | :object-name 'frame-focus-command 107 | :prop-entry this 108 | :prompt "Command to execute to focus Emacs (or RET for None)" 109 | :input-type 'last) 110 | (config-number-prop 111 | :object-name 'frame-focus-delay 112 | :prop-entry this 113 | :prompt "Number of second to wait to focus Emacs" 114 | :input-type 'last)))) 115 | (setq slots (plist-put slots :object-name "org-export") 116 | slots (plist-put slots :description "Org mode") 117 | slots (plist-put slots :validate-modes '(org-mode)) 118 | slots (plist-put slots :props 119 | (append (plist-get slots :props) props)))) 120 | 121 | (cl-call-next-method this slots)) 122 | 123 | (cl-defmethod flex-compiler-load-libraries ((this org-export-flex-compiler)) 124 | "Load `ox-twbs' and `choice-program-complete' libraries for THIS compiler." 125 | (ignore this) 126 | (require 'ox-twbs) 127 | (require 'choice-program-complete)) 128 | 129 | (cl-defmethod config-prop-set ((this org-export-flex-compiler) prop val) 130 | "Set property PROP to VAL for THIS compiler." 131 | (if (eq (config-prop-name prop) 'config-file) 132 | ;; when the file configuration file (Org mode file) changes, we must 133 | ;; mirror the output directory for default behavior; then allow the user 134 | ;; to change the output directory to export some where elese 135 | (with-slots (output-directory) this 136 | (config-prop-validate prop val) 137 | (setq output-directory (file-name-directory val)))) 138 | (cl-call-next-method this prop val)) 139 | 140 | (cl-defmethod flex-compiler--replace-ext ((this org-export-flex-compiler) ext) 141 | "Replace the extension of the configuration file of THIS compiler with EXT." 142 | (with-slots (config-file) this 143 | (replace-regexp-in-string "\\.org$" (format ".%s" ext) config-file))) 144 | 145 | (cl-defmethod flex-compiler-org-export-source ((this org-export-flex-compiler)) 146 | "Return the generated target file by the Org system for THIS compiler." 147 | (with-slots (config-file export-format) this 148 | (let ((ext (if (eq 'org-latex-export-to-pdf export-format) "pdf" "html"))) 149 | (flex-compiler--replace-ext this ext)))) 150 | 151 | (cl-defmethod flex-compiler-org-export-dest ((this org-export-flex-compiler)) 152 | "Return the user desired location of the output file. 153 | THIS is the object instance." 154 | (with-slots (config-file output-directory) this 155 | (let* ((src (flex-compiler-org-export-source this)) 156 | (dst (file-name-nondirectory src))) 157 | (concat (file-name-as-directory output-directory) dst)))) 158 | 159 | (cl-defmethod flex-compiler-compile ((this org-export-flex-compiler)) 160 | "Export the Org mode file and open the resulting file in a browser. 161 | 162 | By default, this uses `org-twbs-export-to-html' set on the `export-format' slot. 163 | 164 | THIS is the object instance." 165 | (config-prop-entry-set-required this) 166 | (cl-flet ((om-render-file (file-name) 167 | (message "Showing file %s" file-name) 168 | (shell-command (format "rend show %s &" file-name)))) 169 | (with-slots (export-format config-file open-file) this 170 | (with-current-buffer (flex-compiler-conf-file-buffer this) 171 | (let* ((open-fn (cl-case open-file 172 | (default-org-mode #'org-open-file) 173 | (render-file #'om-render-file) 174 | (none #'identity))) 175 | (src (save-excursion (call-interactively export-format))) 176 | (dst (flex-compiler-org-export-dest this))) 177 | (rename-file src dst t) 178 | (message "Exported file to %s" dst) 179 | ;; call the function to open the file if configured 180 | (funcall open-fn dst))))) 181 | (with-slots (frame-focus-command frame-focus-delay) this 182 | (when (and frame-focus-command (> (length frame-focus-command) 0)) 183 | (sit-for frame-focus-delay) 184 | (with-temp-buffer 185 | (shell-command frame-focus-command (current-buffer)))))) 186 | 187 | (cl-defmethod flex-compiler-clean ((this org-export-flex-compiler) 188 | &optional allp) 189 | "Invoke the clean functionality of THIS compiler. 190 | if ALLP is non-nil, then invoke a more destructive cleaning when supported." 191 | (ignore allp) 192 | (config-prop-entry-set-required this) 193 | (with-slots (config-file export-format) this 194 | (let* ((view-file (flex-compiler-org-export-dest this)) 195 | (derived (when (eq 'org-latex-export-to-pdf export-format) 196 | (list (flex-compiler--replace-ext this "tex")))) 197 | (files (append (list view-file) derived))) 198 | (dolist (file files) 199 | (if (not (file-exists-p file)) 200 | (message "File %s doesn't exist" file) 201 | (delete-file file t) 202 | (message "Deleted %s" file)))))) 203 | 204 | (flex-compile-manager-register flex-compile-manage-inst 205 | (org-export-flex-compiler)) 206 | 207 | (provide 'flex-compile-org-export) 208 | 209 | ;;; flex-compile-org-export.el ends here 210 | -------------------------------------------------------------------------------- /flex-compile-python.el: -------------------------------------------------------------------------------- 1 | ;;; flex-compile-python.el --- Python compile functions -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2015 - 2025 Paul Landes 4 | 5 | ;; Author: Paul Landes 6 | ;; Maintainer: Paul Landes 7 | ;; Keywords: python integration compilation processes 8 | ;; URL: https://github.com/plandes/flex-compile 9 | ;; Package-Requires: ((emacs "26.1")) 10 | ;; Package-Version: 0 11 | 12 | ;; This file is not part of GNU Emacs. 13 | 14 | ;; This program is free software; you can redistribute it and/or modify 15 | ;; it under the terms of the GNU General Public License as published by 16 | ;; the Free Software Foundation; either version 2, or (at your option) 17 | ;; any later version. 18 | 19 | ;; This program is distributed in the hope that it will be useful, 20 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | ;; GNU General Public License for more details. 23 | 24 | ;; You should have received a copy of the GNU General Public License 25 | ;; along with this program; if not, write to the Free Software 26 | ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, 27 | ;; Boston, MA 02110-1301, USA. 28 | 29 | ;;; Commentary: 30 | 31 | ;;; Implementation compiler for python integration 32 | 33 | ;;; Code: 34 | 35 | (require 'subr-x) 36 | (require 'flex-compile-manage) 37 | (require 'flex-compile-repl) 38 | 39 | (declare-function python-nav-backward-statement "python") 40 | (declare-function python-nav-forward-statement "python") 41 | (declare-function python-shell-calculate-command "python") 42 | (declare-function python-shell-completion-native-setup "python") 43 | (declare-function python-shell-parse-command "python") 44 | (declare-function python-shell-send-buffer "python") 45 | (declare-function python-shell-send-string "python") 46 | (declare-function run-python "python") 47 | 48 | (config-manage-declare-variables 49 | python-shell-completion-native-enable) 50 | 51 | (defun flex-compile-python-path () 52 | "Return the PYTHONPATH environment to be used when creating the REPL." 53 | (getenv "PYTHONPATH")) 54 | 55 | (defclass python-flex-compiler (repl-flex-compiler) 56 | () 57 | :method-invocation-order :c3 58 | :documentation "\ 59 | This is a REPL based compiler that allows for evaluation Python buffers and 60 | expressions using [python mode](https://github.com/fgallina/python.el).") 61 | 62 | (cl-defmethod initialize-instance ((this python-flex-compiler) &optional slots) 63 | "Initialize instance THIS with arguments SLOTS." 64 | (setq slots (plist-put slots :object-name "python") 65 | slots (plist-put slots :validate-modes '(python-mode)) 66 | slots (plist-put slots :repl-buffer-regexp "^\\*Python\\*$") 67 | slots (plist-put slots :repl-buffer-start-timeout 0)) 68 | (cl-call-next-method this slots)) 69 | 70 | (cl-defmethod flex-compiler-load-libraries ((this python-flex-compiler)) 71 | "Load the `python' library for THIS compiler." 72 | (ignore this) 73 | (require 'python)) 74 | 75 | (cl-defmethod flex-compiler-eval-form-impl ((this python-flex-compiler) form) 76 | "Evaluate the FORM and return the response of the REPL for THIS compiler." 77 | (ignore this) 78 | (python-shell-send-string form)) 79 | 80 | (cl-defmethod flex-compiler-start-buffer ((this python-flex-compiler) 81 | start-type) 82 | "Return a new buffer for THIS compiler with a processing compilation. 83 | START-TYPE is either symbols `compile', `run', `clean' depending 84 | if invoked by `flex-compiler-compile' or `flex-compiler-run'." 85 | (let (ret) 86 | (if (eq start-type 'compile) 87 | (if (flex-compiler-repl-running-p this) 88 | (setq ret (cl-call-next-method this start-type)) 89 | (let ((do-native-p python-shell-completion-native-enable) 90 | (python-shell-completion-native-enable nil)) 91 | (flex-compiler-run this) 92 | (if do-native-p 93 | (python-shell-completion-native-setup))))) 94 | (or ret (cl-call-next-method this start-type)))) 95 | 96 | (cl-defmethod flex-compiler-repl-compile ((this python-flex-compiler) file) 97 | "Send the contents of source code FILE to the REPL buffer of THIS compiler." 98 | (ignore this) 99 | (let ((buf (find-file-noselect file))) 100 | (with-current-buffer buf 101 | (python-shell-send-buffer)))) 102 | 103 | (cl-defmethod flex-compiler-eval-initial-at-point ((this python-flex-compiler)) 104 | "Return the Python form at the current point to the REPL for THIS compiler." 105 | (ignore this) 106 | (let ((forward-fn #'python-nav-forward-statement) 107 | (backward-fn #'python-nav-backward-statement)) 108 | (save-excursion 109 | (->> (buffer-substring 110 | (progn 111 | (end-of-line 1) 112 | (while (and (or (funcall backward-fn) 113 | (beginning-of-line 1)) 114 | (> (current-indentation) 0))) 115 | (forward-line 1) 116 | (point-marker)) 117 | (progn 118 | (or (funcall forward-fn) 119 | (end-of-line 1)) 120 | (point-marker))) 121 | string-trim)))) 122 | 123 | (cl-defmethod flex-compiler-repl-start ((this python-flex-compiler)) 124 | "Start the REPL using THIS compiler." 125 | (ignore this) 126 | (let ((old-path (getenv "PYTHONPATH"))) 127 | (unwind-protect 128 | (let ((new-path (flex-compile-python-path))) 129 | (setenv "PYTHONPATH" new-path) 130 | (run-python (python-shell-calculate-command) nil 4)) 131 | (setenv "PYTHONPATH" old-path)))) 132 | 133 | (flex-compile-manager-register flex-compile-manage-inst (python-flex-compiler)) 134 | 135 | (provide 'flex-compile-python) 136 | 137 | ;;; flex-compile-python.el ends here 138 | -------------------------------------------------------------------------------- /flex-compile-repl.el: -------------------------------------------------------------------------------- 1 | ;;; flex-compile-repl.el --- A REPL based compiler -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2015 - 2025 Paul Landes 4 | 5 | ;; Author: Paul Landes 6 | ;; Maintainer: Paul Landes 7 | ;; Keywords: compilation integration processes 8 | ;; URL: https://github.com/plandes/flex-compile 9 | ;; Package-Requires: ((emacs "26.1")) 10 | ;; Package-Version: 0 11 | 12 | ;; This file is not part of GNU Emacs. 13 | 14 | ;; This program is free software; you can redistribute it and/or modify 15 | ;; it under the terms of the GNU General Public License as published by 16 | ;; the Free Software Foundation; either version 2, or (at your option) 17 | ;; any later version. 18 | 19 | ;; This program is distributed in the hope that it will be useful, 20 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | ;; GNU General Public License for more details. 23 | 24 | ;; You should have received a copy of the GNU General Public License 25 | ;; along with this program; if not, write to the Free Software 26 | ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, 27 | ;; Boston, MA 02110-1301, USA. 28 | 29 | ;;; Commentary: 30 | 31 | ;; Run, evaluate and compile functionality for a variety of different languages 32 | ;; and modes. The specific "compilation" method is different across each 33 | ;; add-on library. For example, for ESS and Clojure you can evaluate a 34 | ;; specific file and/or evaluate a specfic expression via a REPL. For running 35 | ;; a script or starting a `make` an async process is started. 36 | ;; 37 | ;; For more information see https://github.com/plandes/flex-compile 38 | 39 | ;;; Code: 40 | 41 | (require 'comint) 42 | (require 'eieio) 43 | (require 'flex-compile-config) 44 | 45 | (defclass repl-flex-compiler (single-buffer-flex-compiler 46 | conf-file-flex-compiler) 47 | ((repl-buffer-regexp :initarg :repl-buffer-regexp 48 | :type string 49 | :documentation "\ 50 | Regular expression to match buffers for functions like killing the session.") 51 | (derived-buffer-names :initarg :derived-buffer-names 52 | :initform nil 53 | :type list 54 | :documentation "\ 55 | List of buffers for functions (like killing a buffer) when session ends.") 56 | (repl-buffer-start-wait :initarg :repl-buffer-start-wait 57 | :initform 0 58 | :type number 59 | :documentation "\ 60 | Number of seconds (as a float) to wait before issuing any first command to the 61 | REPL.") 62 | (repl-buffer-start-timeout :initarg :repl-buffer-start-timeout 63 | :initform 1 64 | :type integer 65 | :documentation "\ 66 | Number of seconds as an integer to wait to start before giving up (and not 67 | displaying). If this is 0, don't wait or display the buffer when it comes 68 | up.") 69 | (prompt-kill-repl-buffer :initarg :prompt-kill-repl-buffer 70 | :initform t 71 | :type boolean 72 | :documentation "\ 73 | If non-`nil' then prompt to kill a REPL buffer on clean.") 74 | (output-clear :initarg :output-clear 75 | :initform nil 76 | :type boolean 77 | :documentation "\ 78 | Whether or not to clear comint buffer after a compilation.") 79 | (form-history :initarg :form-history 80 | :initform (gensym "config-repl-form-history") 81 | :type symbol 82 | :documentation "\ 83 | The history variable for the eval form history.")) 84 | :method-invocation-order :c3 85 | :documentation "Compiles by evaluating expressions in the REPL.") 86 | 87 | (cl-defmethod initialize-instance ((this repl-flex-compiler) &optional slots) 88 | "Initialize instance THIS with arguments SLOTS." 89 | (let ((props 90 | (list 91 | (config-boolean-prop :object-name 'output-clear 92 | :prop-entry this 93 | :prompt "Clear output on compile" 94 | :input-type 'toggle) 95 | (config-boolean-prop :object-name 'prompt-kill-repl-buffer 96 | :prop-entry this 97 | :prompt "Confirm REPL buffer kills" 98 | :input-type 'toggle) 99 | (config-number-prop :object-name 'repl-buffer-start-timeout 100 | :prop-entry this 101 | :prompt "Seconds to wait for REPL to start") 102 | (config-number-prop :object-name 'repl-buffer-start-wait 103 | :prop-entry this 104 | :number-type 'float 105 | :prompt "Seconds to wait before starting the REPL")))) 106 | (setq slots (plist-put slots 107 | :props (append (plist-get slots :props) props)))) 108 | (cl-call-next-method this slots)) 109 | 110 | (cl-defmethod flex-compiler-repl-start ((this repl-flex-compiler)) 111 | "Start the REPL using THIS compiler." 112 | (config-persistent--unimplemented this "start-repl")) 113 | 114 | (cl-defmethod flex-compiler-repl-compile ((this repl-flex-compiler) file) 115 | "Invoked by `compile' type messages from THIS compiler. 116 | 117 | FILE gets evaluated by the compiler either as a IPC communication or by direct 118 | insertion in the REPL buffer. 119 | 120 | This method is meant to allow for REPL compiles \(really some kind of 121 | evaluation), while allowing base class compilation features.." 122 | (ignore this file) 123 | (config-persistent--unimplemented this "repl-compile")) 124 | 125 | (cl-defmethod flex-compiler-wait-for-buffer ((this repl-flex-compiler)) 126 | "Wait for the compilation to start using THIS compiler. 127 | 128 | The caller raises and error if it doesn't start in time." 129 | (with-slots (repl-buffer-start-timeout) this 130 | (let ((count-down repl-buffer-start-timeout) 131 | buf) 132 | (cl-block wfb 133 | (dotimes (i count-down) 134 | (setq buf (flex-compiler-buffer this)) 135 | (if buf 136 | (cl-return-from wfb buf) 137 | (message "Waiting for buffer to start... (%d)" 138 | (- count-down i)) 139 | (sit-for 1))))))) 140 | 141 | (cl-defmethod flex-compiler-repl-running-p ((this repl-flex-compiler)) 142 | "Return whether or not THIS REPL is currently running." 143 | (not (null (flex-compiler-buffer this)))) 144 | 145 | (cl-defmethod flex-compiler-repl-assert-running ((this repl-flex-compiler)) 146 | "Raise an error if THIS REPL *is* running." 147 | (unless (flex-compiler-repl-running-p this) 148 | (error "The REPL for %s isn't started" (config-entry-name this)))) 149 | 150 | (cl-defmethod flex-compiler-repl-assert-not-running ((this repl-flex-compiler)) 151 | "Raise an error if THIS REPL isn't running." 152 | (if (flex-compiler-repl-running-p this) 153 | (error "Compiler %s is already running" 154 | (config-entry-name this)))) 155 | 156 | (cl-defmethod flex-compiler-repl--run-start ((this repl-flex-compiler)) 157 | "Start THIS compiler's REPL if it isn't already." 158 | (with-slots (repl-buffer-start-timeout 159 | repl-buffer-start-wait 160 | start-directory) this 161 | (let ((timeout repl-buffer-start-timeout) 162 | buf) 163 | (unless (flex-compiler-repl-running-p this) 164 | (config-prop-entry-set-required this) 165 | (let ((default-directory (or start-directory default-directory))) 166 | (flex-compiler-repl-start this)) 167 | (when (> timeout 0) 168 | (setq buf (flex-compiler-wait-for-buffer this)) 169 | (unless buf 170 | (error "Couldn't create REPL for compiler %s" 171 | (config-entry-name this)))) 172 | (when (> repl-buffer-start-wait 0) 173 | (sit-for repl-buffer-start-wait)))))) 174 | 175 | (cl-defmethod flex-compiler-send-input ((this repl-flex-compiler) 176 | &optional command) 177 | "Send a COMMAND \(input) to THIS compiler's REPL." 178 | (ignore this) 179 | (goto-char (point-max)) 180 | (insert command) 181 | (comint-send-input)) 182 | 183 | (cl-defmethod flex-compiler-run-command ((this repl-flex-compiler) 184 | &optional command) 185 | "Send COMMAND to THIS compiler's REPL to evaluate or start a process." 186 | (flex-compiler-repl-assert-running this) 187 | (let ((buf (flex-compiler-buffer this))) 188 | (with-current-buffer buf 189 | (if command 190 | (flex-compiler-send-input this command))))) 191 | 192 | (cl-defmethod flex-compiler-buffer ((this repl-flex-compiler)) 193 | "Find THIS compiler's first REPL buffer found in the buffer list." 194 | (with-slots (repl-buffer-regexp) this 195 | (cl-block found-buf 196 | (dolist (buf (buffer-list)) 197 | (let ((buf-name (buffer-name buf))) 198 | (when (string-match repl-buffer-regexp buf-name) 199 | (cl-return-from found-buf buf))))))) 200 | 201 | (cl-defmethod flex-compiler--kill-buffer ((this repl-flex-compiler) buffer) 202 | "Kill THIS compiler's BUFFER. 203 | It kills the buffer without prompting However, in some cases, it might raise 204 | an erorr." 205 | (ignore this) 206 | (with-slots (prompt-kill-repl-buffer) this 207 | (when (buffer-live-p buffer) 208 | (let ((kill-buffer-query-functions 209 | (if prompt-kill-repl-buffer 210 | kill-buffer-query-functions 211 | nil))) 212 | (kill-buffer buffer))))) 213 | 214 | (cl-defmethod flex-compiler-kill-repl ((this repl-flex-compiler)) 215 | "Kill THIS compiler's REPL." 216 | (with-slots (derived-buffer-names prompt-kill-repl-buffer) this 217 | (let ((bufs (append (mapcar 'get-buffer derived-buffer-names) 218 | (cons (flex-compiler-buffer this) nil))) 219 | (count 0)) 220 | (dolist (buf bufs) 221 | (when (and buf (buffer-live-p buf)) 222 | (flex-compiler--kill-buffer this buf) 223 | (cl-incf count))) 224 | (message "%s killed %d buffer(s)" 225 | (capitalize (config-entry-name this)) count)))) 226 | 227 | (cl-defmethod flex-compiler-clear-buffer ((this repl-flex-compiler)) 228 | "Clear THIS compiler's REPL buffer." 229 | (with-current-buffer (flex-compiler-buffer this) 230 | (comint-clear-buffer))) 231 | 232 | (cl-defmethod flex-compiler-start-buffer ((this repl-flex-compiler) start-type) 233 | "Return a new buffer for THIS compiler with a processing compilation. 234 | START-TYPE is either symbols `compile', `run', `clean' depending 235 | if invoked by `flex-compiler-compile' or `flex-compiler-run'." 236 | (config-prop-entry-set-required this) 237 | (with-slots (config-file output-clear) this 238 | (let ((runningp (flex-compiler-repl-running-p this))) 239 | (cl-case start-type 240 | (compile (progn 241 | (unless runningp 242 | (flex-compiler-run this)) 243 | (if (flex-compiler-repl-running-p this) 244 | (progn 245 | (when output-clear 246 | (flex-compiler-clear-buffer this)) 247 | (flex-compiler-repl-compile this config-file)) 248 | (if runningp 249 | (error "REPL hasn't started") 250 | (message "REPL still starting, please wait"))) 251 | (flex-compiler-buffer this))) 252 | (run (progn 253 | (flex-compiler-repl--run-start this) 254 | (flex-compiler-buffer this))) 255 | (clean (progn 256 | (flex-compiler-kill-repl this) 257 | 'killed-buffer)))))) 258 | 259 | (cl-defmethod flex-compiler-eval-initial-at-point ((this repl-flex-compiler)) 260 | "Return the expression at or right before the point for THIS compiler." 261 | (ignore this)) 262 | 263 | (cl-defmethod flex-compiler-eval-form-impl ((this repl-flex-compiler) form) 264 | "Evaluate the FORM and return the response of the REPL for THIS compiler." 265 | (ignore this form) 266 | (config-persistent--unimplemented this "eval-form-impl")) 267 | 268 | (cl-defmethod flex-compiler-query-read-form ((this repl-flex-compiler) 269 | no-input-p) 270 | "Read a form, meaningful for THIS compiler from the user. 271 | NO-INPUT-P, if non-nil, use the symbolic expression at the current point." 272 | (with-slots (form-history) this 273 | (let ((init (flex-compiler-eval-initial-at-point this))) 274 | (if no-input-p 275 | init 276 | (read-string "Form: " init form-history))))) 277 | 278 | (cl-defmethod flex-compiler-evaluate-form ((this repl-flex-compiler) 279 | &optional form) 280 | "Return the evaluation of FORM for THIS compiler. 281 | See the `:eval-form' slot." 282 | (let ((res (flex-compiler-eval-form-impl this form))) 283 | (if (stringp res) 284 | res 285 | (prin1-to-string res)))) 286 | 287 | (provide 'flex-compile-repl) 288 | 289 | ;;; flex-compile-repl.el ends here 290 | -------------------------------------------------------------------------------- /flex-compile-script.el: -------------------------------------------------------------------------------- 1 | ;;; flex-compile-script.el --- Script compile functions -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2015 - 2025 Paul Landes 4 | 5 | ;; Author: Paul Landes 6 | ;; Maintainer: Paul Landes 7 | ;; Keywords: script integration compilation processes 8 | ;; URL: https://github.com/plandes/flex-compile 9 | ;; Package-Requires: ((emacs "26.1")) 10 | ;; Package-Version: 0 11 | 12 | ;; This file is not part of GNU Emacs. 13 | 14 | ;; This program is free software; you can redistribute it and/or modify 15 | ;; it under the terms of the GNU General Public License as published by 16 | ;; the Free Software Foundation; either version 2, or (at your option) 17 | ;; any later version. 18 | 19 | ;; This program is distributed in the hope that it will be useful, 20 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | ;; GNU General Public License for more details. 23 | 24 | ;; You should have received a copy of the GNU General Public License 25 | ;; along with this program; if not, write to the Free Software 26 | ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, 27 | ;; Boston, MA 02110-1301, USA. 28 | 29 | ;;; Commentary: 30 | 31 | ;; Implementation compiler for script integration 32 | 33 | ;;; Code: 34 | 35 | (require 'flex-compile-manage) 36 | 37 | ;;; script file compiler 38 | (defclass script-flex-compiler (single-buffer-flex-compiler 39 | conf-file-flex-compiler) 40 | ((arguments :initarg :arguments 41 | :initform nil 42 | :documentation "The arguments to give to the script.")) 43 | :method-invocation-order :c3 44 | :documentation "\ 45 | This compiler runs a script with optional arguments in an async buffer. 46 | See [motivation](#motivation).") 47 | 48 | (cl-defmethod initialize-instance ((this script-flex-compiler) &optional slots) 49 | "Initialize THIS instance using SLOTS as initial values." 50 | (let* ((fn (lambda (this compiler default prompt history) 51 | (ignore this prompt compiler) 52 | (let ((input (read-string prompt nil history default))) 53 | (and input (split-string input))))) 54 | (props (list (config-eval-prop :object-name 'arguments 55 | :prompt "Arguments (SPACE RET for none)" 56 | :func fn 57 | :prop-entry this 58 | :input-type 'last)))) 59 | (setq slots (plist-put slots :object-name "script") 60 | slots (plist-put slots :description "Script") 61 | slots (plist-put slots :validate-modes 62 | '(sh-mode cperl-mode python-mode clojure-mode)) 63 | slots (plist-put slots :buffer-name "Script Compile") 64 | slots (plist-put slots :kill-buffer-clean t) 65 | slots (plist-put slots :props 66 | (append (plist-get slots :props) props)))) 67 | (cl-call-next-method this slots)) 68 | 69 | (cl-defmethod flex-compiler-load-libraries ((this script-flex-compiler)) 70 | "Load `compile' and `choice-program' libraries for THIS compiler." 71 | (ignore this) 72 | (require 'compile) 73 | (require 'choice-program)) 74 | 75 | (cl-defmethod config-prop-set ((this script-flex-compiler) prop val) 76 | "Set property PROP to VAL on THIS compiler. 77 | In addition, set the `arguments' `config-prop' to nil." 78 | (setf (slot-value this 'arguments) nil) 79 | (cl-call-next-method this prop val)) 80 | 81 | (cl-defmethod flex-compiler-start-buffer ((this script-flex-compiler) 82 | start-type) 83 | "Return a new buffer for THIS compiler with a processing compilation. 84 | See the `single-buffer-flex-compiler' implementation of 85 | `flex-compiler-start-buffer' for more information and START-TYPE." 86 | (cl-case start-type 87 | (compile 88 | (with-slots (config-file start-directory arguments) this 89 | (let ((default-directory start-directory) 90 | (buffer-name (flex-compiler-buffer-name this)) 91 | (cmd (concat config-file " " 92 | (mapconcat #'identity arguments " "))) 93 | buf) 94 | (with-current-buffer 95 | (setq buf 96 | (compilation-start cmd nil (lambda (_) buffer-name)))) 97 | buf))) 98 | (run (error "No defined run action for scripts")))) 99 | 100 | (flex-compile-manager-register flex-compile-manage-inst (script-flex-compiler)) 101 | 102 | (provide 'flex-compile-script) 103 | 104 | ;;; flex-compile-script.el ends here 105 | -------------------------------------------------------------------------------- /flex-compile-single-buffer.el: -------------------------------------------------------------------------------- 1 | ;;; flex-compile-single-buffer.el --- Compiler for single buffer management -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2015 - 2025 Paul Landes 4 | 5 | ;; Author: Paul Landes 6 | ;; Maintainer: Paul Landes 7 | ;; Keywords: compilation integration processes 8 | ;; URL: https://github.com/plandes/flex-compile 9 | ;; Package-Requires: ((emacs "26.1")) 10 | ;; Package-Version: 0 11 | 12 | ;; This file is not part of GNU Emacs. 13 | 14 | ;; This program is free software; you can redistribute it and/or modify 15 | ;; it under the terms of the GNU General Public License as published by 16 | ;; the Free Software Foundation; either version 2, or (at your option) 17 | ;; any later version. 18 | 19 | ;; This program is distributed in the hope that it will be useful, 20 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | ;; GNU General Public License for more details. 23 | 24 | ;; You should have received a copy of the GNU General Public License 25 | ;; along with this program; if not, write to the Free Software 26 | ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, 27 | ;; Boston, MA 02110-1301, USA. 28 | 29 | ;;; Commentary: 30 | 31 | ;; This file contains an abstract utility class that provides other compilers 32 | ;; the ability to display the output of the compilation across buffers and 33 | ;; windows. 34 | 35 | ;;; Code: 36 | 37 | (require 'cl-lib) 38 | (require 'dash) 39 | (require 'eieio) 40 | (require 'flex-compile-base) 41 | 42 | (defconst flex-compile-single-buffer-display-mode-options 43 | '(choice (const :tag "This Window" switch) 44 | (const :tag "Other Window" display) 45 | (const :tag "Next Frame Otherwise Switch" next-frame-switch) 46 | (const :tag "Next Frame Otherwise Display" next-frame-display) 47 | (const :tag "Next Frame Skip Switch" next-frame-skip-switch) 48 | (const :tag "Next Frame Skip Display" next-frame-skip-display) 49 | (const :tag "Never" never) 50 | (const :tag "Only If Error" only-if-error)) 51 | "Customize system's metadata for options displaying the single buffer.") 52 | 53 | (defcustom flex-compile-single-buffer-display-buffer-new-mode 54 | 'next-frame-display 55 | "How to show/switch a new \(not yet created) compilation buffer. 56 | 57 | `Switch to Buffer' means to first pop then switch to the buffer. 58 | 59 | `Display Buffer' means to show the buffer in a different 60 | window (see `display-buffer'). 61 | 62 | `Next Frame Otherwise Switch' means to use the next frame if 63 | there are multiple frames, otherwise pop and switch to the buffer. 64 | 65 | `Next Frame Otherwise Display' means to use the next frame if 66 | there are multiple frames, otherwise show buffer. 67 | 68 | `Next Frame Skip Switch' means to do nothing there are 69 | multiple frames, otherwise pop and switch to the buffer. 70 | 71 | `Next Frame Skip Display' means to do nothing there are multiple 72 | frames, otherwise display the buffer. 73 | 74 | `Never' means to never show the buffer. 75 | 76 | `Only If Error' means to display the buffer if at least one error was detected. 77 | Note: and not all compilers support this option." 78 | :type flex-compile-single-buffer-display-mode-options 79 | :group 'flex-compile) 80 | 81 | (defcustom flex-compile-single-buffer-display-buffer-exists-mode 82 | 'next-frame-skip-display 83 | "Like `flex-compile-single-buffer-display-buffer-new-mode' for existing buffers." 84 | :type flex-compile-single-buffer-display-mode-options 85 | :group 'flex-compile) 86 | 87 | (defvar flex-compile-single-buffer-set-buffer-exists 88 | (delete-dups 89 | (list flex-compile-single-buffer-display-buffer-exists-mode 'never)) 90 | "History variable for `flex-compile-single-buffer-set-buffer-exists-mode'.") 91 | 92 | 93 | (defclass config-timeout-prop (config-prop) 94 | () 95 | :method-invocation-order :c3 96 | :documentation "\ 97 | A time out property that represents number of seconds to wait for something. 98 | No time out \(nil) means to wait indefinitly.") 99 | 100 | (cl-defmethod config-prop-read ((this config-timeout-prop)) 101 | "Read a number in seconds to wait for something to happen. 102 | THIS is the object instance." 103 | (with-slots (history description prompt) this 104 | (let* ((prompt (format "%s or RET for none: " prompt)) 105 | (default (config-prop-default-input this)) 106 | (timeout (read-string prompt default history))) 107 | (if (= 0 (length timeout)) 108 | nil 109 | (string-to-number timeout))))) 110 | 111 | 112 | (defclass single-buffer-flex-compiler (flex-compiler) 113 | ((buffer-new-mode :initarg :buffer-new-mode 114 | :initform 'global 115 | :type symbol 116 | :documentation "\ 117 | Compiler instance of `flex-compile-single-buffer-display-buffer-new-mode'. 118 | This is one of `flex-compile-single-buffer-display-mode-options' 119 | or `global' to use the value of 120 | `flex-compile-single-buffer-display-buffer-new-mode'") 121 | (buffer-exists-mode :initarg :buffer-exists-mode 122 | :initform 'global 123 | :type symbol 124 | :documentation "\ 125 | Compiler instance of `flex-compile-single-buffer-display-buffer-exists-mode'. 126 | This is one of `flex-compile-single-buffer-display-mode-options' 127 | or `global' to use the value of 128 | `flex-compile-single-buffer-display-buffer-exists-mode'") 129 | (buffer-name :initarg :buffer-name 130 | :type string 131 | :documentation "The default name of the single buffer.") 132 | (kill-buffer-clean :initarg :kill-buffer-clean 133 | :initform nil 134 | :type (or integer boolean) 135 | :documentation "\ 136 | If non-nil kill the buffer on clean. 137 | If this is an integer, wait the value in seconds and then kill.") 138 | (last-displayed-context :initarg :last-displayed-context 139 | :initform nil 140 | :type (or null cons) 141 | :documentation "\ 142 | The buffer and new status of the last displayed buffer")) 143 | :abstract t 144 | :method-invocation-order :c3 145 | :documentation "A flex compiler that has a single buffer.") 146 | 147 | (cl-defmethod initialize-instance ((this single-buffer-flex-compiler) 148 | &optional slots) 149 | "Initialize instance THIS with arguments SLOTS." 150 | (let* ((choices (->> (cdr flex-compile-single-buffer-display-mode-options) 151 | (-map #'(lambda (elt) 152 | `(,(nth 2 elt) . ,(car (last elt))))) 153 | (append '(("Global" . global))))) 154 | (props (list (config-choice-description-prop 155 | :object-name 'buffer-exists-mode 156 | :prop-entry this 157 | :prompt "Exists buffer mode" 158 | :choices choices 159 | :order 10 160 | :input-type 'toggle) 161 | (config-choice-description-prop 162 | :object-name 'buffer-new-mode 163 | :prop-entry this 164 | :prompt "New buffer mode" 165 | :choices choices 166 | :order 11 167 | :input-type 'toggle) 168 | (config-timeout-prop 169 | :object-name 'kill-buffer-clean 170 | :prop-entry this 171 | :prompt "Clean buffer time out" 172 | :order 50 173 | :input-type 'last)))) 174 | (setq slots (plist-put slots 175 | :props (append (plist-get slots :props) props)))) 176 | (cl-call-next-method this slots)) 177 | 178 | (cl-defmethod flex-compiler-buffer-name ((this single-buffer-flex-compiler)) 179 | "Return the name of the single buffer for THIS compiler." 180 | (format "*%s*" (slot-value this 'buffer-name))) 181 | 182 | (cl-defmethod flex-compiler-buffer ((this single-buffer-flex-compiler)) 183 | "Return non-nil if there exists a buffer for THIS compiler and is live." 184 | (get-buffer (flex-compiler-buffer-name this))) 185 | 186 | (cl-defmethod flex-compiler-start-buffer ((this single-buffer-flex-compiler) 187 | start-type) 188 | "Return a new buffer for THIS compiler with a processing compilation. 189 | START-TYPE is either symbols `compile', `run', `clean' depending 190 | if invoked by `flex-compiler-compile' or `flex-compiler-run'." 191 | (ignore this start-type) 192 | (config-persistent--unimplemented this "start-buffer")) 193 | 194 | (cl-defmethod flex-compiler-display-modes ((this single-buffer-flex-compiler)) 195 | "Return an alist with keys `new' and `exists' for THIS compiler. 196 | 197 | This implementation returns: 198 | - `flex-compile-single-buffer-display-buffer-new-mode', and 199 | - `flex-compile-single-buffer-display-buffer-exists-mode' 200 | in the following form: 201 | ((newp . 202 | (buffer . )))" 203 | (with-slots (buffer-new-mode buffer-exists-mode) this 204 | (let ((new-mode (if (eq buffer-new-mode 'global) 205 | flex-compile-single-buffer-display-buffer-new-mode 206 | buffer-new-mode)) 207 | (exists-mode (if (eq buffer-exists-mode 'global) 208 | flex-compile-single-buffer-display-buffer-exists-mode 209 | buffer-exists-mode))) 210 | `((new . ,new-mode) 211 | (exists . ,exists-mode))))) 212 | 213 | (defun flex-compile-single-buffer-display-function (mode) 214 | "Return a function used for displaying a buffer using MODE. 215 | MODE is either `flex-compile-single-buffer-display-buffer-new-mode' or 216 | `flex-compile-single-buffer-display-buffer-exists-mode'." 217 | (cl-flet ((display-nf 218 | (single-frame-fn multi-frame-fn) 219 | (if (> (length (visible-frame-list)) 1) 220 | (if multi-frame-fn 221 | multi-frame-fn 222 | '(lambda (buf) 223 | (-> (window-list (next-frame)) 224 | car 225 | (set-window-buffer buf)))) 226 | single-frame-fn))) 227 | (let ((void-fn 'list) 228 | (display-fn 229 | '(lambda (buf) 230 | (let ((display-buffer-fallback-action nil)) 231 | (if (= 1 (length (window-list))) 232 | (split-window)) 233 | (or (display-buffer buf '(display-buffer-reuse-window 234 | ((inhibit-switch-frame . t) 235 | (allow-no-window . t)))) 236 | (display-buffer buf '(display-buffer-use-some-window 237 | ((inhibit-switch-frame . t))))))))) 238 | (cl-case mode 239 | (never void-fn) 240 | (only-if-error void-fn) 241 | (switch #'switch-to-buffer) 242 | (display display-fn) 243 | (next-frame-switch (display-nf 'pop-to-buffer nil)) 244 | (next-frame-display (display-nf display-fn nil)) 245 | (next-frame-skip-switch (display-nf 'pop-to-buffer void-fn)) 246 | (next-frame-skip-display (display-nf display-fn void-fn)) 247 | (t (error "No mode: %S" mode)))))) 248 | 249 | (cl-defmethod flex-compiler-display-buffer ((this single-buffer-flex-compiler) 250 | &optional compile-def) 251 | "Display buffer based on values returned from `flex-compiler-display-modes'. 252 | 253 | COMPILE-DEF is an alias in the following form: 254 | ((newp . 255 | (buffer . ))) 256 | 257 | THIS is the object instance." 258 | (if (null compile-def) 259 | (setq compile-def 260 | (flex-compiler-single-buffer--flex-comp-def this 'compile nil))) 261 | (if (not (consp compile-def)) 262 | (error "Unknown compile-def: %S" compile-def)) 263 | (let* ((modes (if (assq 'force-show compile-def) 264 | '((new . switch) 265 | (exists . switch)) 266 | (flex-compiler-display-modes this))) 267 | (fn (flex-compile-single-buffer-display-function 268 | (if (cdr (assq 'newp compile-def)) 269 | (cdr (assq 'new modes)) 270 | (cdr (assq 'exists modes))))) 271 | (buf (cdr (assq 'buffer compile-def)))) 272 | (unless (eq buf 'killed-buffer) 273 | (if (and buf (not (bufferp buf))) 274 | (error "Unknown buffer object: %S" buf)) 275 | (if (and fn (buffer-live-p buf)) 276 | (funcall fn buf))))) 277 | 278 | (cl-defmethod flex-compiler-single-buffer--flex-comp-def 279 | ((this single-buffer-flex-compiler) start-type startp) 280 | "Return a default compilation definition for THIS compiler. 281 | 282 | START-TYPE is either symbols `compile', `run', `clean' depending if invoked by 283 | `flex-compiler-compile' or `flex-compiler-run'. 284 | 285 | If STARTP is non-nil, start the buffer using `flex-compiler-start-buffer'. 286 | 287 | Return an alist in the following form: 288 | ((newp . 289 | (buffer . )))" 290 | (let* ((has-buffer-p (flex-compiler-buffer this)) 291 | (buf (flex-compiler-buffer this))) 292 | (when (or startp (null buf)) 293 | (if (child-of-class-p (eieio-object-class this) 'conf-flex-compiler) 294 | (config-prop-entry-set-required this)) 295 | (setq buf (flex-compiler-start-buffer this start-type))) 296 | (oset this :last-displayed-context 297 | `((newp . ,(not has-buffer-p)) 298 | (buffer . ,buf))))) 299 | 300 | (cl-defmethod flex-compiler-compile ((this single-buffer-flex-compiler)) 301 | "Invoke the compile functionality of THIS compiler." 302 | (save-some-buffers) 303 | (flex-compiler-single-buffer--flex-comp-def this 'compile t)) 304 | 305 | (cl-defmethod flex-compiler-run ((this single-buffer-flex-compiler)) 306 | "Invoke the run functionality of THIS compiler." 307 | (save-some-buffers) 308 | (flex-compiler-single-buffer--flex-comp-def this 'run t)) 309 | 310 | (cl-defmethod flex-compiler-clean ((this single-buffer-flex-compiler) 311 | &optional allp) 312 | "Invoke the clean functionality of THIS compiler. 313 | if ALLP is non-nil, then invoke a more destructive cleaning when supported." 314 | (let* ((start-type (if allp 'clean-all 'clean)) 315 | (compile-def 316 | (flex-compiler-single-buffer--flex-comp-def this start-type t))) 317 | (with-slots (kill-buffer-clean) this 318 | (when kill-buffer-clean 319 | (let ((killfn `(lambda 320 | () 321 | (let ((buf (flex-compiler-buffer ,this))) 322 | (if (not (buffer-live-p buf)) 323 | (message "No buffer to clean") 324 | (message "Cleaning up buffer %s" buf) 325 | (let ((kill-buffer-query-functions nil)) 326 | (kill-buffer buf))))))) 327 | (if (numberp kill-buffer-clean) 328 | (run-at-time kill-buffer-clean nil killfn) 329 | (funcall killfn))))) 330 | compile-def)) 331 | 332 | 333 | ;; functions 334 | 335 | ;;;###autoload 336 | (defun flex-compile-single-buffer-set-buffer-exists-mode () 337 | "Query and set the value for the display mode for existing buffers. 338 | This sets but doesn't configure 339 | `flex-compile-single-buffer-display-buffer-exists-mode'." 340 | (interactive) 341 | (let ((choices (->> (cdr flex-compile-single-buffer-display-mode-options) 342 | (-map 'last) 343 | (-map 'first))) 344 | (def (or (cl-second flex-compile-single-buffer-set-buffer-exists) 345 | (car flex-compile-single-buffer-set-buffer-exists)))) 346 | (setq flex-compile-single-buffer-display-buffer-exists-mode 347 | (choice-program-complete "Buffer Exists Mode" 348 | choices 349 | nil t ; return string, require match 350 | nil ; initial 351 | 'flex-compile-single-buffer-set-buffer-exists ; history 352 | def ; def 353 | nil ; allow-empty 354 | t t)))) 355 | 356 | (provide 'flex-compile-single-buffer) 357 | 358 | ;;; flex-compile-single-buffer.el ends here 359 | -------------------------------------------------------------------------------- /flex-compile-xml-validate.el: -------------------------------------------------------------------------------- 1 | ;;; flex-compile-xml-validate.el --- XML validation -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2015 - 2025 Paul Landes 4 | 5 | ;; Author: Paul Landes 6 | ;; Maintainer: Paul Landes 7 | ;; Keywords: xml validation compilation processes 8 | ;; URL: https://github.com/plandes/flex-compile 9 | ;; Package-Requires: ((emacs "26.1")) 10 | ;; Package-Version: 0 11 | 12 | ;; This file is not part of GNU Emacs. 13 | 14 | ;; This program is free software; you can redistribute it and/or modify 15 | ;; it under the terms of the GNU General Public License as published by 16 | ;; the Free Software Foundation; either version 2, or (at your option) 17 | ;; any later version. 18 | 19 | ;; This program is distributed in the hope that it will be useful, 20 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | ;; GNU General Public License for more details. 23 | 24 | ;; You should have received a copy of the GNU General Public License 25 | ;; along with this program; if not, write to the Free Software 26 | ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, 27 | ;; Boston, MA 02110-1301, USA. 28 | 29 | ;;; Commentary: 30 | 31 | ;; Implementation compiler for XML validation using command line `xmllint'. 32 | 33 | ;;; Code: 34 | 35 | (require 'flex-compile-manage) 36 | 37 | (eval-when-compile (require 'xml)) 38 | 39 | (defclass config-schema-file-prop (config-file-prop) 40 | () 41 | :method-invocation-order :c3 42 | :documentation "A schema file property") 43 | 44 | (cl-defmethod initialize-instance ((this config-schema-file-prop) 45 | &optional slots) 46 | "Initialize THIS instance using SLOTS as initial values." 47 | (setq slots (plist-put slots :prompt "Schema file") 48 | slots (plist-put slots :validate-modes '(nxml-mode)) 49 | slots (plist-put slots :input-type 'last)) 50 | (cl-call-next-method this slots)) 51 | 52 | (cl-defmethod flex-compiler-guess-schema-file ((this config-schema-file-prop)) 53 | "Try to determine where the XSD is by the location set in THIS property." 54 | (with-temp-buffer 55 | (-> (slot-value this 'prop-entry) 56 | (slot-value 'config-file) 57 | insert-file-contents) 58 | (condition-case nil 59 | (->> (xml-parse-region (point-min) (point-max)) 60 | car 61 | xml-node-attributes 62 | (assq 'xsi:schemaLocation) 63 | cdr 64 | (funcall #'(lambda (xsi) 65 | (if (string-match "file://\\(.*\\)$" xsi) 66 | (match-string 1 xsi))))) 67 | (error)))) 68 | 69 | (cl-defmethod config-prop-read ((this config-schema-file-prop)) 70 | "Read the schema XSD file location from the user and set in THIS property." 71 | (let* ((schema-guess (flex-compiler-guess-schema-file this)) 72 | (initial (and schema-guess (file-name-nondirectory schema-guess))) 73 | (dir (and schema-guess (file-name-directory schema-guess)))) 74 | (read-file-name "Schema XSD: " dir schema-guess t initial))) 75 | 76 | 77 | 78 | (defclass xml-validate-flex-compiler (single-buffer-flex-compiler 79 | conf-file-flex-compiler) 80 | ((xmllint-program :initarg :xmllint-program 81 | :initform "xmllint") 82 | (schema-file :initarg :schema-file 83 | :initform nil 84 | :documentation "\ 85 | Location of the schema file to validate against.")) 86 | :method-invocation-order :c3 87 | :documentation "\ 88 | Implementation compiler for XML validation using command line 89 | \[xmllint](http://xmlsoft.org/xmllint.html) command line tool.") 90 | 91 | (cl-defmethod initialize-instance ((this xml-validate-flex-compiler) 92 | &optional slots) 93 | "Initialize THIS instance using SLOTS as initial values." 94 | (let ((props (list (config-schema-file-prop :object-name 'schema-file 95 | :prop-entry this 96 | :required t 97 | :order 1)))) 98 | (setq slots (plist-put slots :object-name "xml-validate") 99 | slots (plist-put slots :description "XML") 100 | slots (plist-put slots :validate-modes '(nxml-mode)) 101 | slots (plist-put slots :buffer-name "XML Validation") 102 | slots (plist-put slots :props (append (plist-get slots :props) props)))) 103 | (cl-call-next-method this slots)) 104 | 105 | (cl-defmethod flex-compiler-load-libraries ((this xml-validate-flex-compiler)) 106 | "Load the `xml' library for THIS compiler." 107 | (ignore this) 108 | (require 'xml)) 109 | 110 | (cl-defmethod config-prop-set ((this xml-validate-flex-compiler) prop val) 111 | "Set property PROP to VAL for THIS compiler. 112 | 113 | Also reset the `schema-file' slot since a setting any other value invalidates 114 | it." 115 | (setf (slot-value this 'schema-file) nil) 116 | (cl-call-next-method this prop val)) 117 | 118 | (cl-defmethod flex-compiler-start-buffer ((this xml-validate-flex-compiler) 119 | start-type) 120 | "Return a new buffer for THIS compiler with a processing compilation. 121 | This implementation runs the XML validation program. 122 | START-TYPE is ignored." 123 | (ignore start-type) 124 | (with-slots (xmllint-program schema-file config-file) this 125 | (let* ((cmd (mapconcat #'identity 126 | `(,xmllint-program "--noout" "--schema" 127 | ,schema-file ,config-file) 128 | " ")) 129 | (buffer-name (flex-compiler-buffer-name this))) 130 | (compilation-start cmd nil #'(lambda (_) buffer-name))))) 131 | 132 | (flex-compile-manager-register flex-compile-manage-inst 133 | (xml-validate-flex-compiler)) 134 | 135 | (provide 'flex-compile-xml-validate) 136 | 137 | ;;; flex-compile-xml-validate.el ends here 138 | -------------------------------------------------------------------------------- /flex-compile.el: -------------------------------------------------------------------------------- 1 | ;;; flex-compile.el --- Run, evaluate and compile across many languages -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2015 - 2025 Paul Landes 4 | 5 | ;; Version: 1.6.0 6 | ;; Author: Paul Landes 7 | ;; Maintainer: Paul Landes 8 | ;; Keywords: compilation integration processes 9 | ;; URL: https://github.com/plandes/flex-compile 10 | ;; Package-Requires: ((emacs "26.1") (dash "2.17.0") (buffer-manage "1.1")) 11 | 12 | ;; This file is not part of GNU Emacs. 13 | 14 | ;; This program is free software; you can redistribute it and/or modify 15 | ;; it under the terms of the GNU General Public License as published by 16 | ;; the Free Software Foundation; either version 2, or (at your option) 17 | ;; any later version. 18 | 19 | ;; This program is distributed in the hope that it will be useful, 20 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | ;; GNU General Public License for more details. 23 | 24 | ;; You should have received a copy of the GNU General Public License 25 | ;; along with this program; if not, write to the Free Software 26 | ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, 27 | ;; Boston, MA 02110-1301, USA. 28 | 29 | ;;; Commentary: 30 | 31 | ;; Run, evaluate and compile functionality for a variety of different languages 32 | ;; and modes. The specific "compilation" method is different across each 33 | ;; add-on library. For example, for ESS and Clojure you can evaluate a 34 | ;; specific file and/or evaluate a specfic expression via a REPL. For running 35 | ;; a script or starting a `make' an async process is started. 36 | ;; 37 | ;; For more information see https://github.com/plandes/flex-compile 38 | 39 | ;;; Code: 40 | 41 | (require 'flex-compile-manage) 42 | (require 'flex-compile-script) 43 | (require 'flex-compile-make) 44 | (require 'flex-compile-command) 45 | (require 'flex-compile-choice-program) 46 | (require 'flex-compile-org-export) 47 | (require 'flex-compile-xml-validate) 48 | (require 'flex-compile-python) 49 | (require 'flex-compile-clojure) 50 | (require 'flex-compile-lisp) 51 | (require 'flex-compile-ess) 52 | (require 'flex-compile-comint) 53 | (require 'flex-compile-cli) 54 | (require 'flex-compiler) 55 | 56 | (defun flex-compile-key-bindings () 57 | "Bind keys globally to flex compiler actions. 58 | 59 | Note: this clobbers the following bindings: 60 | `mark-page' 61 | `delete-blank-lines'. 62 | 63 | This binds the following: 64 | - C-x C-p: `flex-compiler-do-activate' 65 | - C-x C-u: `flex-compiler-do-compile' 66 | - C-x C-y: `flex-compiler-do-clean' 67 | - C-x C-i: `flex-compiler-do-run-or-set-config' 68 | - C-x C-o: `flex-compiler-do-eval'" 69 | ;; switch compiler; clobbers `mark-page' 70 | (global-set-key "\C-x\C-p" 'flex-compiler-do-activate) 71 | (global-set-key "\C-x\C-u" 'flex-compiler-do-compile) 72 | (global-set-key "\C-x\C-y" 'flex-compiler-do-clean) 73 | (global-set-key "\C-x\C-i" 'flex-compiler-do-run-or-set-config) 74 | ;; clobbers `delete-blank-lines' 75 | (global-set-key "\C-x\C-o" 'flex-compiler-do-eval)) 76 | 77 | (defun flex-compile-init () 78 | "Initialize the flex-compile system." 79 | (flex-compiler-config-load) 80 | (flex-compiler-do-activate "disable")) 81 | 82 | (flex-compile-init) 83 | 84 | (provide 'flex-compile) 85 | 86 | ;;; flex-compile.el ends here 87 | -------------------------------------------------------------------------------- /flex-compiler.el: -------------------------------------------------------------------------------- 1 | ;;; flex-compiler.el --- User interactive interface to compilers -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2015 - 2025 Paul Landes 4 | 5 | ;; Version: 0.7 6 | ;; Author: Paul Landes 7 | ;; Maintainer: Paul Landes 8 | ;; Keywords: compilation integration processes 9 | ;; URL: https://github.com/plandes/flex-compile 10 | ;; Package-Requires: ((emacs "26") (dash "2.13.0") (buffer-manage "0.8")) 11 | 12 | ;; This file is not part of GNU Emacs. 13 | 14 | ;; This program is free software; you can redistribute it and/or modify 15 | ;; it under the terms of the GNU General Public License as published by 16 | ;; the Free Software Foundation; either version 2, or (at your option) 17 | ;; any later version. 18 | 19 | ;; This program is distributed in the hope that it will be useful, 20 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | ;; GNU General Public License for more details. 23 | 24 | ;; You should have received a copy of the GNU General Public License 25 | ;; along with this program; if not, write to the Free Software 26 | ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, 27 | ;; Boston, MA 02110-1301, USA. 28 | 29 | ;;; Commentary: 30 | 31 | ;; Run, evaluate and compile functionality for a variety of different languages 32 | ;; and modes. The specific "compilation" method is different across each 33 | ;; add-on library. For example, for ESS and Clojure you can evaluate a 34 | ;; specific file and/or evaluate a specfic expression via a REPL. For running 35 | ;; a script or starting a `make' an async process is started. 36 | ;; 37 | ;; For more information see https://github.com/plandes/flex-compile 38 | 39 | ;;; Code: 40 | 41 | (require 'flex-compile-manage) 42 | 43 | (defvar flex-compiler-read-history nil 44 | "History variable for `flex-compiler-read'.") 45 | 46 | (defun flex-compiler-by-name (name) 47 | "Convenience function to get a compiler by it's NAME." 48 | (config-manager-entry flex-compile-manage-inst name)) 49 | 50 | (defun flex-compiler-active () 51 | "Return the currently activated compiler." 52 | (flex-compile-manager-active flex-compile-manage-inst)) 53 | 54 | (defun flex-compiler-read (last-compiler-p) 55 | "Read a flexible compiler to use. 56 | 57 | LAST-COMPILER-P, if non-nil, use the last chosen compiler." 58 | (or (if last-compiler-p 59 | (let ((arg (cl-second flex-compiler-read-history))) 60 | (setq flex-compiler-read-history 61 | (append (cons arg nil) 62 | flex-compiler-read-history)) 63 | arg)) 64 | (let* ((this flex-compile-manage-inst) 65 | (names (config-manager-entry-names this))) 66 | (choice-program-complete 67 | "Compiler" names t t nil 68 | 'flex-compiler-read-history 69 | (cl-second flex-compiler-read-history) 70 | nil t t)))) 71 | 72 | (defun flex-compiler-manage-read-form (no-input-p) 73 | "Read the compilation query form from the user. 74 | 75 | If NO-INPUT-P is t, use the default witout getting it from the user. 76 | 77 | This invokes the `flex-compiler-query-read-form method' on the 78 | currently activated compiler." 79 | (let* ((mgr flex-compile-manage-inst) 80 | (this (flex-compile-manager-active mgr))) 81 | (if (not (child-of-class-p (eieio-object-class this) 82 | 'repl-flex-compiler)) 83 | (error "Compiler `%s' has no ability evaluation expressions" 84 | (config-entry-name this)) 85 | (flex-compile-manager-assert-ready mgr) 86 | (flex-compiler-query-read-form this no-input-p)))) 87 | 88 | ;;;###autoload 89 | (defun flex-compiler-config-save () 90 | "Save all compiler and manager configuration." 91 | (interactive) 92 | (config-persistable-save flex-compile-manage-inst)) 93 | 94 | ;;;###autoload 95 | (defun flex-compiler-config-load () 96 | "Load all compiler and manager configuration." 97 | (interactive) 98 | (condition-case err 99 | (config-persistable-load flex-compile-manage-inst) 100 | (invalid-slot-name 101 | (error "Could not unpersist: %S, repair or delete %s" 102 | err (slot-value flex-compile-manage-inst 'file))))) 103 | 104 | ;;;###autoload 105 | (defun flex-compiler-list () 106 | "Display the flex compiler list." 107 | (interactive) 108 | (let ((this flex-compile-manage-inst)) 109 | (->> (format "*%s*" (capitalize (config-manager-name this))) 110 | (config-manager-list-entries-buffer this)))) 111 | 112 | ;;;###autoload 113 | (defun flex-compiler-reset-configuration () 114 | "Reset every compiler's configuration." 115 | (interactive) 116 | (when (y-or-n-p "This will wipe all compiler configuration. Are you sure? ") 117 | (if (file-exists-p flex-compile-manage-persistency-file-name) 118 | (delete-file flex-compile-manage-persistency-file-name)) 119 | (flex-compile-clear flex-compile-manage-inst) 120 | (flex-compiler-config-save) 121 | (message "Configuration reset"))) 122 | 123 | ;;;###autoload 124 | (defun flex-compiler-doc-show () 125 | "Create markdown documentation on all compilers and their meta data." 126 | (interactive) 127 | (config-persistent-doc flex-compile-manage-inst)) 128 | 129 | ;;;###autoload 130 | (defun flex-compiler-show-configuration () 131 | "Create a buffer with the configuration of the current compiler." 132 | (interactive) 133 | (let* ((this flex-compile-manage-inst) 134 | (active (flex-compile-manager-active this))) 135 | (config-prop-entry-show-configuration active))) 136 | 137 | ;;;###autoload 138 | (defun flex-compiler-do-activate (compiler-name) 139 | "Activate/select a compiler. 140 | 141 | COMPILER-NAME the name of the compiler to activate." 142 | (interactive (list (flex-compiler-read current-prefix-arg))) 143 | (let ((this flex-compile-manage-inst)) 144 | (config-manager-activate this compiler-name))) 145 | 146 | ;;;###autoload 147 | (defun flex-compiler-do-compile (config-options) 148 | "Invoke compilation polymorphically. 149 | 150 | CONFIG-OPTIONS, if non-nil invoke the configuration options for the compiler 151 | before invoking the compilation. By default CONFIG-OPTIONS is only detected 152 | config-options by the \\[universal-argument] but some compilers use the numeric 153 | argument as well. This creates the need for an awkward key combination of: 154 | 155 | \\[digital-argument] \\[universal-argument] \\[flex-compiler-compile] 156 | 157 | to invoke this command with full configuration support." 158 | (interactive 159 | (list (cond ((null current-prefix-arg) 'compile) 160 | ;; universal arg 161 | ((equal '(4) current-prefix-arg) nil) 162 | (t (1- current-prefix-arg))))) 163 | (let* ((this flex-compile-manage-inst) 164 | (active (flex-compile-manager-active this))) 165 | (flex-compile-manager-assert-ready this) 166 | (if (eq config-options 'compile) 167 | (let (comp-def) 168 | (let ((display-buffer-alist 169 | (flex-compiler-display-buffer-alist active))) 170 | (setq comp-def (flex-compiler-compile active))) 171 | (flex-compiler-display-buffer active comp-def)) 172 | (condition-case err 173 | (config-prop-entry-configure active config-options) 174 | (cl-no-applicable-method 175 | (message "Compiler %s is not configurable: no method: %S" 176 | (config-entry-name active) 177 | (cl-second err))))))) 178 | 179 | ;;;###autoload 180 | (defun flex-compiler-do-run-or-set-config (action) 181 | "This either invokes the `run' compilation functionality or it configures it. 182 | 183 | ACTION is the interactive argument given by the read function." 184 | (interactive 185 | (list (cond ((null current-prefix-arg) 'run) 186 | ((eq 2 current-prefix-arg) 'force-show) 187 | ;; universal arg 188 | ((equal '(4) current-prefix-arg) 'find) 189 | ((eq 1 current-prefix-arg) 'set-config) 190 | (t (error "Unknown prefix state: %s" current-prefix-arg))))) 191 | (let* ((this flex-compile-manage-inst) 192 | (active (flex-compile-manager-active this))) 193 | (flex-compile-manager-assert-ready this) 194 | (condition-case err 195 | (cl-case action 196 | (run (let (compile-def) 197 | (let ((display-buffer-alist 198 | (flex-compiler-display-buffer-alist active))) 199 | (setq compile-def (flex-compiler-run active))) 200 | (flex-compiler-display-buffer active compile-def))) 201 | (force-show (let (compile-def) 202 | (let ((display-buffer-alist 203 | (flex-compiler-display-buffer-alist active))) 204 | (setq compile-def (append (flex-compiler-run active) 205 | '((force-show . t))))) 206 | (flex-compiler-display-buffer active compile-def))) 207 | (find (if (child-of-class-p (eieio-object-class active) 208 | 'conf-file-flex-compiler) 209 | (flex-compiler-conf-file-display active))) 210 | (set-config (config-prop-entry-configure active 'immediate)) 211 | (t (error "Unknown action: %S" action))) 212 | (cl-no-applicable-method 213 | (message "Unsupported action `%S' on compiler %s: no method %S" 214 | action 215 | (config-entry-name active) 216 | (cl-second err)))))) 217 | 218 | ;;;###autoload 219 | (defun flex-compiler-do-eval (&optional form) 220 | "Evaluate the current form for the \(usually REPL based compiler). 221 | FORM is the form to evaluate \(if implemented). If called with 222 | \\[universal-argument] then prompt the user with the from to evaluation." 223 | (interactive (list (flex-compiler-manage-read-form (not current-prefix-arg)))) 224 | (let* ((mgr flex-compile-manage-inst) 225 | (this (flex-compile-manager-active mgr))) 226 | (flex-compile-manager-assert-ready mgr) 227 | (let ((res (flex-compiler-evaluate-form this form))) 228 | (when (and res (called-interactively-p 'interactive)) 229 | (kill-new res) 230 | (message "%s" res)) 231 | res))) 232 | 233 | ;;;###autoload 234 | (defun flex-compiler-do-clean (allp) 235 | "Invoke the clean functionality of the compiler. 236 | if ALLP is non-nil, then invoke a more destructive cleaning when supported." 237 | (interactive "P") 238 | (let* ((this flex-compile-manage-inst) 239 | (active (flex-compile-manager-active this))) 240 | (condition-case nil 241 | (progn 242 | (flex-compile-manager-assert-ready this) 243 | (let (compile-def) 244 | (let ((display-buffer-alist 245 | (flex-compiler-display-buffer-alist active))) 246 | (setq compile-def (flex-compiler-clean active allp))) 247 | (flex-compiler-display-buffer active compile-def))) 248 | (cl-no-applicable-method 249 | (message "Compiler `%s' has no ability to clean" 250 | (config-entry-name active)))))) 251 | 252 | (provide 'flex-compiler) 253 | 254 | ;;; flex-compiler.el ends here 255 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | ## makefile automates the build and deployment for Emacs Lisp projects 2 | 3 | # type of project 4 | PROJ_TYPE= elisp 5 | 6 | include ./zenbuild/main.mk 7 | -------------------------------------------------------------------------------- /test/flex-compile-test.el: -------------------------------------------------------------------------------- 1 | ;;; package --- Summary 2 | ;;; Commentary: 3 | ;; 4 | ;; Unit tests of flex-compile.el 5 | ;; 6 | ;;; Code: 7 | 8 | (require 'ert) 9 | (require 'dash) 10 | (require 'flex-compile) 11 | (require 'flex-compile-make) 12 | 13 | (ert-deftest test-manager-instance () 14 | "Test successful evaluation of flex-compile." 15 | (let ((this flex-compile-manage-inst)) 16 | (should (eq t (eieio-object-p this))) 17 | (should (eq 'flex-compile-manager (eieio-object-class this))))) 18 | 19 | (ert-deftest test-compiler-registration () 20 | "Test registration of compilers." 21 | (should (equal 22 | '("choice-program" "cli" "clojure" "comint" "command" "disable" 23 | "ess" "lisp" "make" "org-export" "python" "script" 24 | "xml-validate") 25 | (->> (slot-value flex-compile-manage-inst 'entries) 26 | (-map 'config-entry-name) 27 | (funcall #'(lambda (elt) (sort elt 'string<))))))) 28 | 29 | (provide 'flex-compile-test) 30 | 31 | ;;; flex-compile-test.el ends here 32 | --------------------------------------------------------------------------------