├── .VERSION_PREFIX
├── .circleci
└── config.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE.txt
├── README.md
├── bb.edn
├── bin
├── kaocha
└── proj
├── deps.edn
├── dev
└── user.clj
├── pom.xml
├── src
└── lambdaisland
│ └── ansi.cljc
├── test
└── lambdaisland
│ ├── ansi
│ └── cljs_test_runner.clj
│ └── ansi_test.cljc
└── tests.edn
/.VERSION_PREFIX:
--------------------------------------------------------------------------------
1 | 0.7
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2.1
2 |
3 | orbs:
4 | kaocha: lambdaisland/kaocha@0.0.2
5 | clojure: lambdaisland/clojure@0.0.6
6 |
7 | commands:
8 | checkout_and_run:
9 | parameters:
10 | clojure_version:
11 | type: string
12 | steps:
13 | - checkout
14 | - clojure/with_cache:
15 | steps:
16 | - kaocha/execute:
17 | args: "--reporter documentation --plugin cloverage --codecov"
18 | clojure_version: << parameters.clojure_version >>
19 | - kaocha/upload_codecov
20 |
21 | jobs:
22 | java-15-clojure-1_10:
23 | executor: clojure/openjdk15
24 | steps: [{checkout_and_run: {clojure_version: "1.10.3"}}]
25 |
26 | java-11-clojure-1_10:
27 | executor: clojure/openjdk11
28 | steps: [{checkout_and_run: {clojure_version: "1.10.3"}}]
29 |
30 | java-9-clojure-1_10:
31 | executor: clojure/openjdk9
32 | steps: [{checkout_and_run: {clojure_version: "1.10.3"}}]
33 |
34 | java-9-clojure-1_9:
35 | executor: clojure/openjdk9
36 | steps: [{checkout_and_run: {clojure_version: "1.9.0"}}]
37 |
38 | java-8-clojure-1_10:
39 | executor: clojure/openjdk8
40 | steps: [{checkout_and_run: {clojure_version: "1.10.3"}}]
41 |
42 | java-8-clojure-1_9:
43 | executor: clojure/openjdk8
44 | steps: [{checkout_and_run: {clojure_version: "1.9.0"}}]
45 |
46 | workflows:
47 | kaocha_test:
48 | jobs:
49 | - java-15-clojure-1_10
50 | - java-11-clojure-1_10
51 | - java-9-clojure-1_10
52 | - java-9-clojure-1_9
53 | - java-8-clojure-1_10
54 | - java-8-clojure-1_9
55 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .cpcache
2 | .nrepl-port
3 | target
4 | out
5 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Unreleased
2 |
3 | ## Added
4 |
5 | ## Fixed
6 |
7 | ## Changed
8 |
9 | # 0.2.37 (2022-04-27 / 60e897a)
10 |
11 | ## Added
12 |
13 | - Add support for parsing a cursor location message (as sent from terminal to process), i.e. "\e[row;col;H"
14 |
15 | ## Fixed
16 |
17 | - Fix the `with-color-scheme` macro
18 | - Make it impossible to use background CSI code 48
19 |
20 | # v0.1.6 - 2018-08-14
21 |
22 | - Fix a bug where specific character sequences caused regular bits of string to
23 | be treated as escape sequences, essentially swallowing bits of input.
24 |
25 | # v0.1.5 - 2018-07-19
26 |
27 | - Support \e[m as a reset code, together with \e[0m.
28 |
29 | # v0.1.4 - 2018-06-13
30 |
31 | - More optimizations, get rid of the expensive csi regex.
32 |
33 | # v0.1.3 - 2018-06-12
34 |
35 | - Minor optimizations for ClojureScript
36 |
37 | # v0.1.2 - 2018-06-12
38 |
39 | - Added optimizations. Calling token-stream on plain text without escape
40 | sequences will now short circuit, leading to a significant speedup for the
41 | cases where most input is plain text.
42 |
43 | # v0.1.1 - 2018-05-30
44 |
45 | Initial release
46 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Mozilla Public License Version 2.0
2 | ==================================
3 |
4 | 1. Definitions
5 | --------------
6 |
7 | 1.1. "Contributor"
8 | means each individual or legal entity that creates, contributes to
9 | the creation of, or owns Covered Software.
10 |
11 | 1.2. "Contributor Version"
12 | means the combination of the Contributions of others (if any) used
13 | by a Contributor and that particular Contributor's Contribution.
14 |
15 | 1.3. "Contribution"
16 | means Covered Software of a particular Contributor.
17 |
18 | 1.4. "Covered Software"
19 | means Source Code Form to which the initial Contributor has attached
20 | the notice in Exhibit A, the Executable Form of such Source Code
21 | Form, and Modifications of such Source Code Form, in each case
22 | including portions thereof.
23 |
24 | 1.5. "Incompatible With Secondary Licenses"
25 | means
26 |
27 | (a) that the initial Contributor has attached the notice described
28 | in Exhibit B to the Covered Software; or
29 |
30 | (b) that the Covered Software was made available under the terms of
31 | version 1.1 or earlier of the License, but not also under the
32 | terms of a Secondary License.
33 |
34 | 1.6. "Executable Form"
35 | means any form of the work other than Source Code Form.
36 |
37 | 1.7. "Larger Work"
38 | means a work that combines Covered Software with other material, in
39 | a separate file or files, that is not Covered Software.
40 |
41 | 1.8. "License"
42 | means this document.
43 |
44 | 1.9. "Licensable"
45 | means having the right to grant, to the maximum extent possible,
46 | whether at the time of the initial grant or subsequently, any and
47 | all of the rights conveyed by this License.
48 |
49 | 1.10. "Modifications"
50 | means any of the following:
51 |
52 | (a) any file in Source Code Form that results from an addition to,
53 | deletion from, or modification of the contents of Covered
54 | Software; or
55 |
56 | (b) any new file in Source Code Form that contains any Covered
57 | Software.
58 |
59 | 1.11. "Patent Claims" of a Contributor
60 | means any patent claim(s), including without limitation, method,
61 | process, and apparatus claims, in any patent Licensable by such
62 | Contributor that would be infringed, but for the grant of the
63 | License, by the making, using, selling, offering for sale, having
64 | made, import, or transfer of either its Contributions or its
65 | Contributor Version.
66 |
67 | 1.12. "Secondary License"
68 | means either the GNU General Public License, Version 2.0, the GNU
69 | Lesser General Public License, Version 2.1, the GNU Affero General
70 | Public License, Version 3.0, or any later versions of those
71 | licenses.
72 |
73 | 1.13. "Source Code Form"
74 | means the form of the work preferred for making modifications.
75 |
76 | 1.14. "You" (or "Your")
77 | means an individual or a legal entity exercising rights under this
78 | License. For legal entities, "You" includes any entity that
79 | controls, is controlled by, or is under common control with You. For
80 | purposes of this definition, "control" means (a) the power, direct
81 | or indirect, to cause the direction or management of such entity,
82 | whether by contract or otherwise, or (b) ownership of more than
83 | fifty percent (50%) of the outstanding shares or beneficial
84 | ownership of such entity.
85 |
86 | 2. License Grants and Conditions
87 | --------------------------------
88 |
89 | 2.1. Grants
90 |
91 | Each Contributor hereby grants You a world-wide, royalty-free,
92 | non-exclusive license:
93 |
94 | (a) under intellectual property rights (other than patent or trademark)
95 | Licensable by such Contributor to use, reproduce, make available,
96 | modify, display, perform, distribute, and otherwise exploit its
97 | Contributions, either on an unmodified basis, with Modifications, or
98 | as part of a Larger Work; and
99 |
100 | (b) under Patent Claims of such Contributor to make, use, sell, offer
101 | for sale, have made, import, and otherwise transfer either its
102 | Contributions or its Contributor Version.
103 |
104 | 2.2. Effective Date
105 |
106 | The licenses granted in Section 2.1 with respect to any Contribution
107 | become effective for each Contribution on the date the Contributor first
108 | distributes such Contribution.
109 |
110 | 2.3. Limitations on Grant Scope
111 |
112 | The licenses granted in this Section 2 are the only rights granted under
113 | this License. No additional rights or licenses will be implied from the
114 | distribution or licensing of Covered Software under this License.
115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a
116 | Contributor:
117 |
118 | (a) for any code that a Contributor has removed from Covered Software;
119 | or
120 |
121 | (b) for infringements caused by: (i) Your and any other third party's
122 | modifications of Covered Software, or (ii) the combination of its
123 | Contributions with other software (except as part of its Contributor
124 | Version); or
125 |
126 | (c) under Patent Claims infringed by Covered Software in the absence of
127 | its Contributions.
128 |
129 | This License does not grant any rights in the trademarks, service marks,
130 | or logos of any Contributor (except as may be necessary to comply with
131 | the notice requirements in Section 3.4).
132 |
133 | 2.4. Subsequent Licenses
134 |
135 | No Contributor makes additional grants as a result of Your choice to
136 | distribute the Covered Software under a subsequent version of this
137 | License (see Section 10.2) or under the terms of a Secondary License (if
138 | permitted under the terms of Section 3.3).
139 |
140 | 2.5. Representation
141 |
142 | Each Contributor represents that the Contributor believes its
143 | Contributions are its original creation(s) or it has sufficient rights
144 | to grant the rights to its Contributions conveyed by this License.
145 |
146 | 2.6. Fair Use
147 |
148 | This License is not intended to limit any rights You have under
149 | applicable copyright doctrines of fair use, fair dealing, or other
150 | equivalents.
151 |
152 | 2.7. Conditions
153 |
154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
155 | in Section 2.1.
156 |
157 | 3. Responsibilities
158 | -------------------
159 |
160 | 3.1. Distribution of Source Form
161 |
162 | All distribution of Covered Software in Source Code Form, including any
163 | Modifications that You create or to which You contribute, must be under
164 | the terms of this License. You must inform recipients that the Source
165 | Code Form of the Covered Software is governed by the terms of this
166 | License, and how they can obtain a copy of this License. You may not
167 | attempt to alter or restrict the recipients' rights in the Source Code
168 | Form.
169 |
170 | 3.2. Distribution of Executable Form
171 |
172 | If You distribute Covered Software in Executable Form then:
173 |
174 | (a) such Covered Software must also be made available in Source Code
175 | Form, as described in Section 3.1, and You must inform recipients of
176 | the Executable Form how they can obtain a copy of such Source Code
177 | Form by reasonable means in a timely manner, at a charge no more
178 | than the cost of distribution to the recipient; and
179 |
180 | (b) You may distribute such Executable Form under the terms of this
181 | License, or sublicense it under different terms, provided that the
182 | license for the Executable Form does not attempt to limit or alter
183 | the recipients' rights in the Source Code Form under this License.
184 |
185 | 3.3. Distribution of a Larger Work
186 |
187 | You may create and distribute a Larger Work under terms of Your choice,
188 | provided that You also comply with the requirements of this License for
189 | the Covered Software. If the Larger Work is a combination of Covered
190 | Software with a work governed by one or more Secondary Licenses, and the
191 | Covered Software is not Incompatible With Secondary Licenses, this
192 | License permits You to additionally distribute such Covered Software
193 | under the terms of such Secondary License(s), so that the recipient of
194 | the Larger Work may, at their option, further distribute the Covered
195 | Software under the terms of either this License or such Secondary
196 | License(s).
197 |
198 | 3.4. Notices
199 |
200 | You may not remove or alter the substance of any license notices
201 | (including copyright notices, patent notices, disclaimers of warranty,
202 | or limitations of liability) contained within the Source Code Form of
203 | the Covered Software, except that You may alter any license notices to
204 | the extent required to remedy known factual inaccuracies.
205 |
206 | 3.5. Application of Additional Terms
207 |
208 | You may choose to offer, and to charge a fee for, warranty, support,
209 | indemnity or liability obligations to one or more recipients of Covered
210 | Software. However, You may do so only on Your own behalf, and not on
211 | behalf of any Contributor. You must make it absolutely clear that any
212 | such warranty, support, indemnity, or liability obligation is offered by
213 | You alone, and You hereby agree to indemnify every Contributor for any
214 | liability incurred by such Contributor as a result of warranty, support,
215 | indemnity or liability terms You offer. You may include additional
216 | disclaimers of warranty and limitations of liability specific to any
217 | jurisdiction.
218 |
219 | 4. Inability to Comply Due to Statute or Regulation
220 | ---------------------------------------------------
221 |
222 | If it is impossible for You to comply with any of the terms of this
223 | License with respect to some or all of the Covered Software due to
224 | statute, judicial order, or regulation then You must: (a) comply with
225 | the terms of this License to the maximum extent possible; and (b)
226 | describe the limitations and the code they affect. Such description must
227 | be placed in a text file included with all distributions of the Covered
228 | Software under this License. Except to the extent prohibited by statute
229 | or regulation, such description must be sufficiently detailed for a
230 | recipient of ordinary skill to be able to understand it.
231 |
232 | 5. Termination
233 | --------------
234 |
235 | 5.1. The rights granted under this License will terminate automatically
236 | if You fail to comply with any of its terms. However, if You become
237 | compliant, then the rights granted under this License from a particular
238 | Contributor are reinstated (a) provisionally, unless and until such
239 | Contributor explicitly and finally terminates Your grants, and (b) on an
240 | ongoing basis, if such Contributor fails to notify You of the
241 | non-compliance by some reasonable means prior to 60 days after You have
242 | come back into compliance. Moreover, Your grants from a particular
243 | Contributor are reinstated on an ongoing basis if such Contributor
244 | notifies You of the non-compliance by some reasonable means, this is the
245 | first time You have received notice of non-compliance with this License
246 | from such Contributor, and You become compliant prior to 30 days after
247 | Your receipt of the notice.
248 |
249 | 5.2. If You initiate litigation against any entity by asserting a patent
250 | infringement claim (excluding declaratory judgment actions,
251 | counter-claims, and cross-claims) alleging that a Contributor Version
252 | directly or indirectly infringes any patent, then the rights granted to
253 | You by any and all Contributors for the Covered Software under Section
254 | 2.1 of this License shall terminate.
255 |
256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all
257 | end user license agreements (excluding distributors and resellers) which
258 | have been validly granted by You or Your distributors under this License
259 | prior to termination shall survive termination.
260 |
261 | ************************************************************************
262 | * *
263 | * 6. Disclaimer of Warranty *
264 | * ------------------------- *
265 | * *
266 | * Covered Software is provided under this License on an "as is" *
267 | * basis, without warranty of any kind, either expressed, implied, or *
268 | * statutory, including, without limitation, warranties that the *
269 | * Covered Software is free of defects, merchantable, fit for a *
270 | * particular purpose or non-infringing. The entire risk as to the *
271 | * quality and performance of the Covered Software is with You. *
272 | * Should any Covered Software prove defective in any respect, You *
273 | * (not any Contributor) assume the cost of any necessary servicing, *
274 | * repair, or correction. This disclaimer of warranty constitutes an *
275 | * essential part of this License. No use of any Covered Software is *
276 | * authorized under this License except under this disclaimer. *
277 | * *
278 | ************************************************************************
279 |
280 | ************************************************************************
281 | * *
282 | * 7. Limitation of Liability *
283 | * -------------------------- *
284 | * *
285 | * Under no circumstances and under no legal theory, whether tort *
286 | * (including negligence), contract, or otherwise, shall any *
287 | * Contributor, or anyone who distributes Covered Software as *
288 | * permitted above, be liable to You for any direct, indirect, *
289 | * special, incidental, or consequential damages of any character *
290 | * including, without limitation, damages for lost profits, loss of *
291 | * goodwill, work stoppage, computer failure or malfunction, or any *
292 | * and all other commercial damages or losses, even if such party *
293 | * shall have been informed of the possibility of such damages. This *
294 | * limitation of liability shall not apply to liability for death or *
295 | * personal injury resulting from such party's negligence to the *
296 | * extent applicable law prohibits such limitation. Some *
297 | * jurisdictions do not allow the exclusion or limitation of *
298 | * incidental or consequential damages, so this exclusion and *
299 | * limitation may not apply to You. *
300 | * *
301 | ************************************************************************
302 |
303 | 8. Litigation
304 | -------------
305 |
306 | Any litigation relating to this License may be brought only in the
307 | courts of a jurisdiction where the defendant maintains its principal
308 | place of business and such litigation shall be governed by laws of that
309 | jurisdiction, without reference to its conflict-of-law provisions.
310 | Nothing in this Section shall prevent a party's ability to bring
311 | cross-claims or counter-claims.
312 |
313 | 9. Miscellaneous
314 | ----------------
315 |
316 | This License represents the complete agreement concerning the subject
317 | matter hereof. If any provision of this License is held to be
318 | unenforceable, such provision shall be reformed only to the extent
319 | necessary to make it enforceable. Any law or regulation which provides
320 | that the language of a contract shall be construed against the drafter
321 | shall not be used to construe this License against a Contributor.
322 |
323 | 10. Versions of the License
324 | ---------------------------
325 |
326 | 10.1. New Versions
327 |
328 | Mozilla Foundation is the license steward. Except as provided in Section
329 | 10.3, no one other than the license steward has the right to modify or
330 | publish new versions of this License. Each version will be given a
331 | distinguishing version number.
332 |
333 | 10.2. Effect of New Versions
334 |
335 | You may distribute the Covered Software under the terms of the version
336 | of the License under which You originally received the Covered Software,
337 | or under the terms of any subsequent version published by the license
338 | steward.
339 |
340 | 10.3. Modified Versions
341 |
342 | If you create software not governed by this License, and you want to
343 | create a new license for such software, you may create and use a
344 | modified version of this License if you rename the license and remove
345 | any references to the name of the license steward (except to note that
346 | such modified license differs from this License).
347 |
348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary
349 | Licenses
350 |
351 | If You choose to distribute Source Code Form that is Incompatible With
352 | Secondary Licenses under the terms of this version of the License, the
353 | notice described in Exhibit B of this License must be attached.
354 |
355 | Exhibit A - Source Code Form License Notice
356 | -------------------------------------------
357 |
358 | This Source Code Form is subject to the terms of the Mozilla Public
359 | License, v. 2.0. If a copy of the MPL was not distributed with this
360 | file, You can obtain one at http://mozilla.org/MPL/2.0/.
361 |
362 | If it is not possible or desirable to put the notice in a particular
363 | file, then You may include the notice in a location (such as a LICENSE
364 | file in a relevant directory) where a recipient would be likely to look
365 | for such a notice.
366 |
367 | You may add additional accurate notices of copyright ownership.
368 |
369 | Exhibit B - "Incompatible With Secondary Licenses" Notice
370 | ---------------------------------------------------------
371 |
372 | This Source Code Form is "Incompatible With Secondary Licenses", as
373 | defined by the Mozilla Public License, v. 2.0.
374 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # lambdaisland/ansi
2 |
3 |
4 | [](https://circleci.com/gh/lambdaisland/ansi) [](https://cljdoc.org/d/lambdaisland/ansi) [](https://clojars.org/lambdaisland/ansi) [](https://codecov.io/gh/lambdaisland/ansi)
5 |
6 |
7 | Parse ANSI color codes, optionally convert to Hiccup.
8 |
9 | ## Features
10 |
11 | - Clojure and ClojureScript support (cljc)
12 | - parses color codes and bold, including 8-bit and 24-bit (rgb) colors
13 | - Fine-grained building blocks, convert to Hiccup or a format of your choice
14 |
15 | ## Usage
16 |
17 | Colors in your terminal work by embedding "escape codes" in the text. This
18 | library contains utilities for parsing and transforming these escape codes.
19 |
20 | Terminals are stateful, you can set a property like foreground, background, or
21 | bold, and it will stay that way until it gets changes or unset (reset).
22 |
23 | The starting point for dealing with a textual stream is `token-stream`. It takes
24 | a string as input, and returns a sequence of "tokens", either plain text, or a
25 | map of properties that are being set at that point in the stream.
26 |
27 | ``` clojure
28 | (require '[lambdaisland.ansi :as ansi])
29 |
30 | (ansi/token-stream "Hello,\033[1;30;45m world!")
31 | ;;=> ["Hello," {:bold true, :foreground [:rgb 0 0 0], :background [:rgb 205 0 205]} " world!"]
32 | ```
33 |
34 | To convert this to a different format, you need to know which styles apply to
35 | which piece of text, this is what the `apply-props` stateful transducer is for.
36 | It keeps track of the "terminal state", and returns pairs of style information
37 | and text.
38 |
39 | ``` clojure
40 | (def text "\033[1m here \033[45m we \033[31m go! \033[0m done.")
41 |
42 | (sequence ansi/apply-props (ansi/token-stream text))
43 | ;;=>
44 | ([{:bold true} " here "]
45 | [{:bold true
46 | :background [:rgb 205 0 205]} " we "]
47 | [{:bold true
48 | :background [:rgb 205 0 205]
49 | :foreground [:rgb 205 0 0]} " go! "]
50 | [{} " done."])
51 | ```
52 |
53 | Now you have all the information available to convert this into Hiccup, you can
54 | convert a single one of these pairs with `chunk->hiccup`.
55 |
56 | ``` clojure
57 | (ansi/chunk->hiccup [{:bold true, :foreground [:rgb 100 200 0]} "so far..."])
58 | ;;=> [:span {:style {:color "rgb(100,200,0)", :font-weight "bold"}} "so far..."]
59 | ```
60 |
61 | There's a convenience function to do this in one go, `text->hiccup`
62 |
63 |
64 | ``` clojure
65 | (ansi/text->hiccup text)
66 | ;;=>
67 | ([:span {:style {:font-weight "bold"}} " here "]
68 | [:span {:style {:background-color "rgb(205,0,205)", :font-weight "bold"}}
69 | " we "]
70 | [:span {:style {:color "rgb(205,0,0)",
71 | :background-color "rgb(205,0,205)",
72 | :font-weight "bold"}}
73 | " go! "]
74 | [:span {} " done."])
75 | ```
76 |
77 | If you deal with streaming text, then you need to incrementally apply these
78 | state changes, carrying over the old state whenever you receive more input.
79 |
80 | In this case the `hiccup-xform` might be useful.
81 |
82 | ``` clojure
83 | (require '[clojure.core.async :as async])
84 |
85 | (def channel (chan 1 ansi/hiccup-xform))
86 |
87 | (async/go-loop [hiccup-el (async/! channel "\033[1mhello,\033[35mworld!"))
91 | (async/go (async/>! channel " life is \033[0mgreat!"))
92 | ```
93 |
94 |
95 | ## License
96 |
97 | © Arne Brasseur 2018
98 | Available under the terms of the Mozilla Public License Version 2.0, see LICENSE.txt
99 |
--------------------------------------------------------------------------------
/bb.edn:
--------------------------------------------------------------------------------
1 | {:deps
2 | {lambdaisland/open-source {:git/url "https://github.com/lambdaisland/open-source"
3 | :sha "8b3ef2e1fec97493ad89a39c962cca323c76ff8d"}}}
4 |
--------------------------------------------------------------------------------
/bin/kaocha:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | exec clojure -A:test -M -m kaocha.runner "$@"
4 |
--------------------------------------------------------------------------------
/bin/proj:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bb
2 |
3 | (ns proj (:require [lioss.main :as lioss]
4 | [lioss.subshell :as subshell]
5 | [clojure.java.io :as io]
6 | [clojure.string :as str]
7 | [lioss.version :as version]))
8 |
9 | (lioss/main
10 | {:license :mpl
11 | :inception-year 2018
12 | :description "Parse ANSI color escape sequences to Hiccup syntax"
13 | :group-id "lambdaisland"
14 | :aliases-as-optional-deps []})
15 |
16 | ;; Local Variables:
17 | ;; mode:clojure
18 | ;; End:
19 |
--------------------------------------------------------------------------------
/deps.edn:
--------------------------------------------------------------------------------
1 | {:deps
2 | {org.clojure/clojure {:mvn/version "1.11.1"}}
3 |
4 | :paths ["src"]
5 |
6 | :aliases
7 | {:test
8 | {:extra-deps {lambdaisland/kaocha {:mvn/version "1.60.977" }}}
9 |
10 | :dev
11 | {}}}
12 |
--------------------------------------------------------------------------------
/dev/user.clj:
--------------------------------------------------------------------------------
1 | (ns user)
2 |
3 | (defn add-dependency [dep-vec]
4 | (require 'cemerick.pomegranate)
5 | ((resolve 'cemerick.pomegranate/add-dependencies)
6 | :coordinates [dep-vec]
7 | :repositories (merge @(resolve 'cemerick.pomegranate.aether/maven-central)
8 | {"clojars" "https://clojars.org/repo"})))
9 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 | lambdaisland
5 | ansi
6 | 0.2.37
7 | ansi
8 | Parse ANSI color escape sequences to Hiccup syntax
9 | https://github.com/lambdaisland/ansi
10 | 2018
11 |
12 | Lambda Island
13 | https://lambdaisland.com
14 |
15 |
16 | UTF-8
17 |
18 |
19 |
20 | MPL-2.0
21 | https://www.mozilla.org/media/MPL/2.0/index.txt
22 |
23 |
24 |
25 | https://github.com/lambdaisland/ansi
26 | scm:git:git://github.com/lambdaisland/ansi.git
27 | scm:git:ssh://git@github.com/lambdaisland/ansi.git
28 | 85b322f242cdca8068c13a06371baea22f4f3b85
29 |
30 |
31 |
32 | org.clojure
33 | clojure
34 | 1.11.1
35 |
36 |
37 |
38 | src
39 |
40 |
41 | src
42 |
43 |
44 |
45 |
46 | org.apache.maven.plugins
47 | maven-compiler-plugin
48 | 3.8.1
49 |
50 | 1.8
51 | 1.8
52 |
53 |
54 |
55 | org.apache.maven.plugins
56 | maven-jar-plugin
57 | 3.2.0
58 |
59 |
60 |
61 | 85b322f242cdca8068c13a06371baea22f4f3b85
62 |
63 |
64 |
65 |
66 |
67 | org.apache.maven.plugins
68 | maven-gpg-plugin
69 | 1.6
70 |
71 |
72 | sign-artifacts
73 | verify
74 |
75 | sign
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | clojars
85 | https://repo.clojars.org/
86 |
87 |
88 |
89 |
90 | clojars
91 | Clojars repository
92 | https://clojars.org/repo
93 |
94 |
95 |
--------------------------------------------------------------------------------
/src/lambdaisland/ansi.cljc:
--------------------------------------------------------------------------------
1 | (ns lambdaisland.ansi
2 | (:require [clojure.string :as str]))
3 |
4 |
5 | (def ^String ESC
6 | "ASCII escape character (codepoint 27, hex 1b, octal 33)."
7 | #?(:clj "\u001b["
8 | :cljs "\033["))
9 |
10 | (def =i #?(:clj =
11 | :cljs identical?))
12 |
13 | (defn str-length
14 | "Fast string length"
15 | [s]
16 | #?(:clj (.length ^String s)
17 | :cljs (.-length s)))
18 |
19 |
20 | ;; | Name | FG | BG | VGA | CMD | Terminal.app | PuTTY | mIRC | xterm | Ubuntu |
21 | ;; |----------------+----+-----+-------------+-------------+--------------+-------------+-------------+-------------+-------------|
22 | ;; | Black | 30 | 40 | 0,0,0 | 1,1,1 | 0,0,0 | 0,0,0 | 0,0,0 | 0,0,0 | 0,0,0 |
23 | ;; | Red | 31 | 41 | 170,0,0 | 128,0,0 | 194,54,33 | 187,0,0 | 127,0,0 | 205,0,0 | 222,56,43 |
24 | ;; | Green | 32 | 42 | 0,170,0 | 0,128,0 | 37,188,36 | 0,187,0 | 0,147,0 | 0,205,0 | 57,181,74 |
25 | ;; | Yellow | 33 | 43 | 170,85,0 | 128,128,0 | 173,173,39 | 187,187,0 | 252,127,0 | 205,205,0 | 255,199,6 |
26 | ;; | Blue | 34 | 44 | 0,0,170 | 0,0,128 | 73,46,225 | 0,0,187 | 0,0,127 | 0,0,238 | 0,111,184 |
27 | ;; | Magenta | 35 | 45 | 170,0,170 | 128,0,128 | 211,56,211 | 187,0,187 | 156,0,156 | 205,0,205 | 118,38,113 |
28 | ;; | Cyan | 36 | 46 | 0,170,170 | 0,128,128 | 51,187,200 | 0,187,187 | 0,147,147 | 0,205,205 | 44,181,233 |
29 | ;; | White | 37 | 47 | 170,170,170 | 192,192,192 | 203,204,205 | 187,187,187 | 210,210,210 | 229,229,229 | 204,204,204 |
30 | ;; | Bright Black | 90 | 100 | 85,85,85 | 128,128,128 | 129,131,131 | 85,85,85 | 127,127,127 | 127,127,127 | 128,128,128 |
31 | ;; | Bright Red | 91 | 101 | 255,85,85 | 255,0,0 | 252,57,31 | 255,85,85 | 255,0,0 | 255,0,0 | 255,0,0 |
32 | ;; | Bright Green | 92 | 102 | 85,255,85 | 0,255,0 | 49,231,34 | 85,255,85 | 0,252,0 | 0,255,0 | 0,255,0 |
33 | ;; | Bright Yellow | 93 | 103 | 255,255,85 | 255,255,0 | 234,236,35 | 255,255,85 | 255,255,0 | 255,255,0 | 255,255,0 |
34 | ;; | Bright Blue | 94 | 104 | 85,85,255 | 0,0,255 | 88,51,255 | 85,85,255 | 0,0,252 | 92,92,255 | 0,0,255 |
35 | ;; | Bright Magenta | 95 | 105 | 255,85,255 | 255,0,255 | 249,53,248 | 255,85,255 | 255,0,255 | 255,0,255 | 255,0,255 |
36 | ;; | Bright Cyan | 96 | 106 | 85,255,255 | 0,255,255 | 20,240,240 | 85,255,255 | 0,255,255 | 0,255,255 | 0,255,255 |
37 | ;; | Bright White | 97 | 107 | 255,255,255 | 255,255,255 | 233,235,235 | 255,255,255 | 255,255,255 | 255,255,255 | 255,255,255 |
38 | (def color-schemes
39 | "Color schemes used in popular applications."
40 | (->> [[:vga :cmd :osx :putty :mirc :xterm :ubuntu ]
41 | [[ 0 0 0 ][ 1 1 1 ][ 0 0 0 ][ 0 0 0 ][ 0 0 0 ][ 0 0 0 ][ 0 0 0 ]]
42 | [[ 170 0 0 ][ 128 0 0 ][ 194 54 33 ][ 187 0 0 ][ 127 0 0 ][ 205 0 0 ][ 222 56 43 ]]
43 | [[ 0 170 0 ][ 0 128 0 ][ 37 188 36 ][ 0 187 0 ][ 0 147 0 ][ 0 205 0 ][ 57 181 74 ]]
44 | [[ 170 85 0 ][ 128 128 0 ][ 173 173 39 ][ 187 187 0 ][ 252 127 0 ][ 205 205 0 ][ 255 199 6 ]]
45 | [[ 0 0 170 ][ 0 0 128 ][ 73 46 225 ][ 0 0 187 ][ 0 0 127 ][ 0 0 238 ][ 0 111 184 ]]
46 | [[ 170 0 170 ][ 128 0 128 ][ 211 56 211 ][ 187 0 187 ][ 156 0 156 ][ 205 0 205 ][ 118 38 113 ]]
47 | [[ 0 170 170 ][ 0 128 128 ][ 51 187 200 ][ 0 187 187 ][ 0 147 147 ][ 0 205 205 ][ 44 181 233 ]]
48 | [[ 170 170 170 ][ 192 192 192 ][ 203 204 205 ][ 187 187 187 ][ 210 210 210 ][ 229 229 229 ][ 204 204 204 ]]
49 |
50 | [[ 85 85 85 ][ 128 128 128 ][ 129 131 131 ][ 85 85 85 ][ 127 127 127 ][ 127 127 127 ][ 128 128 128 ]]
51 | [[ 255 85 85 ][ 255 0 0 ][ 252 57 31 ][ 255 85 85 ][ 255 0 0 ][ 255 0 0 ][ 255 0 0 ]]
52 | [[ 85 255 85 ][ 0 255 0 ][ 49 231 34 ][ 85 255 85 ][ 0 252 0 ][ 0 255 0 ][ 0 255 0 ]]
53 | [[ 255 255 85 ][ 255 255 0 ][ 234 236 35 ][ 255 255 85 ][ 255 255 0 ][ 255 255 0 ][ 255 255 0 ]]
54 | [[ 85 85 255 ][ 0 0 255 ][ 88 51 255 ][ 85 85 255 ][ 0 0 252 ][ 92 92 255 ][ 0 0 255 ]]
55 | [[ 255 85 255 ][ 255 0 255 ][ 249 53 248 ][ 255 85 255 ][ 255 0 255 ][ 255 0 255 ][ 255 0 255 ]]
56 | [[ 85 255 255 ][ 0 255 255 ][ 20 240 240 ][ 85 255 255 ][ 0 255 255 ][ 0 255 255 ][ 0 255 255 ]]
57 | [[ 255 255 255 ][ 255 255 255 ][ 233 235 235 ][ 255 255 255 ][ 255 255 255 ][ 255 255 255 ][ 255 255 255 ]]]
58 | (apply mapv vector)
59 | (into {} (map (fn [[x & xs]] [x (vec xs)])))))
60 |
61 | (def ^:dynamic *color-scheme*
62 | "Color scheme currently in use during parsing."
63 | (:xterm color-schemes))
64 |
65 | (defmacro with-color-scheme
66 | "Execute code with the given color scheme active, name must be one
67 | of :vga :cmd :osx :putty :mirc :xterm :ubuntu. For finer control bind to
68 | *color-scheme* directly."
69 | [name & body]
70 | `(binding [*color-scheme* (get color-schemes ~name)]
71 | ~@body))
72 |
73 | (defn get-color [n]
74 | (into [:rgb] (get *color-scheme* n)))
75 |
76 | (defn parse-int [s]
77 | #?(:clj (Integer/parseInt s)
78 | :cljs (js/parseInt s 10)))
79 |
80 | (def reset-attrs {:foreground nil
81 | :background nil
82 | :bold nil})
83 |
84 | (defn code->attrs
85 | "Given a CSI code, return a map of properties it sets. A value of `nil` means
86 | the property gets unset."
87 | [code]
88 | (cond
89 | (=i 0 code) reset-attrs
90 | (=i 1 code) {:bold true}
91 | (<= 30 code 37) {:foreground (get-color (- code 30))}
92 | (=i 39 code) {:foreground nil}
93 | (<= 40 code 47) {:background (get-color (- code 40))}
94 | (=i 49 code) {:background nil}
95 | (<= 90 code 99) {:foreground (get-color (+ 8 (- code 90)))}
96 | (<= 100 code 109) {:background (get-color (+ 8 (- code 40)))}))
97 |
98 | ;; 0- 7: standard colors (as in ESC [ 30–37 m)
99 | ;; 8- 15: high intensity colors (as in ESC [ 90–97 m)
100 | ;; 16-231: 6 × 6 × 6 cube (216 colors): 16 + 36 × r + 6 × g + b (0 ≤ r, g, b ≤ 5)
101 | ;; 232-255: grayscale from black to white in 24 steps
102 | (defn color-8-bit
103 | "Parse a \"8-bit\" color, given the code that follows on ESC[38;5;m."
104 | [code]
105 | (cond
106 | (<= 0 code 7) [(get-color code) false]
107 | (<= 8 code 15) [(get-color (- code 8)) true]
108 | (<= 16 code 231) (let [code (- code 16)
109 | blue (mod code 6)
110 | green (mod (/ (- code blue) 6) 6)
111 | red (/ (- code blue (* 6 green)) 36)
112 | color-values [0x00 0x5f 0x87 0xaf 0xd7 0xff]]
113 | [(into [:rgb] (map color-values) [red green blue])
114 | false])
115 | (<= 232 code 255) (let [x (+ 8 (* 10 (- code 232)))]
116 | [[:rgb x x x] false])))
117 |
118 | (defn color-24-bit [[r g b]]
119 | [:rgb r g b])
120 |
121 | (defn parse-color
122 | "Handle CSI code 38 and 48, used to specify 8 or 24 bit colors. This may consume
123 | up to 5 codes in total (ESC [38;2;r;g;bm). Returns the map of properties that
124 | get set, and the remaining, unconsumed codes."
125 | [fg-or-bg [colorspace & more]]
126 | (let [type (case fg-or-bg 38 :foreground 48 :background)]
127 | (case colorspace
128 | 5 (let [[color bold?] (color-8-bit (first more))]
129 | [(merge {type color} (if bold? {:bold true}))
130 | (next more)])
131 | 2 [{type (color-24-bit (take 3 more))}
132 | (nthnext more 3)])))
133 |
134 | (defn str-split
135 | "Like clojure.string/split, but uses a single character instead of a regex,
136 | allowing for faster operation."
137 | [^String s ^String sep]
138 | (loop [res []
139 | start 0
140 | end (.indexOf s sep)]
141 | (if (=i end -1)
142 | (conj res (.substring s start))
143 | (recur (conj res (.substring s start end))
144 | (inc end)
145 | (.indexOf s sep (inc end))))))
146 |
147 | (defn csi->attrs
148 | "Given a CSI specifier, excluding ESC[ but including the final \"m\", convert it
149 | to a map of properties that it sets or unsets. Property values of nil indicate
150 | a reset/unset. "
151 | [^String csi]
152 | (let [split-parts #(str-split (.substring csi 0 (dec (str-length csi))) ";")]
153 | (cond
154 | (.endsWith csi "m") ;; m: SGR - Select Graphic Rendition
155 | (if (or (= csi "m") (= csi "0m"))
156 | reset-attrs
157 | (let [csi-len (str-length csi)]
158 | (loop [[code & codes] (map parse-int (split-parts))
159 | result {}]
160 | (if code
161 | (if (or (=i 38 code) (=i 48 code))
162 | (let [[res codes] (parse-color code codes)]
163 | (recur codes (merge result res)))
164 | (recur codes (merge result (code->attrs code))))
165 | result))))
166 |
167 | (.endsWith csi "H")
168 | (let [[row col] (map parse-int (split-parts))]
169 | {:row row :col col}))))
170 |
171 | (defn has-escape-char?
172 | "Efficient check to see if a string contains an escape character."
173 | [s]
174 | #?(:clj (.contains ^String s ESC)
175 | :cljs (.includes s ESC)))
176 |
177 | (defn str-scan
178 | "Starting at position pos, move forward as long as the characters at the current
179 | position are within the given range. Returns the new position."
180 | [pos ^String s min max]
181 | (let [len (str-length s)]
182 | (loop [pos pos]
183 | (if (>= pos len)
184 | pos
185 | (let [ch (.codePointAt s pos)]
186 | (if (<= min ch max)
187 | (recur (inc pos))
188 | pos))))))
189 |
190 | ;; https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_sequences
191 | ;;
192 |
193 | ;; The ESC [ is followed by any number (including none) of "parameter bytes" in
194 | ;; the range 0x30–0x3F (ASCII 0–9:;<=>?), then by any number of "intermediate
195 | ;; bytes" in the range 0x20–0x2F (ASCII space and !"#$%&'()*+,-./), then finally
196 | ;; by a single "final byte" in the range 0x40–0x7E (ASCII @A–Z[\]^_`a–z{|}~).
197 | (defn next-csi
198 | "Split a string on the next escape sequence, returning a [head CSI tail]
199 | triplet. The returned CSI is excluding ESC[."
200 | ([s]
201 | (next-csi s 0))
202 | ([^String s ^Long start]
203 | (let [esc-pos (.indexOf s ESC start)]
204 | (when-not (identical? esc-pos -1)
205 | (let [pos (-> esc-pos
206 | (+ 2)
207 | (str-scan s 0x30 0x3F)
208 | (str-scan s 0x20 0x2F))]
209 | (when (< 1 pos (str-length s))
210 | (if (<= 0x40 (.codePointAt s pos) 0x7E)
211 | [(.substring s 0 esc-pos) (.substring s (+ 2 esc-pos) (inc pos)) (.substring s (inc pos))]
212 | (recur s pos))))))))
213 |
214 | (defn token-stream
215 | "Tokenize a string, whereby each CSI sequence gets transformed into a map of
216 | properties. The result is a vector of strings and maps."
217 | [string]
218 | (loop [input string
219 | result []]
220 | (if (has-escape-char? input)
221 | (if-let [match (next-csi input)]
222 | (let [[start csi tail] match]
223 | (recur tail
224 | (-> result
225 | (cond-> #_result (seq start) (conj start))
226 | (conj (csi->attrs csi)))))
227 | (cond-> result (seq input) (conj input)))
228 | (cond-> result (seq input) (conj input)))))
229 |
230 | (defn apply-props
231 | "Stateful transducer, apply it over the output of token-stream to know which
232 | styling should be applied over each piece of text.
233 |
234 | The results are pairs consisting of a property map and a string."
235 | [rf]
236 | (let [state (atom {})]
237 | (fn
238 | ([] (rf))
239 | ([res] (rf res))
240 | ([res val]
241 | (when (map? val)
242 | (swap! state #(into {} (remove (comp nil? second)) (merge % val))))
243 | (if (string? val)
244 | (rf res [@state val])
245 | res)))))
246 |
247 | (defn rgb->css [[_ r g b]]
248 | (str "rgb(" r "," g "," b ")"))
249 |
250 | (defn chunk->hiccup [[{:keys [foreground background bold] :as props} text]]
251 | [:span (if (seq props)
252 | {:style (cond-> {}
253 | foreground (assoc :color (rgb->css foreground))
254 | background (assoc :background-color (rgb->css background))
255 | bold (assoc :font-weight "bold"))}
256 | {})
257 | text])
258 |
259 | (def hiccup-xform
260 | "Transducer that consumes strings of input, and produces hiccup elements. Useful
261 | when dealing with streaming input, since it will carry over the 'terminal
262 | state'."
263 | (comp (map token-stream)
264 | apply-props
265 | (map chunk->hiccup)))
266 |
267 | (defn text->hiccup
268 | "Convenience function for the basic case where you have a string of terminal
269 | output and want to turn it into hiccup. Returns a seq of [:span] elements."
270 | [text]
271 | (sequence (comp apply-props
272 | (map chunk->hiccup))
273 | (token-stream text)))
274 |
--------------------------------------------------------------------------------
/test/lambdaisland/ansi/cljs_test_runner.clj:
--------------------------------------------------------------------------------
1 | (ns lambdaisland.ansi.cljs-test-runner
2 | (:gen-class)
3 | (:require [doo.core :as doo]
4 | [cljs.build.api :as cljs]))
5 |
6 | (def cljs-config {:main 'lambdaisland.ansi-test
7 | :output-to "out/testable.js"
8 | :output-dir "out"
9 | :optimizations :simple
10 | :target :nodejs})
11 |
12 | (defn -main [& args]
13 | (cljs/build ["src" "test"] cljs-config)
14 | (let [{:keys [exit] :as res}
15 | (doo/run-script :node cljs-config {:debug true})]
16 | (System/exit exit)))
17 |
--------------------------------------------------------------------------------
/test/lambdaisland/ansi_test.cljc:
--------------------------------------------------------------------------------
1 | (ns lambdaisland.ansi-test
2 | (:require [clojure.test :as t :refer [deftest testing is are run-tests]]
3 | [lambdaisland.ansi :as ansi]
4 | #?(:cljs [doo.runner :refer-macros [doo-tests]])))
5 |
6 | (deftest color-8-bit-test
7 | (are [x] x
8 | (= (ansi/color-8-bit 3)
9 | [[:rgb 205 205 0] false])
10 |
11 | (= (ansi/color-8-bit 12)
12 | [[:rgb 0 0 238] true])
13 |
14 | (= (ansi/color-8-bit 97)
15 | [[:rgb 0x87 0x5f 0xaf] false])
16 |
17 | (= (ansi/color-8-bit 232)
18 | [[:rgb 8 8 8] false])
19 |
20 | (= (ansi/color-8-bit 244)
21 | [[:rgb 128 128 128] false])
22 |
23 | (= (ansi/color-8-bit 255)
24 | [[:rgb 238 238 238] false])))
25 |
26 | (deftest token-stream-test
27 | (is (= (ansi/token-stream (str "start of the string"
28 | "\033[31m this is red"
29 | "\033[45m magenta background"
30 | "\033[1m bold"
31 | "\033[32m green foreground"))
32 | ["start of the string"
33 | {:foreground [:rgb 205 0 0]}
34 | " this is red"
35 | {:background [:rgb 205 0 205]}
36 | " magenta background"
37 | {:bold true}
38 | " bold"
39 | {:foreground [:rgb 0 205 0]}
40 | " green foreground"]))
41 |
42 | (are [x] x
43 | (= (ansi/token-stream "\033[30;47m black on white")
44 | [{:foreground [:rgb 0 0 0], :background [:rgb 229 229 229]} " black on white"])
45 |
46 | (= (ansi/token-stream "\033[1;31m bright red]")
47 | [{:bold true, :foreground [:rgb 205 0 0]} " bright red]"])
48 |
49 | (= (ansi/token-stream "\033[39;49m reset to defaults]")
50 | [{:foreground nil, :background nil} " reset to defaults]"])
51 |
52 | (= (ansi/token-stream "\033[0m reset all")
53 | [{:foreground nil, :background nil, :bold nil} " reset all"])
54 |
55 | (= (ansi/token-stream "\033[91m bright red")
56 | [{:foreground [:rgb 255 0 0]} " bright red"])
57 |
58 | (= (ansi/token-stream "\033[m raw-url-test")
59 | [{:foreground nil, :background nil, :bold nil} " raw-url-test"])))
60 |
61 | (deftest apply-props-test
62 | (is (= (sequence ansi/apply-props
63 | (ansi/token-stream
64 | (str "start of the string"
65 | "\033[31m this is red"
66 | "\033[45m magenta background"
67 | "\033[1m bold"
68 | "\033[39m reset foreground"
69 | "\033[49m reset background"
70 | "\033[32m green foreground"
71 | "\033[0;38;2;99;88;77m reset + rgb color")))
72 | [[{} "start of the string"]
73 | [{:foreground [:rgb 205 0 0]} " this is red"]
74 | [{:foreground [:rgb 205 0 0], :background [:rgb 205 0 205]}
75 | " magenta background"]
76 | [{:foreground [:rgb 205 0 0], :background [:rgb 205 0 205], :bold true}
77 | " bold"]
78 | [{:background [:rgb 205 0 205], :bold true} " reset foreground"]
79 | [{:bold true} " reset background"]
80 | [{:bold true, :foreground [:rgb 0 205 0]} " green foreground"]
81 | [{:foreground [:rgb 99 88 77]} " reset + rgb color"]])))
82 |
83 |
84 | (deftest text->hiccup-test
85 | (is (= (ansi/text->hiccup (str "start of the string"
86 | "\033[31m this is red"
87 | "\033[45m magenta background"
88 | "\033[1m bold"
89 | "\033[39m reset foreground"
90 | "\033[49m reset background"
91 | "\033[32m green foreground"
92 | "\033[0;38;2;99;88;77m reset + rgb color"))
93 |
94 | [[:span {} "start of the string"]
95 | [:span {:style {:color "rgb(205,0,0)"}} " this is red"]
96 | [:span
97 | {:style {:color "rgb(205,0,0)", :background-color "rgb(205,0,205)"}}
98 | " magenta background"]
99 | [:span
100 | {:style
101 | {:color "rgb(205,0,0)",
102 | :background-color "rgb(205,0,205)",
103 | :font-weight "bold"}}
104 | " bold"]
105 | [:span
106 | {:style {:background-color "rgb(205,0,205)", :font-weight "bold"}}
107 | " reset foreground"]
108 | [:span {:style {:font-weight "bold"}} " reset background"]
109 | [:span
110 | {:style {:color "rgb(0,205,0)", :font-weight "bold"}}
111 | " green foreground"]
112 | [:span {:style {:color "rgb(99,88,77)"}} " reset + rgb color"]])))
113 |
114 | (deftest has-escape-char?-test
115 | (are [x y] (= x y)
116 | true (ansi/has-escape-char? "\033[xxx")
117 | true (ansi/has-escape-char? "xxx\033[xxx")
118 | true (ansi/has-escape-char? "xxx\033[")
119 | true (ansi/has-escape-char? "\033[")
120 | false (ansi/has-escape-char? "xxxx")
121 | false (ansi/has-escape-char? "x")
122 | false (ansi/has-escape-char? "")))
123 |
124 | (deftest str-split-test
125 | (is (= (ansi/str-split "" ";")
126 | [""]))
127 | (is (= (ansi/str-split "foo" ";")
128 | ["foo"]))
129 | (is (= (ansi/str-split "foo;bar" ";")
130 | ["foo" "bar"]))
131 | (is (= (ansi/str-split "foo;bar;baz" ";")
132 | ["foo" "bar" "baz"]))
133 | (is (= (ansi/str-split "foo;;baz" ";")
134 | ["foo" "" "baz"] )))
135 |
136 | (deftest str-scan-test
137 | (is (= (ansi/str-scan 0 "abc123" 97 122)
138 | 3))
139 |
140 | (is (= (ansi/str-scan 0 "" 97 122)
141 | 0))
142 |
143 | (is (= (ansi/str-scan 0 "AAA" 97 122)
144 | 0))
145 |
146 | (is (= (ansi/str-scan 2 "AAA" 97 122)
147 | 2))
148 |
149 | (is (= (ansi/str-scan 2 "AAabcAA" 97 122)
150 | 5)))
151 |
152 | (deftest next-csi-test
153 | (is (= (ansi/next-csi "aaa\033[0mbbb")
154 | ["aaa" "0m" "bbb"]))
155 |
156 | (is (= (ansi/next-csi "")
157 | nil))
158 |
159 | (is (= (ansi/next-csi "aaa\033[0mbbb\033[5;18;37m")
160 | ["aaa" "0m" "bbb[5;18;37m"]))
161 |
162 | (is (= (-> (ansi/next-csi "aaa\033[0mbbb\033[5;18;37m")
163 | last
164 | ansi/next-csi)
165 | ["bbb" "5;18;37m" ""])))
166 |
167 | (deftest csi->attrs-test
168 | (is (= {:background [:rgb 205 0 205]}
169 | (ansi/csi->attrs "45m")))
170 |
171 | (is (= {:bold true}
172 | (ansi/csi->attrs "1m")))
173 |
174 | (is (= {:foreground nil}
175 | (ansi/csi->attrs "39m")))
176 |
177 | (is (= {:background nil}
178 | (ansi/csi->attrs "49m")))
179 |
180 | (is (= {:foreground [:rgb 0 205 0]}
181 | (ansi/csi->attrs "32m")))
182 |
183 | (is (= {:foreground [:rgb 99 88 77], :background nil, :bold nil}
184 | (ansi/csi->attrs "0;38;2;99;88;77m")))
185 |
186 | (is (= {:foreground [:rgb 0 0 0], :background [:rgb 229 229 229]}
187 | (ansi/csi->attrs "30;47m")))
188 |
189 | (is (= {:bold true, :foreground [:rgb 205 0 0]}
190 | (ansi/csi->attrs "1;31m")))
191 |
192 | (is (= {:foreground nil, :background nil}
193 | (ansi/csi->attrs "39;49m")))
194 |
195 | (is (= {:foreground [:rgb 255 0 0]}
196 | (ansi/csi->attrs "91m")))
197 |
198 | (is (= {:foreground [:rgb 205 0 0]}
199 | (ansi/csi->attrs "31m")))
200 |
201 | (is (= {:background [:rgb 205 0 205]}
202 | (ansi/csi->attrs "45m")))
203 |
204 | (is (= {:foreground nil, :background nil, :bold nil}
205 | (ansi/csi->attrs "0m")))
206 |
207 | (is (= {:foreground nil, :background nil, :bold nil}
208 | (ansi/csi->attrs "m"))))
209 |
210 |
211 | #?(:cljs (doo-tests 'lambdaisland.ansi-test))
212 |
213 | #_
214 | (run-tests)
215 |
--------------------------------------------------------------------------------
/tests.edn:
--------------------------------------------------------------------------------
1 | #kaocha/v1
2 | {}
3 |
--------------------------------------------------------------------------------