├── .ert-runner
├── .gitignore
├── .travis.yml
├── Cask
├── LICENSE
├── Makefile
├── README.rst
├── create_test_git_repo.sh
├── emaci.el
├── emaci.zsh
├── evm_install.sh
├── rms.data
└── test
└── emaci-test.el
/.ert-runner:
--------------------------------------------------------------------------------
1 | -L .
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .cask
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: generic
2 | sudo: false
3 | before_install:
4 | - source evm_install.sh
5 | - evm install $EVM_EMACS --use --skip
6 | - cask
7 | env:
8 | - EVM_EMACS=emacs-24.4-travis
9 | - EVM_EMACS=emacs-24.5-travis
10 | script:
11 | - git config --global user.name "David Zuber"
12 | - git config --global user.email zuber.david@gmx.de
13 | - emacs --version
14 | - make test
15 |
--------------------------------------------------------------------------------
/Cask:
--------------------------------------------------------------------------------
1 | (source gnu)
2 | (source melpa)
3 |
4 | (package-file "emaci.el")
5 |
6 | (development
7 | (depends-on "ert")
8 | (depends-on "ert-runner")
9 | (depends-on "undercover"))
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 | {one line to give the program's name and a brief idea of what it does.}
635 | Copyright (C) {year} {name of author}
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | {project} Copyright (C) {year} {fullname}
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY : test
2 |
3 | EMACS ?= emacs
4 | CASK ?= cask
5 |
6 | LOADPATH = -L .
7 |
8 | ELPA_DIR = \
9 | .cask/$(shell $(EMACS) -Q --batch --eval '(princ emacs-version)')/elpa
10 |
11 | test: export EMACI_TESTGITDIR=$(shell mktemp -d)
12 | test: export EMACI_SAVEDIR=$(shell mktemp -d)
13 | test: export EMACI_TESTGITDIR2=$(shell mktemp -d)
14 | test: elpa
15 | echo created git temp dir $$EMACI_TESTGITDIR; \
16 | ./create_test_git_repo.sh
17 | $(CASK) exec ert-runner --reporter ert; \
18 | rm -rf $$EMACI_TESTGITDIR; \
19 | rm -rf $$EMACI_TESTGITDIR2; \
20 | rm -rf $$EMACI_SAVEDIR
21 |
22 | elpa: $(ELPA_DIR)
23 | $(ELPA_DIR): Cask
24 | $(CASK) install
25 | touch $@
26 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | =====
2 | emaci
3 | =====
4 |
5 | .. image:: https://travis-ci.org/storax/emaci.svg?branch=master
6 | :target: https://travis-ci.org/storax/emaci
7 |
8 | .. image:: https://coveralls.io/repos/github/storax/emaci/badge.svg?branch=master
9 | :target: https://coveralls.io/github/storax/emaci?branch=master
10 |
11 | A queue for shell commands in emacs
12 |
13 | **Note**:
14 |
15 | This is in a very early alpha. Also this project is just for fun.
16 |
17 | --------
18 | Features
19 | --------
20 |
21 | * Queue arbitrary shell commands.
22 | * Execute the queue automatically.
23 | * Send commands from the command line
24 | * Cancel/Kill Jobs
25 | * Multiple queues
26 | * Permanent build history and logs
27 | * Select git branch for each build
28 | * Select stashes for each build
29 | * Build Management buffer
30 | * Hooks can add metadata to jobs
31 | * Richard Stallman Quotes Plugin
32 |
33 | -------
34 | Roadmap
35 | -------
36 |
37 | * Complex dependency graphs
38 | * Translate zsh function to bash
39 | * Metrics
40 | * Commit hook
41 |
42 | ----------
43 | Quickstart
44 | ----------
45 |
46 | After you installed emaci, load it via::
47 |
48 | (require 'emaci)
49 | (emaci/load-vars)
50 |
51 | Use ``emaci/submit-job`` or ``emaci/submit-job-comint`` to queue shell commands.
52 | The queue will get executed immediately. While the queue is running,
53 | you can submit more jobs. Once a job had finished, it will execute the next in the queue.
54 | You can specify a branch that will get checked out before the command is run.
55 | The original branch will be checked out after the command has finished.
56 | The same goes for stashes. You can apply as many stashes as you like,
57 | which will get reverted afterwards.
58 |
59 | Jobs are queued in ``emaci-queue`` and archived in ``emaci-history``.
60 | You can cancel or kill the currently running job
61 | with ``emaci/cancel-job`` or ``emaci/kill-job``. If there are more jobs in the queue,
62 | you have to restart the queue by calling ``emaci/execute-next``.
63 |
64 | The output, queue and history is saved to the ``emaci-save-dir`` directory.
65 | To load them execute ``(emaci/load-vars)`` after setting ``emaci-save-dir``.
66 | ``emaci-save-dir`` defaults to ``"~/.emaci/"``.
67 |
68 | To view the queues call ``emaci/mgmt-buffer``. This will show the emaci management buffer.
69 | You can expand and hide sections under your cursor by pressing ``TAB``.
70 | Pressing ``RET`` with the cursor on a job, will show you the output.
71 | Use ``c`` or ``k`` for canceling/killing a job.
72 |
73 | By default only the last 50 jobs are shown.
74 | You can customize this by setting ``emaci-max-history-len-status``.
75 |
76 | Like any other good CI-Server ``emaci`` has a quotes plugin.
77 | Enable Richard Stallman quotes by setting ``emaci-enable-rms`` to ``t``.
78 |
79 | ----
80 | Why?
81 | ----
82 |
83 | Having a queue for shell commands has a lot of use cases.
84 |
85 | I often find myself waiting for multiple code reviews to finish. Often they get completed in bulk and
86 | I have to merge multiple feature branches. Before each merge, I want to merge the latest dev branch into my feature branch and run the tests one last time,
87 | to see if it really does work. Because I have to wait for each test before I can test the next branch, scheduling comes in handy.
88 | Also sometimes I have multiple configs/environments for one project but for various reasons (not in my power to change) can't test them in parallel.
89 |
90 | There are already some solutions for emacs like `shell-command-queue.el `_
91 | or `command-queue.el `_.
92 | But they lack some functionality to use emacs as a simple build management system, when you can't (or don't want to) use
93 | a proper build/test server.
94 |
95 | ------
96 | Server
97 | ------
98 |
99 | Emacs has a built-in server. To start the server call::
100 |
101 | (server-start)
102 |
103 | Now you can start emacsclients in any shell, and the clients will
104 | use the running server (e.g. to load faster).
105 | This allows for emaci to be used like a CI-server.
106 |
107 | To use a convenient (zsh) shell function to send commands to emacs
108 | source ``emaci.zsh`` in the emaci directory or copy and paste it in your ``.zshrc`` or
109 | somewhere on your ``fpath``.
110 |
111 | Then you can use::
112 |
113 | $ emaci echo Hello World
114 |
115 | ~~~~~~~~~
116 | Breakdown
117 | ~~~~~~~~~
118 |
119 | To submit a job via the command line use::
120 |
121 | $ emacsclient --eval "(emaci/submit-job-comint nil \"$PWD\" \"echo Hello World\")"
122 |
123 | Note:
124 |
125 | You can also use ``emaci/submit-job`` and specify the mode of your compilation buffer yourself.
126 |
127 | It is important that you use double-quotes so the current shell working directory is
128 | replaced in the string. You could wrap this command in a shell function to make
129 | it easier to submit commands.
130 |
131 | Unfortunately the current environment is not used.
132 | It's only possible by setting the environment in the shell command argument.
133 | You could however automate this via a shell function.
134 |
135 | zsh::
136 |
137 | $ emacienv () { echo -e $(declare -px | awk '{if (NR == 1) printf $0;else if ($0 !~ /^typeset -.*/ && last !~ /^typeset -ax.*/) printf "\\n"$0;else printf " && "$0;}{last=$0}')' && ' }
138 |
139 | This uses ``declare`` to 'serialize' all environment variables to ``typeset`` commands. These can be evaluated to restore the exact same environment. ``awk`` is used to concatenate the list of commands with ``&&`` so we get a long one-liner. The ``awk`` command uses some logic to preserve newlines in environment variables.
140 |
141 | Now we can use this environment replication mechanism to give us a nice command.
142 |
143 | zsh::
144 |
145 | $ emaci () { emacsclient --eval "$(echo "(emaci/submit-job-comint nil \"$PWD\" \"$(emacienv)cd $PWD && $@\")")" }
146 |
147 | Note:
148 |
149 | For some reason there is some weird behaviour with the working directory
150 | if we don't add the ``cd $PWD`` command.
151 |
152 | Now we can use this to send shell commands to emacs::
153 |
154 | $ emaci echo Hello World
155 | $ emaci "./configure && make && make install"
156 | $ emaci 'echo $PWD'
157 |
158 | I find it somehow amusing.
159 |
--------------------------------------------------------------------------------
/create_test_git_repo.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | cd $EMACI_TESTGITDIR2
3 | git init
4 | cd $EMACI_TESTGITDIR
5 | git init
6 | echo "file1" > file1.txt
7 | echo "file2" > file2.txt
8 | git add .
9 | git commit -m "commit 1"
10 | git checkout -b branch1
11 | echo "coolio" > file2.txt
12 | echo "ciao" > file3.txt
13 | git add file2.txt
14 | git add file3.txt
15 | git commit -m "commit 2"
16 | rm file3.txt
17 | git stash save stash1
18 | git checkout master
19 |
--------------------------------------------------------------------------------
/emaci.el:
--------------------------------------------------------------------------------
1 | ;;; emaci.el --- scheduler for compilations in emacs
2 |
3 | ;; Copyright (C) 2016 by David ZUBER
4 |
5 | ;; Author: David ZUBER
6 | ;; URL: https://github.com/storax/emaci
7 | ;; Version: 0.1.0
8 | ;; Package-Requires: ((emacs "24.3"))
9 |
10 | ;; This program is free software; you can redistribute it and/or modify
11 | ;; it under the terms of the GNU General Public License as published by
12 | ;; the Free Software Foundation, either version 3 of the License, or
13 | ;; (at your option) any later version.
14 |
15 | ;; This program is distributed in the hope that it will be useful,
16 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | ;; GNU General Public License for more details.
19 |
20 | ;; You should have received a copy of the GNU General Public License
21 | ;; along with this program. If not, see .
22 |
23 | ;;; Commentary:
24 |
25 | ;; emaci provides a scheduler to queue compilation jobs and execute them automatically.
26 |
27 | ;;; Code:
28 |
29 | (require 'vc-git)
30 | (require 'compile)
31 | (eval-when-compile (require 'cl-lib))
32 | (eval-when-compile (require 'cl))
33 |
34 | (defgroup emaci nil
35 | "Customization group for emaci."
36 | :prefix "emaci-"
37 | :group 'emacs)
38 |
39 | (cl-defstruct emaci-job
40 | buildno queue status statusmsg exitcode datestarted datefinished
41 | oldref ref stashes buffer dir command mode highlight-regexp metadata)
42 |
43 | (cl-defstruct emaci-section arglist)
44 |
45 | (defvar emaci-queue nil
46 | "An alist of queue names as car and a list of `emaci-job' structs as cdr.
47 | Jobs in the queue might already be running.")
48 | (defvar emaci-history nil
49 | "An alist of queue names as car and a list of `emaci-job' structs as cdr.
50 | Jobs in the history are finished or cancled.")
51 | (defvar emaci--buffer-job-alist nil
52 | "A mapping of buffers to jobs.")
53 | (defvar emaci--build-counter nil
54 | "The global job counter.
55 | It is an alist where the queue names as car and the counter number as cdr.")
56 | (defvar emaci-mode-history nil
57 | "History for selecting modes.")
58 | (defvar emaci-finished-hook nil
59 | "Hooks are run after the job has finished.
60 | They should take an `emaci-job' as argument.")
61 | (defvar emaci-started-hook nil
62 | "Hooks are run after the job has started.
63 | They should take an `emaci-job' as argument.")
64 | (defvar emaci-mode-hook nil
65 | "Hooks for the emaci management buffer mode.")
66 |
67 | (defvar emaci-rms-data
68 | (split-string
69 | (with-temp-buffer
70 | (insert-file-contents
71 | (concat (file-name-as-directory (file-name-directory load-file-name)) "rms.data"))
72 | (buffer-string))
73 | "\n" t)
74 | "A list of Richard Stallman quotes.")
75 |
76 | (defvar emaci-mode-map
77 | (let ((map (make-keymap)))
78 | (define-key map (kbd "TAB") 'emaci/toggle-section)
79 | (define-key map (kbd "RET") 'emaci/mgmt-ret)
80 | (define-key map (kbd "k") 'emaci/kill-job)
81 | (define-key map (kbd "c") 'emaci/cancel-job)
82 | map)
83 | "Keymap for emaci major mode.")
84 |
85 | (defvar-local emaci--sections nil
86 | "Internal list for emaci sections in the management browser.")
87 |
88 | (defcustom emaci-save-dir "~/.emaci/"
89 | "Directory where emaci saves history and logs."
90 | :type 'string
91 | :group 'emaci)
92 |
93 | (defcustom emaci-max-history-len-status 50
94 | "Show max number of jobs from history in status buffer."
95 | :type 'int
96 | :group 'emaci)
97 |
98 | (defcustom emaci-enable-rms nil
99 | "Enable Richard Stallman quotes."
100 | :type 'boolean
101 | :group 'emaci)
102 |
103 | (defface emaci-statbar-success-face
104 | '((t :background "#4de137"))
105 | "Face for a successful job in the status bar."
106 | :group 'emaci)
107 |
108 | (defface emaci-statbar-fail-face
109 | '((t :background "#ff4444"))
110 | "Face for a failed job in the status bar."
111 | :group 'emaci)
112 |
113 | (defface emaci-statbar-canceled-face
114 | '((t :background "grey"))
115 | "Face for a cancled job in the status bar."
116 | :group 'emaci)
117 |
118 | (defface emaci-statbar-running-face
119 | '((t :background "#fff574"))
120 | "Face for a running job in the status bar."
121 | :group 'emaci)
122 |
123 | (defface emaci-statbar-queued-face
124 | '((t :background "#51b8e1"))
125 | "Face for a cancled job in the status bar."
126 | :group 'emaci)
127 |
128 | (defface emaci-statbar-unknown-face
129 | '((t :background "blue"))
130 | "Face for a job of unknown status in the status bar."
131 | :group 'emaci)
132 |
133 | (defface emaci-mgmt-success-face
134 | '((t :foreground "#4de137"))
135 | "Face for a successful job in then mgmt buffer."
136 | :group 'emaci)
137 |
138 | (defface emaci-mgmt-fail-face
139 | '((t :foreground "#ff4444"))
140 | "Face for a failed job in the mgmt buffer."
141 | :group 'emaci)
142 |
143 | (defface emaci-mgmt-canceled-face
144 | '((t :foreground "grey"))
145 | "Face for a cancled job in the mgmt buffer."
146 | :group 'emaci)
147 |
148 | (defface emaci-mgmt-running-face
149 | '((t :foreground "#fff574"))
150 | "Face for a running job in the mgmt buffer."
151 | :group 'emaci)
152 |
153 | (defface emaci-mgmt-queued-face
154 | '((t :foreground "#51b8e1"))
155 | "Face for a cancled job in the mgmt buffer."
156 | :group 'emaci)
157 |
158 | (defface emaci-mgmt-unknown-face
159 | '((t :foreground "blue"))
160 | "Face for a job of unknown status in the mgmt buffer."
161 | :group 'emaci)
162 |
163 | (define-error 'emaci-error "Something went wrong with emaci, sry.")
164 | (define-error 'emaci-error-job-running "Job is already running." 'emaci-error)
165 |
166 | (defun emaci//get-buildno (&optional queue)
167 | "Get new build number and increase the counter `emaci--build-counter'.
168 | If QUEUE is non-nil, use the counter for that queue.
169 | If QUEUE is not in the counter, it is added to it, starting with 1."
170 | (let ((queue (or queue "*default*")))
171 | (unless (assoc queue emaci--build-counter)
172 | (add-to-list 'emaci--build-counter (cons queue 0)))
173 | (let ((count (cdr (assoc queue emaci--build-counter))))
174 | (setf (cdr (assoc queue emaci--build-counter)) (+ 1 count)))))
175 |
176 | (defun emaci//new-job (queue dir command branch stashes mode highlight-regexp)
177 | "Create a new job for QUEUE which gets executed in DIR.
178 |
179 | The job is created for QUEUE. If QUEUE is nil, use default queue.
180 |
181 | Run compilation command COMMAND (low level interface).
182 | If COMMAND starts with a cd command, that becomes the `default-directory'.
183 | The rest of the arguments are optional; for them, nil means use the default.
184 |
185 | BRANCH is a git branch name or a ref. It will be checkout out before
186 | running COMMAND.
187 |
188 | STASHES is a list of stashes to apply before running COMMAND or nil.
189 |
190 | MODE is the major mode to set in the compilation buffer. Mode
191 | may also be t meaning use function `compilation-shell-minor-mode'
192 | under `comint-mode'.
193 |
194 | If HIGHLIGHT-REGEXP is non-nil, `next-error' will temporarily highlight
195 | the matching section of the visited source line; the default is to use the
196 | global value of `compilation-highlight-regexp'."
197 | (let ((buildno (emaci//get-buildno queue))
198 | (queue (or queue "*default*")))
199 | (make-emaci-job
200 | :buildno buildno
201 | :queue queue
202 | :status 'queued
203 | :dir dir
204 | :ref branch
205 | :stashes stashes
206 | :command command
207 | :mode mode
208 | :highlight-regexp highlight-regexp)))
209 |
210 | (defun emaci//running-job-p (&optional queue)
211 | "Return t if there is a running job in QUEUE."
212 | (let* ((queue (or queue "*default*"))
213 | (job (cadr (assoc queue emaci-queue))))
214 | (and job (eq (emaci-job-status job) 'running))))
215 |
216 | (defun emaci//queue-job (job)
217 | "Add JOB to QUEUE in `emaci-queue'."
218 | (let ((queue (emaci-job-queue job)))
219 | (if (assoc queue emaci-queue)
220 | (setf (cdr (assoc queue emaci-queue)) (append (cdr (assoc queue emaci-queue)) (list job)))
221 | (add-to-list 'emaci-queue (cons queue (list job))))))
222 |
223 | (defun emaci//schedule (queue dir command &optional branch stashes mode highlight-regexp deferred)
224 | "Create and schedule a new job.
225 |
226 | Schedule the job in QUEUE.
227 | The job will get executed in DIR.
228 |
229 | Run compilation command COMMAND (low level interface).
230 | If COMMAND starts with a cd command, that becomes the `default-directory'.
231 | The rest of the arguments are optional; for them, nil means use the default.
232 |
233 | BRANCH is a git branch name or a ref. It will be checkout out before
234 | running COMMAND.
235 |
236 | STASHES is a list of stashes to apply before running COMMAND or nil.
237 |
238 | MODE is the major mode to set in the compilation buffer. Mode
239 | may also be t meaning use function `compilation-shell-minor-mode'
240 | under `comint-mode'.
241 |
242 | If HIGHLIGHT-REGEXP is non-nil, `next-error' will temporarily highlight
243 | the matching section of the visited source line; the default is to use the
244 | global value of `compilation-highlight-regexp'.
245 |
246 | If DEFERRED is non-nil, don't execute the job right away if queue is empty.
247 |
248 | Return the schedules job."
249 | (let ((job (emaci//new-job queue dir command branch stashes mode highlight-regexp)))
250 | (emaci//queue-job job)
251 | (if (or (emaci//running-job-p queue) deferred)
252 | (emaci//mgmt-buffer-update)
253 | (emaci/execute-next queue))
254 | job))
255 |
256 | (defun storax//compilation-exit-function (status code msg)
257 | "Set the exitcode on the job with STATUS exit CODE and MSG."
258 | (let ((job (cdr (assoc (current-buffer) emaci--buffer-job-alist))))
259 | (when job
260 | (setf (emaci-job-exitcode job) code)))
261 | (cons msg code))
262 |
263 | (defun emaci//compilation-finished (buffer msg)
264 | "Callback when compilation buffer finishes in BUFFER with MSG.
265 |
266 | Calls `emaci//job-finished'."
267 | (let ((job (cdr (assoc buffer emaci--buffer-job-alist))))
268 | (when job
269 | (emaci//job-finished job 'finished msg))))
270 |
271 | (defun emaci//get-log-filepath (job)
272 | "Return filepath to a file for the output of JOB."
273 | (let* ((expanded
274 | (directory-file-name
275 | (expand-file-name emaci-save-dir)))
276 | (directory (directory-file-name
277 | (concat (file-name-as-directory expanded)
278 | (file-name-as-directory "logs")
279 | (file-name-as-directory (emaci-job-queue job)))))
280 | (fullpath (concat (file-name-as-directory directory)
281 | (format "build_%04d.log" (emaci-job-buildno job)))))
282 | (make-directory directory t)
283 | fullpath))
284 |
285 | (defun emaci//save-log (job)
286 | "Save the output of the given JOB."
287 | (let ((buffer (emaci-job-buffer job)))
288 | (when buffer
289 | (with-current-buffer buffer
290 | (save-excursion
291 | (write-region (point-min) (point-max) (emaci//get-log-filepath job)))))))
292 |
293 | (defun emaci//job-finished (job status statusmsg)
294 | "Callback when JOB finished with STATUS and STATUSMSG and execute the next."
295 | (setf (emaci-job-status job) status)
296 | (setf (emaci-job-statusmsg job) statusmsg)
297 | (setf (emaci-job-datefinished job) (current-time))
298 | (emaci//move-job-to-history job)
299 | (emaci//save-vars)
300 | (emaci//save-log job)
301 | (run-hook-with-args 'emaci-finished-hook job)
302 | (emaci//git-revert job)
303 | (emaci/execute-next (emaci-job-queue job)))
304 |
305 | (defun emaci/execute-next (&optional queue)
306 | "Execute the next job in the QUEUE."
307 | (interactive (list (emaci//select-queue)))
308 | (let* ((queue (or queue "*default*"))
309 | (job (cadr (assoc queue emaci-queue))))
310 | (when job
311 | (let ((status (emaci-job-status job)))
312 | (cond
313 | ((eq (emaci-job-status job) 'queued)
314 | (emaci//execute job))
315 | ((eq (emaci-job-status job) 'running)
316 | (signal 'emaci-error-job-running job)))))
317 | (emaci//mgmt-buffer-update)))
318 |
319 | (defun emaci//create-buffer-name (job)
320 | "Return a buffer name for JOB."
321 | (format "%s: Build #%s" (emaci-job-queue job) (emaci-job-buildno job)))
322 |
323 | (defun emaci//create-buffer (job)
324 | "Create a buffer name for JOB."
325 | (let ((buffer (get-buffer-create (emaci//create-buffer-name job))))
326 | (add-to-list 'emaci--buffer-job-alist (cons buffer job))
327 | buffer))
328 |
329 | (defun emaci//execute (job)
330 | "Execute the next JOB."
331 | (setf (emaci-job-status job) 'running)
332 | (setf (emaci-job-oldref job) (emaci//current-commit (emaci-job-dir job)))
333 | (setf (emaci-job-buffer job) (buffer-name (emaci//create-buffer job)))
334 | (setf (emaci-job-datestarted job) (current-time))
335 | (let ((default-directory (emaci-job-dir job)))
336 | (emaci//git-apply job)
337 | (compilation-start
338 | (emaci-job-command job)
339 | (emaci-job-mode job)
340 | `(lambda (mode) (emaci//create-buffer-name ,job))
341 | (emaci-job-highlight-regexp job)))
342 | (run-hook-with-args 'emaci-started-hook job))
343 |
344 | (defun emaci//stashes (dir)
345 | "Return a list of stashes of repo in DIR."
346 | (let* ((default-directory dir)
347 | (stashesstr (vc-git--run-command-string nil "stash" "list" "--pretty=format:%H")))
348 | (if (or (not stashesstr) (equal "" stashesstr))
349 | nil
350 | (split-string stashesstr "\n"))))
351 |
352 | (defun emaci//stashes-human (dir)
353 | "Return a list of human readable stashes of repo in DIR."
354 | (let* ((default-directory dir)
355 | (stashesstr (vc-git--run-command-string nil "stash" "list")))
356 | (if (or (not stashesstr) (equal "" stashesstr))
357 | nil
358 | (split-string stashesstr "\n" t))))
359 |
360 | (defun emaci//current-commit (dir)
361 | "Return the current commit of repo in DIR."
362 | (let ((default-directory dir))
363 | (vc-git-working-revision dir)))
364 |
365 | (defun emaci//branches (dir)
366 | "Return a list with all branches of repo in DIR."
367 | (let* ((default-directory dir)
368 | (branches (vc-git-branches)))
369 | (if (car branches)
370 | branches
371 | nil)))
372 |
373 | (defun emaci//switch-to-branch (branch dir)
374 | "Switch to BRANCH of repo in DIR."
375 | (let ((default-directory dir))
376 | (when (and branch (vc-git-responsible-p default-directory))
377 | (vc-git-checkout nil nil branch))))
378 |
379 | (defun emaci//apply-stashes (stashes dir)
380 | "Apply STASHES to repo in DIR."
381 | (let ((default-directory dir))
382 | (when (vc-git-responsible-p default-directory)
383 | (dolist (stash stashes)
384 | (vc-git-stash-apply stash)))))
385 |
386 | (defun emaci//revert-stashes (stashes dir)
387 | "Reverse apply STASHES to repo in DIR."
388 | (let ((default-directory dir))
389 | (when (vc-git-responsible-p default-directory)
390 | (dolist (stash (reverse stashes))
391 | (shell-command (format "git stash show -p %s | git apply -R" stash))))))
392 |
393 | (defun emaci//git-revert (job)
394 | "Revert repo of JOB to the old status.
395 | Checkout the branch it was one before the job got executed.
396 | Revert all stashes that were applied by JOB."
397 | (let ((dir (emaci-job-dir job)))
398 | (emaci//revert-stashes (emaci-job-stashes job) dir)
399 | (emaci//switch-to-branch (emaci-job-oldref job) dir)))
400 |
401 | (defun emaci//git-apply (job)
402 | "Switch to ref stored in JOB and apply the stashes."
403 | (let ((dir (emaci-job-dir job)))
404 | (emaci//switch-to-branch (emaci-job-ref job) dir)
405 | (emaci//apply-stashes (emaci-job-stashes job) dir)))
406 |
407 | (defun emaci//signal-job (sigcode job)
408 | "Send SIGCODE to JOB.
409 |
410 | SIGCODE may be an integer, or a symbol whose name is a signal name."
411 | (let* ((job-buffer (get-buffer (emaci-job-buffer job)))
412 | (job-status (emaci-job-status job))
413 | (job-proc (get-buffer-process job-buffer)))
414 | (when (and job-proc (eq job-status 'running))
415 | (signal-process job-proc sigcode))))
416 |
417 | (defun emaci//cancel-job1 (job)
418 | "Set JOB status to `canceled'."
419 | (let ((job-status (emaci-job-status job)))
420 | (when (or (eq job-status 'running) (eq job-status 'queued))
421 | (setf (emaci-job-status job) 'canceled)
422 | (emaci//move-job-to-history job))))
423 |
424 | (defun emaci/cancel-job (job)
425 | "Cancel JOB by interrupting the process if it is running."
426 | (interactive (list (emaci//select-job "Cancel Job: " (emaci//select-queue))))
427 | (when job
428 | (let ((job-status (emaci-job-status job)))
429 | (when (eq job-status 'running)
430 | (emaci//signal-job 2 job))
431 | (emaci//cancel-job1 job))
432 | (emaci//mgmt-buffer-update)))
433 |
434 | (defun emaci/kill-job (job)
435 | "Cancel JOB by killing the process if it is running."
436 | (interactive (list (emaci//select-job "Kill Job: " (emaci//select-queue))))
437 | (let ((job-status (emaci-job-status job)))
438 | (when (eq job-status 'running)
439 | (emaci//signal-job 9 job))
440 | (emaci//cancel-job1 job))
441 | (emaci//mgmt-buffer-update))
442 |
443 | (defun emaci//move-job-to-history (job)
444 | "Remove JOB from queue and put it in history."
445 | (let ((queue (emaci-job-queue job)))
446 | (when (member job (assoc queue emaci-queue))
447 | (progn
448 | (if (assoc queue emaci-history)
449 | (setf (cdr (assoc queue emaci-history)) (append (cdr (assoc queue emaci-history)) (list job)))
450 | (add-to-list 'emaci-history (cons queue (list job))))
451 | (setf (cdr (assoc queue emaci-queue)) (delete job (cdr (assoc queue emaci-queue))))))))
452 |
453 | (defun emaci//save-var (var file)
454 | "Save the given VAR to FILE in `emaci-save-dir'."
455 | (let ((expanded
456 | (directory-file-name
457 | (expand-file-name emaci-save-dir))))
458 | (make-directory expanded t)
459 | (with-temp-buffer
460 | (prin1 (symbol-value var) (current-buffer))
461 | (write-file (concat (file-name-as-directory expanded) file)))))
462 |
463 | (defun emaci//save-history ()
464 | "Save the history to file."
465 | (emaci//save-var 'emaci-history "history.el"))
466 |
467 | (defun emaci//save-queue ()
468 | "Save the queue to file."
469 | (emaci//save-var 'emaci-queue "queue.el"))
470 |
471 | (defun emaci//save-build-counter ()
472 | "Save the build-counter to file."
473 | (emaci//save-var 'emaci--build-counter "build-counter.el"))
474 |
475 | (defun emaci//save-vars ()
476 | "Save history, queue and other variables to the `emaci-save-dir'."
477 | (emaci//save-history)
478 | (emaci//save-queue)
479 | (emaci//save-build-counter))
480 |
481 | (defun emaci//load-var (var file)
482 | "Load the given VAR from FILE in `emaci-load-dir'."
483 | (let* ((expanded
484 | (directory-file-name
485 | (expand-file-name emaci-save-dir)))
486 | (fullpath (concat (file-name-as-directory expanded) file)))
487 | (when (file-exists-p fullpath)
488 | (with-temp-buffer
489 | (insert-file-contents fullpath)
490 | (goto-char (point-min))
491 | (set var (read (current-buffer)))))))
492 |
493 | (defun emaci//load-history ()
494 | "Load the history from file."
495 | (emaci//load-var 'emaci-history "history.el"))
496 |
497 | (defun emaci//load-queue ()
498 | "Load the queue from file."
499 | (emaci//load-var 'emaci-queue "queue.el"))
500 |
501 | (defun emaci//load-build-counter ()
502 | "Load the build-counter from file."
503 | (emaci//load-var 'emaci--build-counter "build-counter.el"))
504 |
505 | (defun emaci/load-vars ()
506 | "Load history, queue and other variables from `emaci-save-dir'."
507 | (emaci//load-history)
508 | (emaci//load-queue)
509 | (emaci//load-build-counter))
510 |
511 | (defun emaci/get-dir ()
512 | "Interactively return a directory."
513 | (let ((default (if (boundp 'projectile-project-p) (projectile-project-p) default-directory)))
514 | (read-directory-name
515 | "Working directory: "
516 | default
517 | nil t)))
518 |
519 | (defun emaci/get-command ()
520 | "Interactively return a command for emaci."
521 | (read-shell-command "Command: " (car shell-command-history)))
522 |
523 | (defun emaci//list-major-modes ()
524 | "Return list of potential major mode names.
525 | From Tobias Zawada (http://stackoverflow.com/questions/5536304/emacs-stock-major-modes-list)"
526 | (let (l)
527 | (mapatoms #'(lambda (f) (and
528 | (commandp f)
529 | (string-match "-mode$" (symbol-name f))
530 | ;; auto-loaded
531 | (or (and (autoloadp (symbol-function f))
532 | (let ((doc (documentation f)))
533 | (when doc
534 | (and
535 | (let ((docSplit (help-split-fundoc doc f)))
536 | (and docSplit ;; car is argument list
537 | (null (cdr (read (car docSplit)))))) ;; major mode starters have no arguments
538 | (if (string-match "[mM]inor" doc) ;; If the doc contains "minor"...
539 | (string-match "[mM]ajor" doc) ;; it should also contain "major".
540 | t) ;; else we cannot decide therefrom
541 | ))))
542 | (null (help-function-arglist f)))
543 | (setq l (cons (symbol-name f) l)))))
544 | l))
545 |
546 | (defun emaci//select-mode ()
547 | "Select a major mode to use in the compilation buffer."
548 | (intern
549 | (completing-read
550 | "Select mode for compilation buffer: "
551 | (emaci//list-major-modes)
552 | nil t nil emaci-mode-history "comint-mode")))
553 |
554 | (defun emaci//list-queues ()
555 | "Return a list of queues."
556 | (mapcar 'car emaci-queue))
557 |
558 | (defun emaci//select-queue ()
559 | "Select a queue from `emaci-queue'."
560 | (completing-read
561 | "Select mode for compilation buffer: "
562 | (emaci//list-queues)
563 | nil nil nil nil "*default*"))
564 |
565 | (defun emaci//select-branch (dir)
566 | "Select a branch of repo in DIR."
567 | (when (vc-git-responsible-p dir)
568 | (completing-read
569 | "Select git branch: " (emaci//branches dir) nil 'confirm nil nil (emaci//current-commit dir))))
570 |
571 | (defun emaci//select-stashes (dir)
572 | "Select stashes of repoin DIR."
573 | (when (vc-git-responsible-p dir)
574 | (let* ((stashes-human (emaci//stashes-human dir))
575 | (stashes (completing-read-multiple
576 | "Select git stashes: " stashes-human nil t nil nil nil))
577 | (hashes (emaci//stashes dir)))
578 | (mapcar
579 | (lambda (stash) (nth (cl-position stash stashes-human :test 'equal) hashes))
580 | stashes))))
581 |
582 | (defun emaci/submit-job (queue dir command &optional branch stashes mode highlight-regexp)
583 | "Submit a job to emaci.
584 |
585 | Select a QUEUE for the job. Each queue is consumed serially.
586 | Multiple queues are executed in parallel.
587 |
588 | DIR is the working directory for the COMMAND to execute.
589 |
590 | If BRANCH is non-nil, switch to the branch before execution and switch back
591 | to the branch that was checked out when execution started afterwards.
592 |
593 | If STASHES is a list of stash hashes,
594 | apply stashes before executing the command.
595 | The changes will be reverted after the execution.
596 |
597 | MODE and HIGHLIGHT-REGEXP are for the compilation buffer.
598 | See `compilation-start'.
599 |
600 | Return the submitted job."
601 | (interactive (let ((dir (emaci/get-dir)))
602 | (list
603 | (emaci//select-queue)
604 | dir
605 | (emaci/get-command)
606 | (emaci//select-branch dir)
607 | (emaci//select-stashes dir)
608 | (emaci//select-mode))))
609 | (emaci//schedule queue dir command branch stashes mode highlight-regexp))
610 |
611 | (defun emaci/submit-job-comint (queue dir command &optional branch stashes highlight-regexp)
612 | "Submit a job to emaci.
613 |
614 | Select a QUEUE for the job. Each queue is consumed serially.
615 | Multiple queues are executed in parallel.
616 |
617 | DIR is the working directory for the COMMAND to execute.
618 |
619 | If BRANCH is non-nil, switch to the branch before execution and switch back
620 | to the branch that was checked out when execution started afterwards.
621 |
622 | If STASHES is a list of stash hashes,
623 | apply stashes before executing the command.
624 | The changes will be reverted after the execution.
625 |
626 | HIGHLIGHT-REGEXP is for the compilation buffer.
627 | See `compilation-start'. For mode, t will be used.
628 |
629 | Return the submitted job."
630 | (interactive (let ((dir (emaci/get-dir)))
631 | (list
632 | (emaci//select-queue)
633 | dir
634 | (emaci/get-command)
635 | (emaci//select-branch dir)
636 | (emaci//select-stashes dir))))
637 | (emaci//schedule queue dir command branch stashes t highlight-regexp))
638 |
639 | (defun emaci/init ()
640 | "Set callbacks for compilation."
641 | (setq compilation-exit-message-function 'storax//compilation-exit-function)
642 | (add-hook 'compilation-finish-functions 'emaci//compilation-finished))
643 |
644 | (emaci/init)
645 |
646 | (defun emaci//mgmt-buffer-heading ()
647 | "Return a heading for the emaci management buffer."
648 | (propertize "Queues:\n" 'face 'outline-1))
649 |
650 | (defun emaci//mgmt-buffer-queue-heading (queuename)
651 | "Return a heading for QUEUENAME for the emaci management buffer."
652 | (propertize (concat queuename ":") 'face 'outline-2))
653 |
654 | (defun emaci//mgmt-buffer-format-job-for-statusbar (job)
655 | "Return a statusbar segment for JOB."
656 | (when (emaci-job-p job)
657 | (let ((tick (propertize
658 | " " 'face
659 | (cond
660 | ((and (emaci-job-exitcode job) (zerop (emaci-job-exitcode job)))
661 | 'emaci-statbar-success-face)
662 | ((emaci-job-exitcode job)
663 | 'emaci-statbar-fail-face)
664 | ((eq (emaci-job-status job) 'canceled)
665 | 'emaci-statbar-canceled-face)
666 | ((eq (emaci-job-status job) 'running)
667 | 'emaci-statbar-running-face)
668 | ((eq (emaci-job-status job) 'queued)
669 | 'emaci-statbar-queued-face)
670 | (t
671 | 'emaci-statbar-unknown-face))
672 | 'statjob job)))
673 | (if (eq (emaci-job-status job) 'running)
674 | (concat " " tick " ")
675 | tick))))
676 |
677 | (defun emaci//mgmt-propface-label (str)
678 | "Return STR with `outline-4'."
679 | (propertize str 'face 'outline-4))
680 |
681 | (defun emaci//mgmt-format-status-detail (job)
682 | "Return formatted status detail for JOB."
683 | (format "%s %-11s"
684 | (emaci//mgmt-propface-label "Status:")
685 | (emaci-job-status job)))
686 |
687 | (defun emaci//mgmt-format-exitcode-detail (job)
688 | "Return formatted code detail for JOB."
689 | (let ((code (emaci-job-exitcode job)))
690 | (format "%s %s"
691 | (emaci//mgmt-propface-label "Exitcode:")
692 | (if code (emaci//mgmt-propface-for-status (number-to-string code) job) "--"))))
693 |
694 | (defun emaci//mgmt-format-duration-detail (job)
695 | "Return formatted duration detail for JOB."
696 | (let* ((started (emaci-job-datestarted job))
697 | (ended (emaci-job-datefinished job))
698 | (duration (when (and started ended) (time-to-seconds (time-subtract ended started)))))
699 | (format "%s %-11s"
700 | (emaci//mgmt-propface-label "Duration:")
701 | (if duration (format-seconds "%yy %dd %hh %mm %z%ss" duration) "--"))))
702 |
703 | (defun emaci//mgmt-format-started-detail (job)
704 | "Return formatted started detail for JOB."
705 | (let ((started (emaci-job-datestarted job)))
706 | (format "%s %s"
707 | (emaci//mgmt-propface-label "Started:")
708 | (if started (format-time-string "%d/%m/%Y %H:%M" started) "--"))))
709 |
710 | (defun emaci//mgmt-format-branch-detail (job)
711 | "Return a formatted branch detail for JOB."
712 | (let ((branch (emaci-job-ref job)))
713 | (format "%s %s"
714 | (emaci//mgmt-propface-label "Branch:")
715 | (if branch branch "--"))))
716 |
717 | (defun emaci//mgmt-format-command-detail (job &optional len)
718 | "Return formatted command detail for JOB.
719 |
720 | LEN is the maximum length of the command until it gets cut off.
721 | Defaults to 80."
722 | (let ((len (or len 80))
723 | (cmd (emaci-job-command job)))
724 | (format "%s\n%s"
725 | (emaci//mgmt-propface-label "Command:")
726 | (if (> (length cmd) len)
727 | (concat "..." (substring cmd (* -1 len)))
728 | cmd))))
729 |
730 | (defun emaci//mgmt-format-metadata-detail (job)
731 | "Return formatted metadata for JOB."
732 | (let ((metadata (emaci-job-metadata job)))
733 | (if metadata
734 | (concat
735 | "\n"
736 | (mapconcat
737 | (lambda (metadata)
738 | (let ((title (car metadata))
739 | (data (cdr metadata)))
740 | (format
741 | "%s\n%s"
742 | (emaci//mgmt-propface-label (concat title ":"))
743 | data)))
744 | metadata
745 | "\n"))
746 | "")))
747 |
748 | (defun emaci//mgmt-format-details (job)
749 | "Return formated JOB details."
750 | (format
751 | "\n%s %s\n%s %s\n%s\n%s%s%s"
752 | (emaci//mgmt-format-status-detail job)
753 | (emaci//mgmt-format-exitcode-detail job)
754 | (emaci//mgmt-format-duration-detail job)
755 | (emaci//mgmt-format-started-detail job)
756 | (emaci//mgmt-format-branch-detail job)
757 | (emaci//mgmt-format-command-detail job)
758 | (emaci//mgmt-format-metadata-detail job)
759 | (if emaci-enable-rms
760 | (concat
761 | "\n" (emaci//mgmt-propface-label "Richard Stallman: ")
762 | (nth (random (length emaci-rms-data)) emaci-rms-data))
763 | "")))
764 |
765 | (defun emaci//mgmt-buffer-format-job (job)
766 | "Return a formated JOB."
767 | (when (emaci-job-p job)
768 | (let* ((queue (emaci-job-queue job))
769 | (status (emaci-job-status job))
770 | (code (emaci-job-exitcode job))
771 | (started (emaci-job-datestarted job))
772 | (ended (emaci-job-datefinished job))
773 | (duration (when (and started ended) (time-to-seconds (time-subtract ended started)))))
774 | (concat
775 | (emaci//mgmt-propertize
776 | (emaci//mgmt-propface-for-status (format "\n#%s:" (emaci-job-buildno job)) job)
777 | (list queue job) (list 'field job))
778 | (emaci//mgmt-propertize
779 | (emaci//mgmt-format-details job)
780 | (list queue job 'details) (list 'field job))
781 | ""))))
782 |
783 | (defun emaci//mgmt-buffer-queue (queue)
784 | "Return the formated QUEUE for the emaci management buffer."
785 | (let* ((queuename (car queue))
786 | (queueitems (cdr queue))
787 | (historyitems (cl-subseq (cdr (assoc queuename emaci-history))
788 | (* -1 emaci-max-history-len-status)))
789 | (jobs (append historyitems queueitems)))
790 | (format
791 | "\n%s\n%s%s\n"
792 | (emaci//mgmt-buffer-queue-heading queuename)
793 | (emaci//mgmt-add-properties
794 | (concat
795 | (mapconcat
796 | (lambda (job) (emaci//mgmt-buffer-format-job-for-statusbar job))
797 | jobs "")
798 | "\n"
799 | (propertize "Jobs:" 'face 'outline-3))
800 | (list queuename))
801 | (mapconcat
802 | (lambda (job) (emaci//mgmt-buffer-format-job job))
803 | (reverse jobs) ""))))
804 |
805 | (defun emaci//mgmt-buffer-update ()
806 | "Initialize the management buffer."
807 | (let ((buffer? (get-buffer "*Emaci*"))
808 | (buffer (get-buffer-create "*Emaci*"))
809 | (inhibit-read-only t))
810 | (with-current-buffer buffer
811 | (unless buffer?
812 | (emaci-mode))
813 | (save-excursion
814 | (erase-buffer)
815 | (insert (emaci//mgmt-buffer-heading))
816 | (dolist (queue (reverse emaci-queue))
817 | (insert (emaci//mgmt-buffer-queue queue)))
818 | (unless buffer?
819 | (setq buffer-read-only t)
820 | (setq buffer-invisibility-spec nil)
821 | (mapcar
822 | (lambda (section)
823 | (if (> (length (emaci-section-arglist section)) 1)
824 | (add-to-invisibility-spec (cons section t))))
825 | emaci--sections))))))
826 |
827 | (defun emaci//mgmt-get-section-ident (args)
828 | "Return the section identifier for ARGS."
829 | (let (secargs sections)
830 | (dolist (arg args)
831 | (add-to-list 'secargs arg t)
832 | (add-to-list
833 | 'sections
834 | (let* ((section (make-emaci-section :arglist secargs))
835 | (pos (cl-position section emaci--sections :test 'equal)))
836 | (if pos
837 | (nth pos emaci--sections)
838 | (progn
839 | (add-to-list 'emaci--sections section)
840 | section)))))
841 | sections))
842 |
843 | (defun emaci//mgmt-propertize (string sechierarchy &optional props)
844 | "Propertize STRING.
845 |
846 | SECHIERARCHY is used for the `invisible' property.
847 | PROPS are also added."
848 | (apply
849 | 'propertize string
850 | (append (list 'invisible (emaci//mgmt-get-section-ident sechierarchy)) props)))
851 |
852 | (defun emaci//mgmt-add-properties (string sechierarchy)
853 | "Add properties to STRING.
854 |
855 | SECHIERARCHY is used for the `invisible' property."
856 | (progn
857 | (add-text-properties
858 | 0 (length string) (list 'invisible (emaci//mgmt-get-section-ident sechierarchy)) string)
859 | string))
860 |
861 | (defun emaci//mgmt-propface-for-status (string job)
862 | "Return a STRING with a face according to status of JOB."
863 | (propertize
864 | string 'face
865 | (cond
866 | ((and (emaci-job-exitcode job) (zerop (emaci-job-exitcode job)))
867 | 'emaci-mgmt-success-face)
868 | ((emaci-job-exitcode job)
869 | 'emaci-mgmt-fail-face)
870 | ((eq (emaci-job-status job) 'canceled)
871 | 'emaci-mgmt-canceled-face)
872 | ((eq (emaci-job-status job) 'running)
873 | 'emaci-mgmt-running-face)
874 | ((eq (emaci-job-status job) 'queued)
875 | 'emaci-mgmt-queued-face)
876 | (t
877 | 'emaci-mgmt-unknown-face))))
878 |
879 | (defun emaci//format-job-for-selection (job)
880 | "Return a concise description of JOB."
881 | (format
882 | "%s #%s: %s"
883 | (emaci-job-queue job)
884 | (emaci-job-buildno job)
885 | (emaci//mgmt-format-command-detail job)))
886 |
887 | (defun emaci//select-job (prompt queue)
888 | "Return a job from QUEUE."
889 | (let* ((jobs (reverse (cdr (assoc queue emaci-queue))))
890 | (jobs-human (mapcar
891 | (lambda (job) (emaci//format-job-for-selection job))
892 | jobs))
893 | (seljob (completing-read prompt jobs-human nil t)))
894 | (nth (cl-position seljob jobs-human :test 'equal) jobs)))
895 |
896 | (defun emaci//get-children-section (section all)
897 | "Get all children sections of SECTION.
898 |
899 | If ALL is non-nil return also grandchildren."
900 | (let* ((arglist (emaci-section-arglist section))
901 | children)
902 | (mapc
903 | (lambda (cand)
904 | (let ((secarglist (emaci-section-arglist cand))
905 | (larg (length arglist)))
906 | (when (and (if all t (equal (+ 1 larg) (length secarglist)))
907 | (equal (cl-subseq secarglist 0 larg) arglist)
908 | (not (eq cand section)))
909 | (add-to-list 'children cand))))
910 | emaci--sections)
911 | children))
912 |
913 | (defun emaci/toggle-section (arg)
914 | "Open and close sections.
915 |
916 | If ARG is non-nil, Apply show/close recursively."
917 | (interactive "P")
918 | (let* ((prop (car (get-text-property (point) 'invisible)))
919 | (arglist (when (emaci-section-p prop) (emaci-section-arglist prop))))
920 | (when arglist
921 | (let ((children (emaci//get-children-section prop arg)))
922 | (if children
923 | (let ((func (if (member (cons (car children) t) buffer-invisibility-spec)
924 | 'remove-from-invisibility-spec
925 | 'add-to-invisibility-spec)))
926 | (mapc
927 | `(lambda (child)
928 | (,func (cons child t)))
929 | children))
930 | (if (member (cons prop t) buffer-invisibility-spec)
931 | (remove-from-invisibility-spec (cons prop t))
932 | (add-to-invisibility-spec (cons prop t))))
933 | (force-window-update (get-buffer-window (current-buffer)))))))
934 |
935 | (defun emaci//show-log (job)
936 | "Show the buffer of JOB if it exists or try to open the log file."
937 | (when (emaci-job-p job)
938 | (let ((buffer (get-buffer (emaci-job-buffer job))))
939 | (if buffer
940 | (display-buffer buffer)
941 | (let ((path (emaci//get-log-filepath job)))
942 | (when (file-exists-p path)
943 | (find-file-other-window path)))))))
944 |
945 | (defun emaci//find-job-field (job)
946 | "Goto the field with JOB as property value."
947 | (when (emaci-job-p job)
948 | (goto-char (point-min))
949 | (let* ((buffer (current-buffer))
950 | (pos (next-property-change (point-min) buffer)))
951 | (while pos
952 | (if (eq job (get-text-property pos 'field buffer))
953 | (progn (goto-char (+ 1 pos))
954 | (setq pos nil))
955 | (setq pos (next-property-change pos buffer))))
956 | (let* ((sections (get-text-property (point) 'invisible))
957 | (childs (emaci//get-children-section (car sections) nil))
958 | (children (emaci//get-children-section (cadr sections) nil))
959 | (allsections (append sections childs children)))
960 | (dolist (section allsections)
961 | (remove-from-invisibility-spec (cons section t)))
962 | (force-window-update (get-buffer-window (current-buffer)))))))
963 |
964 | (defun emaci/mgmt-ret ()
965 | "When on status bar, goto job, when on job show log."
966 | (interactive)
967 | (let ((statjob (get-text-property (point) 'statjob (current-buffer)))
968 | (field (get-text-property (point) 'field (current-buffer))))
969 | (if statjob
970 | (emaci//find-job-field statjob)
971 | (emaci//show-log field))))
972 |
973 | (define-derived-mode emaci-mode special-mode "Emaci"
974 | "Major mode for emaci management buffer.
975 |
976 | \\{emaci-mode-map}
977 |
978 | Entry to this mode calls the value of `emaci-mode-hook'
979 | if that value is non-nil.")
980 |
981 | (defun emaci/mgmt-buffer ()
982 | "Show the emaci management buffer."
983 | (interactive)
984 | (emaci//mgmt-buffer-update)
985 | (display-buffer "*Emaci*"))
986 |
987 | (provide 'emaci)
988 |
989 | ;;; emaci.el ends here
990 |
--------------------------------------------------------------------------------
/emaci.zsh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env zsh
2 |
3 | # Source this script to get a convenient emaci function to send commands to emacs.
4 | # It also tries to transfer all environment variables.
5 | # Usage:
6 | # emaci echo Hello World
7 | # emaci 'echo $PWD'
8 | # emaci './configure && make && make install'
9 |
10 | emacienv () { echo -e $(declare -px | awk '{if (NR == 1) printf $0;else if ($0 !~ /^typeset -.*/ && last !~ /^typeset -ax.*/) printf "\\n"$0;else printf " && "$0;}{last=$0}')' && ' }
11 | emaci () { emacsclient --eval "$(echo "(emaci/submit-job-comint nil \"$PWD\" \"$(emacienv)cd $PWD && $@\")")" }
12 |
--------------------------------------------------------------------------------
/evm_install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # This script will setup Evm (Emacs Version Manager) and Cask on
4 | # Travis to use for Emacs Lisp testing.
5 | #
6 | # In .travis.yml, add this:
7 | #
8 | # - curl -fsSkL https://gist.github.com/rejeep/ebcd57c3af83b049833b/raw > x.sh && source ./x.sh
9 | #
10 | # Emacs 24.3 is installed in the above script because Cask requires
11 | # Emacs 24 to be installed. Because of this, when installing other
12 | # environments in the .travis.yml configuration, use the --skip
13 | # option, for example:
14 | #
15 | # - evm install $EVM_EMACS --use --skip
16 |
17 | export PATH="/home/travis/.evm/bin:$PATH"
18 | export PATH="/home/travis/.cask/bin:$PATH"
19 |
20 | git clone https://github.com/rejeep/evm.git /home/travis/.evm
21 | evm config path /tmp
22 | evm install $EVM_EMACS --use --skip
23 |
24 | curl -fsSkL https://raw.github.com/cask/cask/master/go | python
25 |
--------------------------------------------------------------------------------
/rms.data:
--------------------------------------------------------------------------------
1 | When Richard Stallman throws exceptions, it’s across the room.
2 | Some people check their computers for viruses. Viruses check their computers for Richard Stallman.
3 | All arrays Richard Stallman declares are of infinite size, because Richard Stallman knows no bounds.
4 | Richard Stallman doesn’t have disk latency because the hard drive knows to hurry the hell up.
5 | Richard Stallman writes code that optimizes itself.
6 | Richard Stallman can’t test for equality because he has no equal.
7 | Richard Stallman burst the dot com bubble.
8 | Richard Stallman can write infinite recursion functions…and have them return.
9 | Richard Stallman can solve the Towers of Hanoi in one move.
10 | The only pattern Richard Stallman knows is God Object.
11 | Project managers never ask Richard Stallman for estimations... ever.
12 | Richard Stallman doesn’t use web standards as the web will conform to him.
13 | “It works on my machine” always holds true for Richard Stallman.
14 | Richard Stallman can delete the Recycling Bin.
15 | Richard Stallman’s beard can type 140 wpm.
16 | Richard Stallman can unit test entire applications with a single assert.
17 | All viruses are programmed to delete themselves if they find out that they are on Richard Stallman's machine.
18 | Richard Stallman can tell you if your program will halt.
19 | Your program will halt when Richard Stallman tells it to halt.
20 | Richard Stallman never showers: he runs 'make clean'.
21 | Richard Stallman's left and right hands are named "(" and ")"
22 | Richard Stallman has created the singularity, but since he usually works without an internet connection it won't be apparent for another two weeks.
23 | Richard Stallman first words were actually syscalls.
24 | Richard Stallman is the only man alive who can pronounce GNU the way it is meant to be pronounced.
25 | When Richard Stallman makes a sudo command, he loses permissions.
26 | Richard Stallman's beard is made of parentheses.
27 | Richard Stallman's DNA is in binary.
28 | Richard Stallman's nervous system is completely wireless.
29 | Richard Stallman's brain accepts UNIX commands.
30 | Richard Stallman's brain can triple-boot.
31 | If Richard Stallman has 1GB of RAM, and you have 1GB of RAM, Richard Stallman has more RAM than you.
32 | Richard Stallman eats ethernet cables. That's why they invented wireless.
33 | Richard Stallman wrote a program that divides by zero.
34 | Ricahrd Stallman doesn't use zip drives, he just squeezes the hard drive.
35 | Richard Stallman's compiler is afraid to report errors.
36 | Richard Stallman wrote the compiler God used. The Big Bang was the Universe's first segfault.
37 | Richard Stallman successfully compiled a kernel of popcorn.
38 | Richard Stallman doesn't write programs, they write themselves out of reverence.
39 | Richard Stallman can make infinite loops end.
40 | Richard Stallman's computer doesn't have a clock, it defines what time it is.
41 | Richard Stallman wrote a program to compute the last digit of pi.
42 | Richard Stallman can solve the halting problem... in polynomial time.
43 | For Richard Stallman, polynomial time is O(1).
44 | Richard Stallman doesn't use web browsers. Emacs connects directly to the network driver and he reads and writes TCP/IP himself.
45 | Richard Stallman does not use a web browser. At all.
46 | Richard Stallman can write "int main();", compile it, and get a working copy of HURD. The only reason he hasn't done so yet is because he wants to give other GNU hackers some practice.
47 | Some people wear Linus Torvalds pyjama's to bed, Linus Torvalds wears RMS pyjama's.
48 | There is no software development process, only a bunch of programs Richard Stallman allows to exist.
49 | The chief export of Richard Stallman is s-expressions.
50 | Richard Stallman once ate three 72 oz. steaks in one hour. He spent the first 45 minutes coding a new Emacs elisp extension.
51 | In an average living room there are 1,242 objects RMS could use to write an OS, including the room itself.
52 | Richard Stallman has no mother, he coded himself.
53 | When Richard Stallman goes to the loo, he core dumps.
54 | Richard Stalman can write a dead loop that executes in under 10 ms.
55 | Richard Stallman is the architect of the matrix.
56 | Richard Stallman doesn't read web pages. They write to him.
57 | Richard Stallman has a working version of Hurd, he just think the world isn't ready for that much freedom.
58 | When Richard Stallman sets a file's permissions to executable, the program dies.
59 | Richard Stallman needs neither mouse nor keyboard to operate his computer. He just stares it down until it does what he wants.
60 | Richard Stallman doesn't need to backup his data; he remembers every one and zero and can recreate his data at will.
61 | Richard Stallman didn't write the GPL. He is the GPL.
62 | Inside every program Richard Stallman writes there is a compiler for a new language he just created.
63 | Vendor lock-in is when vendors lock themselves inside of a building out of fear of Richard Stallman's wrath.
64 | When Richard Stallman pipes to more, he gets less.
65 | In Soviet Russia, Richard Stallman is still Richard Stallman!
66 | Richard Stallman is pronounced as "Richard Stallman" in Klingon.
67 | Richard Stallman can touch MC\ Hammer.
68 | There's no chin under Richard Stallman's beard, only another Emacs.
69 | Richard Stallman gets 9 bits to the byte.
70 | Richard Stallman can leave neutral or negative feedback on eBay.
71 | Richard Stallman counted to infinity. Twice...
72 | Richard Stallman's flute only plays free music.
73 | In a fight between John Von Neumann and Alan Turing, the winner would be Richard Stallman.
74 | There is no theory of evolution, just a linked-list of processes that Richard Stallman hasn't killed.
75 | When Richard Stallman executes ps -e, you show up.
76 | Richard Stallman is so complex that psychiatrists find probing his mind NP-Hard.
77 | On Richard Stallman's computer the bootloader is contained in his ~/.emacs.
78 | Richard Stallman's Emacs is multithreaded.
79 | Richard Stallman's Emacs config is the reason we all exist.
80 | Richard Stallman can write to ROM.
81 | Richard Stallman has 105% uptime guaranteed.
82 | Richard Stallman doesn't run tests because he can conclusively prove that his programm works.
83 | Richard Stallman can access private methods.
84 | Richard Stallman can retrieve output from /dev/null.
85 | Richard Stallman can press every Emacs keybinding at once with one finger.
86 | Richard Stallman has run a stable version of GNU Hurd for years.
87 | Richard Stallman once described the entire universe exhaustively in a 2k plain text org file.
88 | The only reason you are alive is because Richard Stallman organises your life with org-mode in plain text.
89 | If Richard Stallman has 2 parentheses and you have 2, Richard Stallman has still more than you.
90 | When Richard Stallman enters ) a matching parenthesis is always found.
91 |
--------------------------------------------------------------------------------
/test/emaci-test.el:
--------------------------------------------------------------------------------
1 | ;;; test-emaci.el --- test emaci main functions
2 |
3 | ;; Copyright (C) 2016 by David ZUBER
4 |
5 | ;; Author: David ZUBER
6 |
7 | ;; This program is free software; you can redistribute it and/or modify
8 | ;; it under the terms of the GNU General Public License as published by
9 | ;; the Free Software Foundation, either version 3 of the License, or
10 | ;; (at your option) any later version.
11 |
12 | ;; This program is distributed in the hope that it will be useful,
13 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | ;; GNU General Public License for more details.
16 |
17 | ;; You should have received a copy of the GNU General Public License
18 | ;; along with this program. If not, see .
19 |
20 | ;;; Commentary:
21 |
22 | ;;; Code:
23 |
24 | (require 'ert)
25 |
26 | (when (require 'undercover nil t)
27 | (require 'edebug)
28 | (def-edebug-spec setf (&rest [place form]))
29 | (undercover "*.el"))
30 |
31 | (require 'cl-lib)
32 | (require 'vc-git)
33 | (require 'emaci)
34 |
35 | (emaci/init)
36 |
37 | (defvar ert-async-timeout 10
38 | "Number of seconds to wait for callbacks before failing.")
39 |
40 | (defvar emaci-test-repo (file-name-as-directory (getenv "EMACI_TESTGITDIR")))
41 | (defvar emaci-test-empty-repo (file-name-as-directory (getenv "EMACI_TESTGITDIR2")))
42 |
43 | (defmacro with-repo (&rest body)
44 | "Evaluate BODY with `emaci-test-repo' as default directory."
45 | `(let ((default-directory emaci-test-repo))
46 | ,@body))
47 |
48 | (defmacro with-empty-repo (&rest body)
49 | "Evaluate BODY with `emaci-test-empty-repo' as default directory."
50 | `(let ((default-directory emaci-test-empty-repo))
51 | ,@body))
52 |
53 | (defmacro with-no-repo (&rest body)
54 | "Evaluate BODY with / as default directory."
55 | `(let ((default-directory "/"))
56 | ,@body))
57 |
58 | (defvar emaci-commit-1 (with-repo (vc-git-working-revision emaci-test-repo)))
59 | (defvar emaci-stashes (with-repo
60 | (split-string (shell-command-to-string "git stash list --pretty=format:%H"))))
61 |
62 | (defun tmp-save-dir ()
63 | (make-temp-file (concat (file-name-as-directory (getenv "EMACI_SAVEDIR")) "emaci") t))
64 |
65 | (setq emaci-save-dir (tmp-save-dir))
66 |
67 | (ert-deftest comp-finished-hook ()
68 | "Test if hook is installed"
69 | (should (member 'emaci//compilation-finished compilation-finish-functions)))
70 |
71 | (defmacro with-sandbox (&rest body)
72 | "Evaluate BODY with all variables let bound."
73 | `(let ((emaci-queue nil)
74 | (emaci-history nil)
75 | (emaci--buffer-job-alist nil)
76 | (emaci--build-counter nil)
77 | (emaci-save-dir (tmp-save-dir)))
78 | ,@body
79 | (delete-directory emaci-save-dir t)))
80 |
81 | (defmacro with-advice (args &rest body)
82 | "Replace a function (car of ARGS) with function (cdr of ARGS) while executing BODY."
83 | (declare (indent 1))
84 | (let ((fun-name (car args))
85 | (advice (cadr args))
86 | (orig-sym (make-symbol "orig")))
87 | `(cl-letf* ((,orig-sym (symbol-function ',fun-name))
88 | ((symbol-function ',fun-name)
89 | (lambda (&rest args)
90 | (apply ,advice ,orig-sym args))))
91 | ,@body)))
92 |
93 | (defun dummy-callback ()
94 | "Dummy callback so we make sure to wait for async methods."
95 | t)
96 |
97 | (defmacro ert-deftest-async (name callbacks &rest body)
98 | "Like `ert-deftest' but with support for async.
99 |
100 | NAME is the name of the test, which is the first argument to
101 | `ert-deftest'.
102 |
103 | CALLBACKS is a list of callback functions that all must be called
104 | before `ert-async-timeout'. If all callback functions have not
105 | been called before the timeout, the test fails.
106 |
107 | The callback functions should be called without any argument. If
108 | a callback function is called with a string as argument, the test
109 | will fail with that error string.
110 |
111 | BODY is the actual test."
112 | (unless callbacks
113 | (setq callbacks (list 'dummy-callback)))
114 | (declare (indent 2))
115 | (let ((cbs
116 | (mapcar
117 | (lambda (callback)
118 | `(lambda (&rest args)
119 | (,callback)
120 | (if (member ',callback callbacked)
121 | (unless (eq 'dummy-callback ',callback)
122 | (ert-fail (format "Callback %s called multiple times" ',callback)))
123 | (push ',callback callbacked))))
124 | callbacks)))
125 | `(ert-deftest ,name ()
126 | (let ((callbacks ',cbs)
127 | (callbacked nil)
128 | (emaci-queue nil)
129 | (emaci-history nil)
130 | (emaci--buffer-job-alist nil)
131 | (emaci--build-counter nil)
132 | (emaci-save-dir (tmp-save-dir))
133 | (compilation-finish-functions (list 'emaci//compilation-finished)))
134 | (setq compilation-finish-functions (append compilation-finish-functions callbacks))
135 | (with-timeout
136 | (ert-async-timeout
137 | (ert-fail (format "Timeout of %ds exceeded. Expected the functions [%s] to be called, but was [%s]."
138 | ert-async-timeout
139 | ,(mapconcat 'symbol-name callbacks " ")
140 | (mapconcat 'symbol-name callbacked " "))))
141 | ,@body
142 | (while (or (not (equal (sort (mapcar 'symbol-name callbacked) 'string<)
143 | (sort (mapcar 'symbol-name ',callbacks) 'string<)))
144 | (process-list))
145 | (accept-process-output nil 0.05))
146 | (delete-directory emaci-save-dir t))))))
147 |
148 | (ert-deftest get-buildno-default ()
149 | "Test getting buildno for default queue."
150 | (with-sandbox
151 | (let ((result (emaci//get-buildno)))
152 | (should (equal result 1)))))
153 |
154 | (ert-deftest get-buildno-increase-default ()
155 | "Test getting another buildno for default queue."
156 | (with-sandbox
157 | (let ((emaci--build-counter '(("*default*" . 1))))
158 | (emaci//get-buildno)
159 | (should (equal (cdr (assoc "*default*" emaci--build-counter)) 2)))))
160 |
161 | (ert-deftest get-buildno-queue ()
162 | "Test getting buildno for a new queue."
163 | (with-sandbox
164 | (let ((result (emaci//get-buildno "testqueue")))
165 | (should (equal result 1)))))
166 |
167 | (ert-deftest build-counter-new-queue ()
168 | "Test getting a buildno for a new queue."
169 | (with-sandbox
170 | (let ((emaci--build-counter '(("*default*" . 1))))
171 | (emaci//get-buildno "testqueue")
172 | (should (equal emaci--build-counter '(("testqueue" . 1) ("*default*" . 1)))))))
173 |
174 | (ert-deftest build-counter-increase-queue ()
175 | "Test getting a buildno for an existing queue."
176 | (with-sandbox
177 | (let ((emaci--build-counter '(("*default*" . 1) ("testqueue" . 1))))
178 | (should (equal (emaci//get-buildno "testqueue") 2))
179 | (should (equal emaci--build-counter '(("*default*" . 1) ("testqueue" . 2)))))))
180 |
181 | (defun test-job ()
182 | "Create a test job."
183 | (emaci//new-job "testqueue" "~" "echo Tis but a scratch" nil nil 'comint-mode "$.*^"))
184 |
185 | (defun default-test-job ()
186 | "Create a test job."
187 | (emaci//new-job nil "~" "echo Tis but a scratch" nil nil 'comint-mode "$.*^"))
188 |
189 | (defun assert-job
190 | (job queue buildno status statusmsg code oldref ref stashes buffer dir command mode highlight-regexp)
191 | "Assert job has right attributes."
192 | (should (equal (emaci-job-queue job) queue))
193 | (should (equal (emaci-job-buildno job) buildno))
194 | (should (equal (emaci-job-status job) status))
195 | (should (equal (emaci-job-statusmsg job) statusmsg))
196 | (should (equal (emaci-job-exitcode job) code))
197 | (should (equal (emaci-job-oldref job) oldref))
198 | (should (equal (emaci-job-ref job) ref))
199 | (should (equal (emaci-job-stashes job) stashes))
200 | (should (equal (emaci-job-buffer job) buffer))
201 | (should (equal (emaci-job-dir job) dir))
202 | (should (equal (emaci-job-command job) command))
203 | (should (equal (emaci-job-mode job) mode))
204 | (should (equal (emaci-job-highlight-regexp job) highlight-regexp)))
205 |
206 | (ert-deftest new-job-buildno-first ()
207 | "Test if first job has right build-no."
208 | (with-sandbox
209 | (let ((job (test-job)))
210 | (should (equal (emaci-job-buildno job) 1)))))
211 |
212 | (ert-deftest new-job-buildno-second ()
213 | "Test if first job has right build-no."
214 | (with-sandbox
215 | (let ((job (test-job))
216 | (job2 (test-job)))
217 | (should (equal (emaci-job-buildno job2) 2)))))
218 |
219 | (ert-deftest new-job-build-counter ()
220 | "Test if counter is increased after job creation."
221 | (with-sandbox
222 | (let ((job (test-job)))
223 | (should (equal emaci--build-counter '(("testqueue" . 1)))))))
224 |
225 | (ert-deftest new-job-status ()
226 | "Test initial job status."
227 | (with-sandbox
228 | (let ((job (test-job)))
229 | (should (equal (emaci-job-status job) 'queued)))))
230 |
231 | (ert-deftest new-job-statusmsg ()
232 | "Test initial job statusmsg."
233 | (with-sandbox
234 | (let ((job (test-job)))
235 | (should (equal (emaci-job-statusmsg job) nil)))))
236 |
237 | (ert-deftest new-job-buffer ()
238 | "Test initial job buffer."
239 | (with-sandbox
240 | (let ((job (test-job)))
241 | (should (equal (emaci-job-buffer job) nil)))))
242 |
243 | (ert-deftest new-job-dir ()
244 | "Test job dir attribute."
245 | (with-sandbox
246 | (let ((job (test-job)))
247 | (should (equal (emaci-job-dir job) "~")))))
248 |
249 | (ert-deftest new-job-command ()
250 | "Test job command attribute."
251 | (with-sandbox
252 | (let ((job (test-job)))
253 | (should (equal (emaci-job-command job)
254 | "echo Tis but a scratch")))))
255 |
256 | (ert-deftest new-job-mode ()
257 | "Test job mode attribute."
258 | (with-sandbox
259 | (let ((job (test-job)))
260 | (should (equal (emaci-job-mode job) 'comint-mode)))))
261 |
262 | (ert-deftest new-job-highlight-regexp ()
263 | "Test job highlight regexp attribute."
264 | (with-sandbox
265 | (let ((job (test-job)))
266 | (should (equal (emaci-job-highlight-regexp job) "$.*^")))))
267 |
268 | (ert-deftest new-job-queue ()
269 | "Test if queue stays empty when creating a job."
270 | (with-sandbox
271 | (let ((job (test-job)))
272 | (should (equal emaci-queue nil)))))
273 |
274 | (ert-deftest queue-job-one ()
275 | "Test if a job gets queued."
276 | (with-sandbox
277 | (let ((job (test-job)))
278 | (emaci//queue-job job)
279 | (should (equal emaci-queue (list (cons "testqueue" (list job))))))))
280 |
281 | (ert-deftest queue-job-two ()
282 | "Test if job gets appended to queue."
283 | (with-sandbox
284 | (let ((job (test-job))
285 | (job2 (test-job)))
286 | (emaci//queue-job job)
287 | (emaci//queue-job job2)
288 | (should (equal emaci-queue (list (cons "testqueue" (list job job2))))))))
289 |
290 | (ert-deftest queue-job-one-default ()
291 | "Test if a job gets queued to default queue."
292 | (with-sandbox
293 | (let ((job (default-test-job)))
294 | (emaci//queue-job job)
295 | (should (equal emaci-queue (list (cons "*default*" (list job))))))))
296 |
297 | (ert-deftest queue-job-two-default ()
298 | "Test if job gets appended to default queue."
299 | (with-sandbox
300 | (let ((job (default-test-job))
301 | (job2 (default-test-job)))
302 | (emaci//queue-job job)
303 | (emaci//queue-job job2)
304 | (should (equal emaci-queue (list (cons "*default*" (list job job2))))))))
305 |
306 | (ert-deftest queue-move-to-history ()
307 | "Test moving job to history."
308 | (with-sandbox
309 | (let ((job (test-job))
310 | (job2 (test-job)))
311 | (emaci//queue-job job)
312 | (emaci//queue-job job2)
313 | (emaci//move-job-to-history job2)
314 | (should (equal emaci-queue (list (cons "testqueue" (list job)))))
315 | (should (equal emaci-history (list (cons "testqueue" (list job2))))))))
316 |
317 | (ert-deftest queue-move-to-history-default ()
318 | "Test moving job to default history."
319 | (with-sandbox
320 | (let ((job (default-test-job))
321 | (job2 (default-test-job)))
322 | (emaci//queue-job job)
323 | (emaci//queue-job job2)
324 | (emaci//move-job-to-history job2)
325 | (should (equal emaci-queue (list (cons "*default*" (list job)))))
326 | (should (equal emaci-history (list (cons "*default*" (list job2))))))))
327 |
328 | (ert-deftest running-job-p-empty ()
329 | "Test there is no running job in an empty queue."
330 | (with-sandbox
331 | (should-not (emaci//running-job-p))))
332 |
333 | (ert-deftest running-job-p-not-running ()
334 | "Test if queued job is not running."
335 | (with-sandbox
336 | (let ((job (test-job)))
337 | (emaci//queue-job job)
338 | (should-not (emaci//running-job-p "testqueue")))))
339 |
340 | (ert-deftest running-job-p-running ()
341 | "Test if there is a running job in queue."
342 | (with-sandbox
343 | (let ((job (test-job))
344 | (job2 (test-job)))
345 | (setf (emaci-job-status job) 'running)
346 | (emaci//queue-job job)
347 | (emaci//queue-job job2)
348 | (should (emaci//running-job-p "testqueue")))))
349 |
350 | (ert-deftest running-job-p-running-default ()
351 | "Test if there is a running job in default queue."
352 | (with-sandbox
353 | (let ((job (default-test-job))
354 | (job2 (default-test-job)))
355 | (setf (emaci-job-status job) 'running)
356 | (emaci//queue-job job)
357 | (emaci//queue-job job2)
358 | (should (emaci//running-job-p)))))
359 |
360 | (defun assert-history-one-default ()
361 | (should (equal (length (assoc "*default*" emaci-queue)) 1))
362 | (should (equal (length (assoc "*default*" emaci-history)) 2))
363 | (assert-job
364 | (cadr (assoc "*default*" emaci-history))
365 | "*default*" 1 'finished "finished\n" 0 nil nil nil
366 | "*default*: Build #1" "~" "echo 'Come on, you pansy!'" nil nil))
367 |
368 | (ert-deftest-async
369 | schedule-history-default (assert-history-one-default)
370 | (emaci//schedule nil "~" "echo 'Come on, you pansy!'"))
371 |
372 | (defun assert-history-one-queue ()
373 | (should (equal (length (assoc "testqueue" emaci-queue)) 1))
374 | (should (equal (length (assoc "testqueue" emaci-history)) 2))
375 | (assert-job
376 | (cadr (assoc "testqueue" emaci-history))
377 | "testqueue" 1 'finished "finished\n" 0 nil nil nil
378 | "testqueue: Build #1" "~" "echo 'Come on, you pansy!'" nil nil))
379 |
380 | (ert-deftest-async
381 | schedule-history-queue (assert-history-one-queue)
382 | (emaci//schedule "testqueue" "~" "echo 'Come on, you pansy!'"))
383 |
384 | (defun assert-queue-empty-default ()
385 | (should-not (cdr (assoc "*default*" emaci-queue))))
386 |
387 | (ert-deftest-async
388 | schedule-queue-default (assert-queue-empty-default)
389 | (emaci//schedule nil "~" "echo 'Come on, you pansy!'"))
390 |
391 | (defun assert-queue-empty-queue ()
392 | (should-not (cdr (assoc "testqueue" emaci-queue))))
393 |
394 | (ert-deftest-async
395 | schedule-queue-queue (assert-queue-empty-queue)
396 | (emaci//schedule "testqueue" "~" "echo 'Come on, you pansy!'"))
397 |
398 | (ert-deftest-async
399 | schedule-running-default ()
400 | (emaci//schedule nil "~" "echo 'Come on, you pansy!'")
401 | (assert-job
402 | (cadr (assoc "*default*" emaci-queue))
403 | "*default*" 1 'running nil nil nil nil nil
404 | "*default*: Build #1" "~" "echo 'Come on, you pansy!'" nil nil))
405 |
406 | (ert-deftest-async
407 | schedule-running ()
408 | (emaci//schedule "testqueue" "~" "echo 'Come on, you pansy!'")
409 | (assert-job
410 | (cadr (assoc "testqueue" emaci-queue))
411 | "testqueue" 1 'running nil nil nil nil nil
412 | "testqueue: Build #1" "~" "echo 'Come on, you pansy!'" nil nil))
413 |
414 | (ert-deftest-async
415 | schedule-two-first-default ()
416 | (emaci//schedule nil "~" "echo 'Come on, you pansy!'")
417 | (emaci//schedule nil "~" "echo 'Come on, you pansy!'")
418 | (assert-job
419 | (cadr (assoc "*default*" emaci-queue))
420 | "*default*" 1 'running nil nil nil nil nil
421 | "*default*: Build #1" "~" "echo 'Come on, you pansy!'" nil nil))
422 |
423 | (ert-deftest-async
424 | schedule-two-second-default ()
425 | (emaci//schedule nil "~" "echo 'Come on, you pansy!'")
426 | (emaci//schedule nil "~" "echo 'Come on, you pansy!'")
427 | (assert-job
428 | (nth 1 (cdr (assoc "*default*" emaci-queue)))
429 | "*default*" 2 'queued nil nil nil nil nil nil
430 | "~" "echo 'Come on, you pansy!'" nil nil))
431 |
432 | (ert-deftest schedule-deferred ()
433 | "Test queuing with DEFERRED arg."
434 | (with-sandbox
435 | (emaci//schedule "testqueue" "~" "echo 'Come on, you pansy!'" nil nil nil nil t)
436 | (assert-job
437 | (cadr (assoc "testqueue" emaci-queue))
438 | "testqueue" 1 'queued nil nil nil nil nil nil
439 | "~" "echo 'Come on, you pansy!'" nil nil)))
440 |
441 | (ert-deftest cancel-queued ()
442 | "Test canceling a queued job."
443 | (with-sandbox
444 | (let ((job (test-job))
445 | (job2 (test-job)))
446 | (emaci//queue-job job)
447 | (emaci//queue-job job2)
448 | (emaci/cancel-job job2)
449 | (should (equal emaci-queue (list (cons "testqueue" (list job)))))
450 | (should (equal emaci-history (list (cons "testqueue" (list job2)))))
451 | (should (eq (emaci-job-status job2) 'canceled)))))
452 |
453 | (ert-deftest cancel-running ()
454 | "Test canceling a running job."
455 | (with-sandbox
456 | (let ((job (test-job))
457 | (job2 (test-job)))
458 | (setf (emaci-job-buffer job2) (buffer-name (emaci//create-buffer job2)))
459 | (emaci//queue-job job)
460 | (emaci//queue-job job2)
461 | (setf (emaci-job-status job2) 'running)
462 | (emaci/cancel-job job2)
463 | (should (equal emaci-queue (list (cons "testqueue" (list job)))))
464 | (should (equal emaci-history (list (cons "testqueue" (list job2)))))
465 | (should (eq (emaci-job-status job2) 'canceled)))))
466 |
467 | (ert-deftest cancel-canceled ()
468 | "Test canceling a canceled job."
469 | (with-sandbox
470 | (let ((job (test-job))
471 | (job2 (test-job)))
472 | (emaci//queue-job job)
473 | (emaci//queue-job job2)
474 | (setf (emaci-job-status job2) 'canceled)
475 | (emaci//move-job-to-history job2)
476 | (emaci/cancel-job job2)
477 | (should (equal emaci-queue (list (cons "testqueue" (list job)))))
478 | (should (equal emaci-history (list (cons "testqueue" (list job2)))))
479 | (should (eq (emaci-job-status job2) 'canceled)))))
480 |
481 | (ert-deftest kill-queued ()
482 | "Test killing a queued job."
483 | (with-sandbox
484 | (let ((job (test-job))
485 | (job2 (test-job)))
486 | (emaci//queue-job job)
487 | (emaci//queue-job job2)
488 | (emaci/kill-job job2)
489 | (should (equal emaci-queue (list (cons "testqueue" (list job)))))
490 | (should (equal emaci-history (list (cons "testqueue" (list job2)))))
491 | (should (eq (emaci-job-status job2) 'canceled)))))
492 |
493 | (ert-deftest kill-running ()
494 | "Test killing a running job."
495 | (with-sandbox
496 | (let ((job (test-job))
497 | (job2 (test-job)))
498 | (setf (emaci-job-buffer job2) (buffer-name (emaci//create-buffer job2)))
499 | (emaci//queue-job job)
500 | (emaci//queue-job job2)
501 | (setf (emaci-job-status job2) 'running)
502 | (emaci/kill-job job2)
503 | (should (equal emaci-queue (list (cons "testqueue" (list job)))))
504 | (should (equal emaci-history (list (cons "testqueue" (list job2)))))
505 | (should (eq (emaci-job-status job2) 'canceled)))))
506 |
507 | (ert-deftest kill-canceled ()
508 | "Test killing a canceled job."
509 | (with-sandbox
510 | (let ((job (test-job))
511 | (job2 (test-job)))
512 | (emaci//queue-job job)
513 | (emaci//queue-job job2)
514 | (setf (emaci-job-status job2) 'canceled)
515 | (emaci//move-job-to-history job2)
516 | (emaci/kill-job job2)
517 | (should (equal emaci-queue (list (cons "testqueue" (list job)))))
518 | (should (equal emaci-history (list (cons "testqueue" (list job2)))))
519 | (should (eq (emaci-job-status job2) 'canceled)))))
520 |
521 | (ert-deftest compilation-finished-no-queue ()
522 | "Test compilation finished callback with empty queue."
523 | (with-sandbox
524 | (emaci//compilation-finished "some buffer" "test")))
525 |
526 | (defmacro with-double-running-queue (&rest body)
527 | "Execute body with a queue with two running jobs."
528 | `(with-sandbox
529 | (get-buffer-create "some buffer")
530 | (get-buffer-create "test buffer")
531 | (let* ((emaci-queue
532 | (list
533 | (cons
534 | "testqueue"
535 | (list
536 | (make-emaci-job
537 | :buildno 1 :status 'running :statusmsg nil
538 | :buffer "test buffer" :dir "~"
539 | :command "echo test1" :mode nil :highlight-regexp nil)
540 | (make-emaci-job
541 | :buildno 2 :status 'running :statusmsg nil
542 | :buffer "some buffer" :dir "~"
543 | :command "echo test2" :mode nil :highlight-regexp nil)))))
544 | (emaci--buffer-job-alist
545 | (list (cons (get-buffer-create "some buffer") (cadr (assoc "testqueue" emaci-queue))))))
546 | ,@body)))
547 |
548 | (ert-deftest compilation-finished-cb ()
549 | "Test compilation finished callback with empty queue."
550 | (with-double-running-queue
551 | (let ((job-finished-called nil))
552 | (with-advice
553 | (emaci//job-finished
554 | (lambda (orig-fun job status statusmsg)
555 | (setq job-finished-called t)
556 | (should (eq job (cadr (assoc "testqueue" emaci-queue))))
557 | (should (eq status 'finished))
558 | (should (equal statusmsg "test"))))
559 | (emaci//compilation-finished (get-buffer-create "some buffer") "test"))
560 | (should job-finished-called))))
561 |
562 | (ert-deftest job-finished ()
563 | "Test job finished"
564 | (with-sandbox
565 | (let* ((emaci-history (list (cons "testqueue" (list "dummy"))))
566 | (job (test-job))
567 | (emaci-queue (list (cons "testqueue" (list job)))))
568 | (emaci//job-finished job 'finished "finished\n")
569 | (should (eq (emaci-job-status job) 'finished))
570 | (should (equal (emaci-job-statusmsg job) "finished\n"))
571 | (should (equal emaci-history (list (cons "testqueue" (list "dummy" job)))))
572 | (should (equal emaci-queue (list (cons "testqueue" nil)))))))
573 |
574 | (ert-deftest job-finished-default ()
575 | "Test job finished with default job"
576 | (with-sandbox
577 | (let* ((emaci-history (list (cons "*default*" (list "dummy"))))
578 | (job (default-test-job))
579 | (emaci-queue (list (cons "*default*" (list job)))))
580 | (emaci//job-finished job 'finished "finished\n")
581 | (should (eq (emaci-job-status job) 'finished))
582 | (should (equal (emaci-job-statusmsg job) "finished\n"))
583 | (should (equal emaci-history (list (cons "*default*" (list "dummy" job)))))
584 | (should (equal emaci-queue (list (cons "*default*" nil)))))))
585 |
586 | (ert-deftest execute-next-empty-queue ()
587 | "Test execute-next with empty queue."
588 | (with-sandbox
589 | (emaci/execute-next "testqueue")))
590 |
591 | (ert-deftest execute-next-empty-default-queue ()
592 | "Test execute-next with empty default queue."
593 | (with-sandbox
594 | (emaci/execute-next)))
595 |
596 | (ert-deftest execute-next-running ()
597 | "Test execute-next with running job."
598 | (with-sandbox
599 | (let ((job (test-job)))
600 | (setf (emaci-job-status job) 'running)
601 | (setq emaci-queue (list (cons "testqueue" (list job))))
602 | (should (eq (cdr (should-error (emaci/execute-next "testqueue")
603 | :type 'emaci-error-job-running))
604 | job)))))
605 |
606 | (ert-deftest execute-next-running-default ()
607 | "Test execute-next with running job and default queue."
608 | (with-sandbox
609 | (let ((job (default-test-job)))
610 | (setf (emaci-job-status job) 'running)
611 | (setq emaci-queue (list (cons "*default*" (list job))))
612 | (should (eq (cdr (should-error (emaci/execute-next)
613 | :type 'emaci-error-job-running))
614 | job)))))
615 |
616 | (ert-deftest execute-next-queued ()
617 | "Test execute-next with queued job."
618 | (with-sandbox
619 | (let (emaci//execute-called-p)
620 | (with-advice
621 | (emaci//execute
622 | (lambda (orig-fun job) (assert-job job "testqueue" 1 'queued nil nil nil nil nil
623 | nil "~" "echo Tis but a scratch" 'comint-mode "$.*^")
624 | (setq emaci//execute-called-p t)))
625 | (let ((job (test-job)))
626 | (setq emaci-queue (list (cons "testqueue" (list job))))
627 | (emaci/execute-next "testqueue")
628 | (should emaci//execute-called-p))))))
629 |
630 | (ert-deftest create-buffer-name ()
631 | "Test creating buffer names"
632 | (with-sandbox
633 | (should (equal (emaci//create-buffer-name (test-job)) "testqueue: Build #1"))))
634 |
635 | (ert-deftest create-buffer ()
636 | "Test creating buffer names"
637 | (with-sandbox
638 | (let ((job (test-job)))
639 | (should (equal (emaci//create-buffer job) (get-buffer "testqueue: Build #1")))
640 | (should (get-buffer "testqueue: Build #1"))
641 | (should
642 | (equal
643 | emaci--buffer-job-alist
644 | (list (cons (get-buffer "testqueue: Build #1") job)))))))
645 |
646 | (defun assert-execute-job ()
647 | (assert-job
648 | (cadr (assoc "testqueue" emaci-history)) "testqueue" 1 'finished "finished\n"
649 | 0 nil nil nil "testqueue: Build #1" "~" "echo Tis but a scratch" 'comint-mode "$.*^"))
650 |
651 | (ert-deftest-async
652 | execute (assert-execute-job)
653 | (let ((job (test-job)))
654 | (setq emaci-queue (list (cons "testqueue" (list job))))
655 | (emaci//execute job)
656 | (assert-job
657 | (cadr (assoc "testqueue" emaci-queue)) "testqueue" 1 'running
658 | nil nil nil nil nil "testqueue: Build #1"
659 | "~" "echo Tis but a scratch" 'comint-mode "$.*^")))
660 |
661 | (defun assert-log ()
662 | (let* ((job (cadr (assoc "testqueue" emaci-history)))
663 | (buffer (get-buffer (emaci-job-buffer job)))
664 | (output (with-current-buffer buffer (buffer-string)))
665 | (fullpath (emaci//get-log-filepath job)))
666 | (should (equal output
667 | (with-temp-buffer
668 | (insert-file-contents (emaci//get-log-filepath job))
669 | (buffer-string))))))
670 |
671 | (ert-deftest-async
672 | log (assert-log)
673 | (let ((job (test-job)))
674 | (setq emaci-queue (list (cons "testqueue" (list job))))
675 | (emaci//execute job)))
676 |
677 | (ert-deftest peristent ()
678 | "Test persistent variables."
679 | (with-sandbox
680 | (let ((job (test-job))
681 | (job2 (test-job)))
682 | (emaci//queue-job job)
683 | (emaci//queue-job job2)
684 | (emaci//move-job-to-history job2)
685 | (emaci//save-vars)
686 | (let ((old-history emaci-history)
687 | (old-queue emaci-queue)
688 | (old-build-counter emaci--build-counter))
689 | (setq emaci-history nil
690 | emaci-queue nil
691 | emaci--build-counter nil)
692 | (emaci/load-vars)
693 | (should (equal (type-of emaci-queue) (type-of old-queue)))
694 | (should (equal emaci-queue old-queue))
695 | (should (equal emaci-history old-history))
696 | (should (equal emaci--build-counter old-build-counter))))))
697 |
698 | (ert-deftest load-empty ()
699 | "Test loading non-existent variables."
700 | (with-sandbox
701 | (let ((emaci-save-dir "/NONEXISTENTDIRECTORY/")
702 | (emaci-queue t))
703 | (emaci/load-vars)
704 | (should emaci-queue))))
705 |
706 | (ert-deftest get-stashes ()
707 | (should (equal (emaci//stashes emaci-test-repo) emaci-stashes)))
708 |
709 | (ert-deftest get-stashes-no-repo ()
710 | (should-not (emaci//stashes "/")))
711 |
712 | (ert-deftest get-stashes-no-stashes ()
713 | (should-not (emaci//stashes emaci-test-empty-repo)))
714 |
715 | (ert-deftest get-current-commit ()
716 | (should (equal (emaci//current-commit emaci-test-repo) emaci-commit-1)))
717 |
718 | (ert-deftest get-current-commit-no-repo ()
719 | (should-not (emaci//current-commit "/")))
720 |
721 | (ert-deftest get-current-commit-empty-repo ()
722 | (should (equal (emaci//current-commit emaci-test-empty-repo) "master")))
723 |
724 | (ert-deftest get-branches ()
725 | (should (equal (emaci//branches emaci-test-repo) (list "master" "branch1"))))
726 |
727 | (ert-deftest get-branches-no-repo ()
728 | (should-not (emaci//branches "/")))
729 |
730 | (ert-deftest get-branches-empty-repo ()
731 | (should-not (emaci//branches emaci-test-empty-repo)))
732 |
733 | (ert-deftest switch-branch ()
734 | (emaci//switch-to-branch "branch1" emaci-test-repo)
735 | (let ((branch (car (emaci//branches emaci-test-repo))))
736 | (emaci//switch-to-branch "master" emaci-test-repo)
737 | (should (equal branch "branch1"))))
738 |
739 | (ert-deftest switch-branch-no-repo ()
740 | (emaci//switch-to-branch "branch1" "/")
741 | (should-not (emaci//branches "/")))
742 |
743 | (ert-deftest switch-branch-commit ()
744 | (emaci//switch-to-branch "branch1" emaci-test-repo)
745 | (emaci//switch-to-branch emaci-commit-1 emaci-test-repo)
746 | (let ((commit (emaci//current-commit emaci-test-repo)))
747 | (emaci//switch-to-branch "master" emaci-test-repo)
748 | (should (equal commit emaci-commit-1))))
749 |
750 | (ert-deftest oldref-new ()
751 | (with-sandbox
752 | (let ((job (emaci//new-job nil emaci-test-repo "exit 0" nil nil t nil)))
753 | (should-not (emaci-job-oldref job)))))
754 |
755 | (defun assert-oldref ()
756 | (assert-job
757 | (cadr (assoc "*default*" emaci-history))
758 | "*default*" 1 'finished "finished\n" 0 "master" nil nil
759 | "*default*: Build #1" emaci-test-repo "git checkout branch1" nil nil))
760 |
761 | (ert-deftest-async
762 | oldref-executed (assert-oldref)
763 | (emaci//schedule nil emaci-test-repo "git checkout branch1"))
764 |
765 | (defun assert-master-branch ()
766 | (assert-job
767 | (cadr (assoc "*default*" emaci-history))
768 | "*default*" 1 'finished "finished\n" 0 "master" nil nil
769 | "*default*: Build #1" emaci-test-repo "git checkout branch1" nil nil)
770 | (should (equal (emaci//current-commit emaci-test-repo) emaci-commit-1)))
771 |
772 | (ert-deftest-async
773 | switch-branch-back (assert-master-branch)
774 | (emaci//schedule nil emaci-test-repo "git checkout branch1"))
775 |
776 | (defun assert-branch-before-execute ()
777 | (assert-job
778 | (cadr (assoc "*default*" emaci-history))
779 | "*default*" 1 'finished "finished\n" 0 "master" "branch1" nil
780 | "*default*: Build #1" emaci-test-repo "cat file3.txt" nil nil)
781 | (should (equal (emaci//current-commit emaci-test-repo) "master")))
782 |
783 | (ert-deftest-async
784 | switch-branch-before-execute (assert-branch-before-execute)
785 | (should (equal (emaci//current-commit emaci-test-repo) "master"))
786 | (emaci//schedule nil emaci-test-repo "cat file3.txt" "branch1")
787 | (should (equal (emaci//current-commit emaci-test-repo) "branch1")))
788 |
789 | (defun assert-stash-before-execute ()
790 | (assert-job
791 | (cadr (assoc "*default*" emaci-history))
792 | "*default*" 1 'finished "finished\n" 0 "master" "branch1" emaci-stashes
793 | "*default*: Build #1" emaci-test-repo "cat file3.txt || exit 0" nil nil)
794 | (should (equal (emaci//current-commit emaci-test-repo) "master"))
795 | (should-not (file-exists-p (concat emaci-test-repo "file3.txt"))))
796 |
797 | (ert-deftest-async
798 | stash-apply (assert-stash-before-execute)
799 | (should-not (file-exists-p (concat emaci-test-repo "file3.txt")))
800 | (emaci//schedule nil emaci-test-repo "cat file3.txt || exit 0" "branch1" emaci-stashes)
801 | (should-not (file-exists-p (concat emaci-test-repo "file3.txt"))))
802 |
803 | ;;; test-emaci.el ends here
804 |
--------------------------------------------------------------------------------