├── .gitignore ├── LICENSE ├── META6.json ├── README.md ├── lib ├── Pattern │ └── Match │ │ ├── Match.rakumod │ │ └── README.md ├── Print │ └── Dbg │ │ ├── Dbg.rakumod │ │ └── README.md ├── Self │ └── Recursion │ │ ├── README.md │ │ └── Recursion.rakumod ├── Test │ ├── Doctest │ │ └── Markdown │ │ │ ├── Markdown.rakumod │ │ │ └── README.md │ └── Fluent │ │ ├── Fluent.rakumod │ │ └── README.md ├── Text │ ├── Paragraphs │ │ ├── Paragraphs.rakumod │ │ └── README.md │ └── Wrap │ │ ├── README.md │ │ └── Wrap.rakumod └── _.rakumod └── t ├── 01-selective-imports.rakutest ├── PatternMatch ├── 01-import.rakutest └── 02-basic.rakutest ├── PrintDbg ├── 01-import.rakutest └── 02-basic.rakutest ├── SelfRecursion ├── 01-import.rakutest └── 02-basic.rakutest ├── TestDoctestMarkdown └── 02-basic.rakutest ├── TestFluent └── 02-basic.rakutest ├── TextParagraphs ├── 01-import.rakutest └── 02-basic.rakutest └── TextWrap ├── 01-import.rakutest ├── 02-basic.rakutest └── 03-intermediate.rakutest /.gitignore: -------------------------------------------------------------------------------- 1 | **/.precomp/ 2 | old 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The Artistic License 2.0 2 | 3 | Copyright (c) 2000-2006, The Perl Foundation. 4 | 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 | This license establishes the terms under which a given free software 11 | Package may be copied, modified, distributed, and/or redistributed. 12 | The intent is that the Copyright Holder maintains some artistic 13 | control over the development of that Package while still keeping the 14 | Package available as open source and free software. 15 | 16 | You are always permitted to make arrangements wholly outside of this 17 | license directly with the Copyright Holder of a given Package. If the 18 | terms of this license do not permit the full use that you propose to 19 | make of the Package, you should contact the Copyright Holder and seek 20 | a different licensing arrangement. 21 | 22 | Definitions 23 | 24 | "Copyright Holder" means the individual(s) or organization(s) 25 | named in the copyright notice for the entire Package. 26 | 27 | "Contributor" means any party that has contributed code or other 28 | material to the Package, in accordance with the Copyright Holder's 29 | procedures. 30 | 31 | "You" and "your" means any person who would like to copy, 32 | distribute, or modify the Package. 33 | 34 | "Package" means the collection of files distributed by the 35 | Copyright Holder, and derivatives of that collection and/or of 36 | those files. A given Package may consist of either the Standard 37 | Version, or a Modified Version. 38 | 39 | "Distribute" means providing a copy of the Package or making it 40 | accessible to anyone else, or in the case of a company or 41 | organization, to others outside of your company or organization. 42 | 43 | "Distributor Fee" means any fee that you charge for Distributing 44 | this Package or providing support for this Package to another 45 | party. It does not mean licensing fees. 46 | 47 | "Standard Version" refers to the Package if it has not been 48 | modified, or has been modified only in ways explicitly requested 49 | by the Copyright Holder. 50 | 51 | "Modified Version" means the Package, if it has been changed, and 52 | such changes were not explicitly requested by the Copyright 53 | Holder. 54 | 55 | "Original License" means this Artistic License as Distributed with 56 | the Standard Version of the Package, in its current version or as 57 | it may be modified by The Perl Foundation in the future. 58 | 59 | "Source" form means the source code, documentation source, and 60 | configuration files for the Package. 61 | 62 | "Compiled" form means the compiled bytecode, object code, binary, 63 | or any other form resulting from mechanical transformation or 64 | translation of the Source form. 65 | 66 | 67 | Permission for Use and Modification Without Distribution 68 | 69 | (1) You are permitted to use the Standard Version and create and use 70 | Modified Versions for any purpose without restriction, provided that 71 | you do not Distribute the Modified Version. 72 | 73 | 74 | Permissions for Redistribution of the Standard Version 75 | 76 | (2) You may Distribute verbatim copies of the Source form of the 77 | Standard Version of this Package in any medium without restriction, 78 | either gratis or for a Distributor Fee, provided that you duplicate 79 | all of the original copyright notices and associated disclaimers. At 80 | your discretion, such verbatim copies may or may not include a 81 | Compiled form of the Package. 82 | 83 | (3) You may apply any bug fixes, portability changes, and other 84 | modifications made available from the Copyright Holder. The resulting 85 | Package will still be considered the Standard Version, and as such 86 | will be subject to the Original License. 87 | 88 | 89 | Distribution of Modified Versions of the Package as Source 90 | 91 | (4) You may Distribute your Modified Version as Source (either gratis 92 | or for a Distributor Fee, and with or without a Compiled form of the 93 | Modified Version) provided that you clearly document how it differs 94 | from the Standard Version, including, but not limited to, documenting 95 | any non-standard features, executables, or modules, and provided that 96 | you do at least ONE of the following: 97 | 98 | (a) make the Modified Version available to the Copyright Holder 99 | of the Standard Version, under the Original License, so that the 100 | Copyright Holder may include your modifications in the Standard 101 | Version. 102 | 103 | (b) ensure that installation of your Modified Version does not 104 | prevent the user installing or running the Standard Version. In 105 | addition, the Modified Version must bear a name that is different 106 | from the name of the Standard Version. 107 | 108 | (c) allow anyone who receives a copy of the Modified Version to 109 | make the Source form of the Modified Version available to others 110 | under 111 | 112 | (i) the Original License or 113 | 114 | (ii) a license that permits the licensee to freely copy, 115 | modify and redistribute the Modified Version using the same 116 | licensing terms that apply to the copy that the licensee 117 | received, and requires that the Source form of the Modified 118 | Version, and of any works derived from it, be made freely 119 | available in that license fees are prohibited but Distributor 120 | Fees are allowed. 121 | 122 | 123 | Distribution of Compiled Forms of the Standard Version 124 | or Modified Versions without the Source 125 | 126 | (5) You may Distribute Compiled forms of the Standard Version without 127 | the Source, provided that you include complete instructions on how to 128 | get the Source of the Standard Version. Such instructions must be 129 | valid at the time of your distribution. If these instructions, at any 130 | time while you are carrying out such distribution, become invalid, you 131 | must provide new instructions on demand or cease further distribution. 132 | If you provide valid instructions or cease distribution within thirty 133 | days after you become aware that the instructions are invalid, then 134 | you do not forfeit any of your rights under this license. 135 | 136 | (6) You may Distribute a Modified Version in Compiled form without 137 | the Source, provided that you comply with Section 4 with respect to 138 | the Source of the Modified Version. 139 | 140 | 141 | Aggregating or Linking the Package 142 | 143 | (7) You may aggregate the Package (either the Standard Version or 144 | Modified Version) with other packages and Distribute the resulting 145 | aggregation provided that you do not charge a licensing fee for the 146 | Package. Distributor Fees are permitted, and licensing fees for other 147 | components in the aggregation are permitted. The terms of this license 148 | apply to the use and Distribution of the Standard or Modified Versions 149 | as included in the aggregation. 150 | 151 | (8) You are permitted to link Modified and Standard Versions with 152 | other works, to embed the Package in a larger work of your own, or to 153 | build stand-alone binary or bytecode versions of applications that 154 | include the Package, and Distribute the result without restriction, 155 | provided the result does not expose a direct interface to the Package. 156 | 157 | 158 | Items That are Not Considered Part of a Modified Version 159 | 160 | (9) Works (including, but not limited to, modules and scripts) that 161 | merely extend or make use of the Package, do not, by themselves, cause 162 | the Package to be a Modified Version. In addition, such works are not 163 | considered parts of the Package itself, and are not subject to the 164 | terms of this license. 165 | 166 | 167 | General Provisions 168 | 169 | (10) Any use, modification, and distribution of the Standard or 170 | Modified Versions is governed by this Artistic License. By using, 171 | modifying or distributing the Package, you accept this license. Do not 172 | use, modify, or distribute the Package, if you do not accept this 173 | license. 174 | 175 | (11) If your Modified Version has been derived from a Modified 176 | Version made by someone other than you, you are nevertheless required 177 | to ensure that your Modified Version complies with the requirements of 178 | this license. 179 | 180 | (12) This license does not grant you the right to use any trademark, 181 | service mark, tradename, or logo of the Copyright Holder. 182 | 183 | (13) This license includes the non-exclusive, worldwide, 184 | free-of-charge patent license to make, have made, use, offer to sell, 185 | sell, import and otherwise transfer the Package with respect to any 186 | patent claims licensable by the Copyright Holder that are necessarily 187 | infringed by the Package. If you institute patent litigation 188 | (including a cross-claim or counterclaim) against any party alleging 189 | that the Package constitutes direct or contributory patent 190 | infringement, then this Artistic License to you shall terminate on the 191 | date that such litigation is filed. 192 | 193 | (14) Disclaimer of Warranty: 194 | THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS 195 | IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED 196 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR 197 | NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL 198 | LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL 199 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 200 | DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF 201 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 202 | -------------------------------------------------------------------------------- /META6.json: -------------------------------------------------------------------------------- 1 | { 2 | "auth" : "zef:codesections", 3 | "authors" : [ 4 | "Daniel Sockwell" 5 | ], 6 | "build-depends" : [ ], 7 | "depends" : [ ], 8 | "description" : "A meta package for zero-dependency micro packages of under 70 lines of code", 9 | "license" : "Artistic-2.0", 10 | "name" : "_", 11 | "perl" : "6.d", 12 | "provides" : { 13 | "_": "lib/_.rakumod", 14 | "Print::Dbg": "lib/Print/Dbg/Dbg.rakumod", 15 | "Pattern::Match": "lib/Pattern/Match/Match.rakumod", 16 | "Self::Recursion": "lib/Self/Recursion/Recursion.rakumod", 17 | "Text::Wrap": "lib/Text/Wrap/Wrap.rakumod", 18 | "Text::Paragraphs": "lib/Text/Paragraphs/Paragraphs.rakumod", 19 | "Test::Doctest::Markdown": "lib/Test/Doctest/Markdown/Markdown.rakumod", 20 | "Test::Fluent": "lib/Test/Fluent/Fluent.rakumod" 21 | 22 | }, 23 | "resources" : [ ], 24 | "source-url" : "https://github.com/codesections/_.git", 25 | "support" : { 26 | "email" : "daniel@codesections.com" 27 | }, 28 | "tags" : [ 29 | "micro", 30 | "bundle", 31 | "lowbar", 32 | "_", 33 | "metapackage" 34 | ], 35 | "test-depends" : [ ], 36 | "version" : "0.0.1" 37 | } 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `_` 2 | 3 | `_` (pronounced "lowbar") is a meta utility package for the [Raku](https://raku.org) programming 4 | language. Being _meta_ means that `_` is comprised of independent sub-packages, each with their own 5 | documentation and tests. Being a _utility_ package means that each of `_`'s sub-packages is 6 | provides some helper functionality — the type of functionality that, if it weren't in `_`, might 7 | live in a `./utils` directory or in a tiny module that would bloat your dependency tree. 8 | 9 | `_`'s goal is to provide you with utilities that are [correct by 10 | inspection](https://proofwiki.org/wiki/ProofWiki:Jokes#Proof_by_Inspection): that is, with code so 11 | simple that you can look at it and _tell_ that it is correct. To achieve this goal, each `_` 12 | sub-package will always be: 13 | 14 | 1. **A single file** (not counting tests/docs) 15 | 2. **with zero dependencies** (not counting other `_` files or [core 16 | modules](https://docs.raku.org/language/modules-core)) 17 | 3. **with no more than 70 lines** 18 | 19 | This means that you or any other Raku programmer can evaluate any `_` sub-package by opening a 20 | single file and reading a page of code. If you have questions or concerns about any `_` 21 | sub-packages, I encourage you to do just that. 22 | 23 | However, just because you can doesn't mean that you must: each `_` sub-package is also documented in 24 | the accompanying `README` file located in its directory. Similarly, as valuable as "proof by 25 | inspection" my be, it's no substitute for tests. (Recall [Knuth's 26 | warning to a colleague](https://www-cs-faculty.stanford.edu/~knuth/faq.html): "Beware of bugs in the above code; I 27 | have only proved it correct, not tried it."). Accordingly, each sub-package also has its own tests. 28 | 29 | **NOTE**: Once `_` has a production release, it will guarantee backwards compatibility. However, 30 | `_` is currently beta software and does **not promise backwards compatibility**. 31 | 32 | For more information about `_`'s goals and plans, please see the [announcement blog 33 | post](https://raku-advent.blog/unix_philosophy_without_leftpad_part2). 34 | 35 | ## Installation 36 | 37 | Install `_` with `$ zef install _:auth`. 38 | 39 | ## Usage 40 | 41 | To use `_`, you can import all of `_`'s non-test functions with `use _`, all of its test functions 42 | with `use _ :Test`, or both sets with `use _ :ALL`. This style of importing is intended for 43 | prototyping/experimentation when you are not which `_` functions you may use. 44 | 45 | Alternatively, you can selectively import exported functions (or other symbols) by passing their 46 | name to the `use _` statement. For example, here's how you could import the `&dbg` function from 47 | the `Print::Dbg` sub-package and the `&wrap-words` function from the `Text::Wrap` sub-package using 48 | a fully-qualified use statement: 49 | 50 | ```raku 51 | use _:ver<0.0.1>:auth <&dbg &wrap-words>; 52 | ``` 53 | 54 | This style of imports is intended for later in the development process/when you want to pin to an 55 | exact `_` version and ensure that `_` does not cause unexpected name clashes. 56 | 57 | ## sub-packages 58 | 59 | `_` includes the following sub-packages. You can find more information about each one in 60 | the `README` file in its directory. 61 | 62 | * `Pattern::Match` - pattern match with Raku's full destructuring from signature 63 | binding. [README](lib/Pattern/Match/README.md); [src](lib/Pattern/Match/Match.rakumod) 64 | 65 | * `Print::Dbg` - better print-line debugging than `.raku` or 66 | `dd`. [README](lib/Print/Dbg/README.md); [src](lib/Print/Dbg/Dbg.rakumod) 67 | 68 | * `Self::Recursion` - provides `&_` as an alias for `&?ROUTINE` for anonymous self-recursion. 69 | [README](lib/Self/Recursion/README.md); [src](lib/Self/Recursion/Recursion.rakumod) 70 | 71 | * `Text::Paragraphs` - provides a `paragraphs` function similar to 72 | [`lines`](https://docs.raku.org/routine/lines) except that it breaks text up into paragraphs 73 | rather than lines. [README](lib/Text/Paragraphs/README.md); [src](lib/Text/Paragraphs/Paragraphs.rakumod) 74 | 75 | * `Text::Wrap` - provides a `wrap-words` function that wraps text to a specified line length (a 76 | better alternative to Rakudo's private `naive-word-wrapper`). [README](lib/Text/Wrap/README.md); 77 | [src](lib/Text/Wrap/Wrap.rakumod) 78 | 79 | * `Test::Doctest::Markdown` - tests Raku code blocks from `README`s or other markdown files and, 80 | optionally, compares their output to `# OUTPUT: «…»` 81 | comments. [README](lib/Test/Doctest/Markdown/README.md); [src](lib/Test/Doctest/Markdown/Markdown.md) 82 | 83 | * `Test::Fluent` - A thin wrapper over [Test](https://docs.raku.org/type/Test) that lets you 84 | describe tests in [declarator 85 | comments](https://docs.raku.org/language/pod#index-entry-declarator_blocks_#=) (`#|`) and to 86 | more fluently chain test methods.[README](lib/Test/Fluent/README.md); [src](lib/Test/Fluent/Fluent.md) 87 | 88 | ## Contributing 89 | 90 | You would be welcome to contribute to `_`'s development; you can help in any of the following ways: 91 | 92 | * by opening an issue 93 | - to report a section of the documentation that you found unclear 94 | - to report a bug in an existing sub-package 95 | - to suggest a feature for an existing sub-package 96 | - to suggest a new sub-package 97 | - to discuss future plans for `_`/and of the 98 | [questions from the announcement 99 | post](https://raku-advent.blog/unix_philosophy_without_leftpad_part2#future_plans) 100 | * by opening a pull request 101 | - to improve the documentation 102 | - to add tests for an exiting sub-package 103 | - to fix a bug 104 | - to add a feature for a sub-package 105 | - to add a new sub-package 106 | 107 | (For the last two, it'd probably be a good idea to mention your idea in an issue first; that's not 108 | a requirement, but it might prevent you spending time of a feature that isn't a great fit for `_`). 109 | 110 | All `_` contributors agree to abide by the [Raku Code of 111 | Conduct](https://raku.github.io/Raku-Steering-Council/papers/CoC). 112 | 113 | ## Roadmap 114 | 115 | My initial goal for `_` is to get it to a 1.0.0/stable release as soon as possible in order to 116 | provide guarantees regarding backwards compatibility. To that end, my priority is to decide what 117 | `_`'s overall approach to versioning will be and to implement that system. The [announcement blog 118 | post](https://raku-advent.blog/unix_philosophy_without_leftpad_part2#versioning) has additional 119 | details about the versioning considerations. 120 | 121 | Once `_` has a stable release, the plan is to focus on growing `_` to address other needs in the 122 | Raku ecosystem. 123 | -------------------------------------------------------------------------------- /lib/Pattern/Match/Match.rakumod: -------------------------------------------------------------------------------- 1 | unit module Pattern::Match; 2 | 3 | #| Error for a shadowed pattern 4 | class X::PatternMatch::Unreachable is Exception is export { 5 | has Signature $.shadowed is required; 6 | has Signature $.prior is required; 7 | 8 | method message { "The pattern\n $.shadowed.gist() \nwill never be matched because it is entirely " 9 | ~"shadowed by the prior pattern\n $.prior.gist()\n" } 10 | } 11 | 12 | #| Error for unmatched input – suggests a default case 13 | class X::PatternMatch::NoMatch is Exception is export { 14 | has Str $.capture is required; 15 | has Code @.branches is required; 16 | 17 | method message { "Cannot match the pattern\n$.capture.gist.indent(4)\n" 18 | ~"because none of these branches match:\n" 19 | ~ @.branches.map(*.signature.gist).join("\n").indent(4) 20 | ~"\nIf you would like to provide a default pattern, you" 21 | ~"can do so with a capture:\n" 22 | ~ '-> | { #`[code that handles default case] }'.indent(4) } 23 | } 24 | 25 | #| Detects whether &prior-fn totally shadows &cur-fn – that is, whether its signature accepts 26 | #| anything that &cur-fn's signature accepts. Because literal signatures aren't 27 | #| introspectable, detecting literals uses crude textual heuristics. 28 | sub shadows(&prior-fn, &cur-fn) { 29 | sub signature-with-literals($_ --> List()) { 30 | .signature.params.map: -> $param { given $param.raku { 31 | m/^\s?<( [ $=[ '-'? \d+ ['.' \d+]? 'e0'? ] 32 | | $ =[ '"' <-["]>* '"' ] ])> $/; 33 | 34 | with $/ { (val ~$_).WHAT } 35 | else { $param.type }}}} 36 | 37 | &prior-fn.signature ~~ &cur-fn.signature 38 | or &prior-fn.signature.params.grep({ .sigil eq '$' && .type !=:= Any}) 39 | && &prior-fn.&signature-with-literals ~~ &cur-fn.signature 40 | } 41 | 42 | #| Run the first of the provided blocks with a signature that matches $topic 43 | our proto choose(|) is export {*} 44 | multi choose(:on($topic) is raw = callframe(2).my<$_>, *@fns where .grep(Block) == +$_) { 45 | my (Bool $match-found, Mu $return-val) = (False, Mu); 46 | for @fns -> &f { 47 | # We need to run the full loop to find shadowed cases, so don't &last after finding a match 48 | if @fns[$++^..*].first({.&shadows: &f}) -> $_ { 49 | die X::PatternMatch::Unreachable.new: :shadowed(.signature) :prior(&f.signature) } 50 | next if $match-found; 51 | if try &f.cando($topic.List.Capture) ->$ (&fn) { $match-found = True; 52 | $return-val = fn |$topic }} 53 | 54 | $match-found ?? $return-val !! die X::PatternMatch::NoMatch.new: :capture($topic.raku):branches[@fns] 55 | } 56 | -------------------------------------------------------------------------------- /lib/Pattern/Match/README.md: -------------------------------------------------------------------------------- 1 | ## _::Pattern::Match 2 | 3 | This package provides the `&choose` function, which allows pattern-matching using Raku's [signature 4 | destructuring](https://docs.raku.org/type/Signature#Destructuring_arguments) as a more-powerful 5 | alternative to smartmatch's [partial pattern 6 | matching](https://docs.raku.org/language/syntax#Signature_literals). `&choose` takes a list of 7 | blocks and runs the first block with a signature that matches the current 8 | [topic](https://docs.raku.org/language/variables#index-entry-topic_variable). 9 | 10 | Because `&choose` uses signature destructuring, it supports binding to elements of the 11 | sub-signature. Thus, instead of this: 12 | 13 | ```raku 14 | # Without Pattern::Match 15 | for (:add(1, 5), :sub(9, 8), :mult(7, 7)) { 16 | when .key eq 'add' { 17 | say "{.value[0]} + {.value[1]} is {sum .value}" } 18 | when .key eq 'sub' { 19 | say "{.value[0]} - {.value[1]} is {[-] .value}" } 20 | when .key eq 'mult' { 21 | say "{.value[0]} × {.value[1]} is {[×] .value}" } 22 | default { die "Unknown op: $_" } 23 | } 24 | ``` 25 | 26 | You can write: 27 | 28 | ```raku 29 | use _ <&choose>; 30 | for (:add(1, 5), :sub(9, 8), :mult(7, 7)) { 31 | choose -> :$add ($a, $b) { say "$a + $b is {$a+$b}" }, 32 | -> :$sub ($a, $b) { say "$a - $b is {$a-$b}" }, 33 | -> :$mult ($a, $b) { say "$a × $b is {$a×$b}" }, 34 | -> |cap { die "Unknown op: " ~|cap } 35 | } 36 | ``` 37 | 38 | `&choose` can work especially well with [formal 39 | parameters](https://docs.raku.org/language/variables#index-entry-twigil_$:) and [automatic 40 | signatures](https://docs.raku.org/language/functions#index-entry-@__). Using those features, you 41 | could re-write the expression above as: 42 | 43 | 44 | ```raku 45 | for (:add(1, 5), :sub(9, 8), :mult(7, 7)) { 46 | choose { say "$:add[0] + $:add[1] is { [+] $add}" }, 47 | { say "$:sub[0] - $:sub[1] is { [-] $sub[*]}" }, 48 | { say "$:mult[0] × $:mult[1] is {[×] $mult[*]}" }, 49 | { die "Unknown op: " ~@_ } 50 | } 51 | ``` 52 | 53 | As with signatures, you can match against literals. Matches are evaluated from top to bottom (just 54 | as with `given`/`when`), so you can place more specific cases above more general ones: 55 | 56 | ```raku 57 | my $today = Date.new: '2021-12-11'; 58 | say do given $today { 59 | choose -> $ (12 :$month, 25 :$day, |) { "Merry Christmas!" }, 60 | -> $ (12 :$month, :$day where 26..*, |) { "I hope you had a nice Christmas :)" }, 61 | -> | { "Only {359 - .day-of-year} days 'till Christmas" } 62 | } 63 | ``` 64 | 65 | But if you put a more general type above specific type, it could make it impossible to match the 66 | more specific type. This is known as "shadowing"; `&choose` will throw an error if it detects a 67 | shadowed case: 68 | 69 | ```raku 70 | my $today = Date.new: '2021-12-11'; 71 | say do given $today { 72 | choose -> | { "Only {359 - .day-of-year} days 'till Christmas" }, 73 | -> $ (12 :$month, 25 :$day, |) { "Merry Christmas!" }, 74 | -> $ (12 :$month, :$day where 26..*, |) { "I hope you had a nice Christmas :)" }, 75 | } 76 | 77 | # THROWS with this message: 78 | # The pattern 79 | # ($ (Int :$month where { ... }, Int :$day where { ... }, |)) 80 | # will never be matched because it is entirely shadowed by the prior pattern 81 | # (|) 82 | ``` 83 | 84 | `&choose` will also throw an error if the topic does not match any of the cases. If you want to 85 | allow non-matching input, you can set a default pattern with `-> |` (as in the prior example). 86 | 87 | When you provide conditions for `&choose`, you are passing a list of `Block`s to a function. This 88 | means that, unlike `when` blocks, the conditional blocks must use [list 89 | syntax](https://docs.raku.org/language/list#Literal_lists) – that is, in the examples above, the 90 | **trailing commas are required** (except after the last block). 91 | 92 | If you don't care for the look of the `,`, you Raku allows you to separate list items with `;` so 93 | long as it's clear that the semicolon isn't ending a statement. Here, this means using 94 | parenthesizes to call `&choose`; using this syntax, the first example could be written as: 95 | 96 | ```raku 97 | for (:add(1, 5), :sub(9, 8), :mult(7, 7)) { 98 | choose( { say "$:add[0] + $:add[1] is { [+] $add}" }; 99 | { say "$:sub[0] - $:sub[1] is { [-] $sub[*]}" }; 100 | { say "$:mult[0] × $:mult[1] is {[×] $mult[*]}" }; 101 | { die "Unknown op: " ~@_ }) 102 | } 103 | ``` 104 | 105 | In all of the examples above, `&choose` has matched against `$_` (the current topic), which is its 106 | default behavior. But if you want it to match on some other value, you can pass that value with the 107 | `:on` named parameter. 108 | -------------------------------------------------------------------------------- /lib/Print/Dbg/Dbg.rakumod: -------------------------------------------------------------------------------- 1 | unit module Print::Dbg; 2 | our proto dbg(|) is export {*} 3 | 4 | #| Format the [$file:$line-num] header 5 | sub fmt-header() {'[%s:%2u] '.sprintf: callframe(3).&{.file.IO.basename, .line} } 6 | 7 | multi dbg($_ is raw) { note fmt-header() ~('' R// try "$(.^name) $(.VAR.name) = ") ~.raku; 8 | $_ } 9 | multi dbg(+args) { my $arg-list = args.map({('' R// try "$(.^name) $(.VAR.name)=") ~.raku}); 10 | note fmt-header() ~“($arg-list.join(", "))”; 11 | args } 12 | -------------------------------------------------------------------------------- /lib/Print/Dbg/README.md: -------------------------------------------------------------------------------- 1 | # _::Print::Dbg 2 | 3 | Provides the `dbg` function, an alternative to `say .raku` or Rakudo's 4 | [`&dd`](https://docs.raku.org/programs/01-debugging#index-entry-dd). `&dbg` accepts one or more Raku 5 | expressions and prints the value of those expressions and the line on which it was called: 6 | 7 | ```raku 8 | # In file `example.raku` with this as line 1 9 | dbg(42, 5, 'foo'.uc, 1+1); 10 | # OUTPUT: «[example.raku:2] (42, 5, "FOO", 2)» 11 | ``` 12 | 13 | When passed variables, `&dbg` will print information about the variables names: 14 | 15 | ```raku 16 | my $i = 42; 17 | my @a = ; 18 | dbg($i, @a); 19 | [example.raku:3] (Int $i=42, Array @a=["a", "b", "c"]) 20 | ``` 21 | 22 | The biggest difference between `&dbg` and `&dd` is that `&dbg` returns the value(s) it was called 23 | with, which lets you print an expression without preventing other parts of the code from using that 24 | expression. For example, you can use `&dbg` to see the value of `&arg` in the expression below 25 | without interfering with the call to `&some-function` 26 | 27 | 28 | ```raku 29 | sub some-function($arg1, dbg($arg2), $arg3); 30 | ``` 31 | -------------------------------------------------------------------------------- /lib/Self/Recursion/README.md: -------------------------------------------------------------------------------- 1 | # Self::Recursion 2 | 3 | Provides the variable `&_` as a shorter alias for `&?ROUTINE`, which allows for more concise 4 | anonymous recursion. For example, you can define a naive Fibonacci function as 5 | 6 | 7 | ```raku 8 | sub fib(UInt $n) { $n == 0|1 ?? $n !! &_($n-1) + &_($n-2) } 9 | say fib(5); # OUTPUT: «5» 10 | say (^11).map(&fib); # OUTPUT: «(0 1 1 2 3 5 8 13 21 34 55)» 11 | ``` 12 | 13 | Using `&_` has the same advantages main advantages as using `&?ROUTINE` – namely that it clearly 14 | expresses the intent to self-recourse, you don't need to change the name if you rename `fib`, and it 15 | can be used in an anonymous function. The only advantage `&_` provides over `&?ROUTINE` is a 16 | shorter name (well, and that it completes the pattern created by [`$_`, `@_`, and 17 | `%_`](https://docs.raku.org/language/functions#index-entry-@__)). 18 | 19 | 20 | 21 | ```raku 22 | sub f(UInt $n) { $n == 0|1 ?? $n !! &_($n-1) + &_($n-2) } 23 | say f(5); # OUTPUT: «85» 24 | ``` 25 | 26 | Please note that (as the lack of a `?` in its name indicates) `&_` is a run-time construct rather 27 | than a compile-time one and thus has significantly worse performance than `&?ROUTINE`, specifically 28 | in situations where an implementation would inline a call to `&?ROUTINE` (e.g., deeply recursive 29 | calls to a simple function, such as `fib` from above). Once Raku has stable support for 30 | Raku-AST-based macros, I plan to add a `&?_` variable. Until that time, you should avoid using `&_` 31 | in performance-critical code/with very deep recursion. 32 | -------------------------------------------------------------------------------- /lib/Self/Recursion/Recursion.rakumod: -------------------------------------------------------------------------------- 1 | unit module Self::Recursion; 2 | 3 | class X::_::Unsupported is Exception is export { 4 | method message { "Sorry, multis with no proto (or with an onlystar proto) don't currently\n" 5 | ~"work with \&_. Either use \&?ROUTINE instead, or add a proto with " ~ '{{*}}' } 6 | } 7 | 8 | #| Proxy to store the &calling-fn the first time we get it (to avoid walking the callframe each time) 9 | sub ROUTINE is rw { 10 | my &calling-fn; 11 | Proxy.new: FETCH => method () { &calling-fn }, 12 | STORE => method (&new is raw){ &calling-fn = &new }} 13 | 14 | my $fn := ROUTINE; 15 | our proto term:<&_>(|) is export {*} 16 | multi term:<&_> { 17 | if callframe(1).code.?multi.not { $fn = callframe(1).code } 18 | elsif callframe(1).code.name eq callframe(2).code.name { $fn = callframe(2).code } 19 | else { die X::_::Unsupported.new } 20 | $fn 21 | }; 22 | -------------------------------------------------------------------------------- /lib/Test/Doctest/Markdown/Markdown.rakumod: -------------------------------------------------------------------------------- 1 | unit module Test::Doctest::Markdown; 2 | use Test; 3 | 4 | my token doctest-comment { '' \n {$*line-number++}} 5 | my token any-line { \N*? \n { $*line-number++} } 6 | my token codefence-token { ['`' | '~'] ** 3..*} 7 | my token info-string { 'raku' \N* \n {$*line-number++}} 8 | 9 | our proto doctest(|) {*} 10 | multi doctest(IO::Path $file) { doctest($file.slurp, :file-name($file.basename))} 11 | 12 | multi doctest(Str $_, :$file-name = '') { 13 | my ($pos, $codeblock-number, $*line-number) = (0, 1, 1); 14 | while $pos < .chars { 15 | my Str() $codeblock = 16 | '' R// m:c($pos)• *? 17 | ? 18 | <.ws> 19 | <($=[ *? ] )> {} 20 | $( $) 21 | •; 22 | 23 | $pos = $/.to orelse last; # no $/.to means no codeblock found, so &last 24 | my %cfg = do if $ -> $_ { .EVAL }; 25 | 26 | my ($stdout, @output-comments); 27 | do { @output-comments = ( $codeblock ~~ m:g • '# OUTPUT: «' <(.+?)> '»' •); 28 | temp $*OUT = $stdout = new( 29 | class { has $!txt handles = ''; 30 | method print(+a) { $!txt ~= a.join }}:); 31 | (%cfg:v ~$codeblock).EVAL } 32 | 33 | if @output-comments.map({"'$_'"}).join(' .* ') -> $expected-output { 34 | $stdout.&like: / <$expected-output> /, 35 | "Output matches code block $($codeblock-number++) from $file-name" 36 | or diag "<\$expected-output> regex was: $expected-output\n" 37 | ~"from line $*line-number" } 38 | else { pass "No exceptions thrown in code block $($codeblock-number++) in $file-name"} 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/Test/Doctest/Markdown/README.md: -------------------------------------------------------------------------------- 1 | # _::Test::Doctest::Markdown 2 | 3 | Provide `&doctest` that tests Raku code contained in a Markdown file (typically a README) 4 | `&doctest`'s primary purpose is to run examples in READMEs and other documentation to ensure that 5 | all examples compile and run – nothing’s worse than broken examples! 6 | 7 | `&doctest` scans the Markdown file for any [fenced code 8 | blocks](https://spec.commonmark.org/0.30/#fenced-code-blocks) with 'raku' in their [info 9 | string](https://spec.commonmark.org/0.30/#info-string) and tests the code in each block. 10 | 11 | If the code block has `OUTPUT: «…»` comments, `&doctest` captures the code’s output and tests it 12 | against the expected output; if the code block doesn’t have `OUTPUT` comments, `&doctest` tests 13 | whether the code can be `EVALed` ok. 14 | 15 | `&doctest` also supports adding configuration info by preceding the code block with a 16 | `` comment; currently, the only config option is to provide setup code that’s run as 17 | part of the test without being displayed in the Markdown file; in the future, this will likely 18 | include more option, such as expecting tests to fail. 19 | -------------------------------------------------------------------------------- /lib/Test/Fluent/Fluent.rakumod: -------------------------------------------------------------------------------- 1 | unit module Test::Fluent; 2 | need Test; 3 | # Re-exported in _.rakumod 4 | constant &plan = &Test::EXPORT::DEFAULT::plan; 5 | constant &done-testing = &Test::EXPORT::DEFAULT::done-testing; 6 | constant &subtest = &Test::EXPORT::DEFAULT::subtest; 7 | constant &diag = &Test::EXPORT::DEFAULT::diag; 8 | constant &skip-rest = &Test::EXPORT::DEFAULT::skip-rest; 9 | constant &bail-out = &Test::EXPORT::DEFAULT::bail-out; 10 | 11 | # Just for local convenience 12 | my constant &ok = &Test::EXPORT::DEFAULT::ok; 13 | my constant &nok = &Test::EXPORT::DEFAULT::nok; 14 | my constant &is = &Test::EXPORT::DEFAULT::is; 15 | my constant &isnt = &Test::EXPORT::DEFAULT::isnt; 16 | my constant &is-deeply = &Test::EXPORT::DEFAULT::is-deeply; 17 | my constant &like = &Test::EXPORT::DEFAULT::like; 18 | my constant &unlike = &Test::EXPORT::DEFAULT::unlike; 19 | # TODO vvv 20 | my constant &is-approx = &Test::EXPORT::DEFAULT::is-approx; 21 | my constant &cmp-ok = &Test::EXPORT::DEFAULT::cmp-ok; 22 | my constant &isa-ok = &Test::EXPORT::DEFAULT::isa-ok; 23 | my constant &can-ok = &Test::EXPORT::DEFAULT::can-ok; 24 | my constant &does-ok = &Test::EXPORT::DEFAULT::does-ok; 25 | my constant &use-ok = &Test::EXPORT::DEFAULT::use-ok; 26 | my constant &dies-ok = &Test::EXPORT::DEFAULT::dies-ok; 27 | my constant &lives-ok = &Test::EXPORT::DEFAULT::lives-ok; 28 | my constant &eval-dies-ok = &Test::EXPORT::DEFAULT::eval-dies-ok; 29 | my constant &eval-lives-ok = &Test::EXPORT::DEFAULT::eval-lives-ok; 30 | my constant &throws-like = &Test::EXPORT::DEFAULT::throws-like; 31 | my constant &fails-like = &Test::EXPORT::DEFAULT::fails-like; 32 | my constant &todo = &Test::EXPORT::DEFAULT::todo; 33 | my constant &skip = &Test::EXPORT::DEFAULT::skip; 34 | my constant &pass = &Test::EXPORT::DEFAULT::pass; 35 | my constant &flunk = &Test::EXPORT::DEFAULT::flunk; 36 | 37 | class TestBlock does Callable { 38 | has $.got; 39 | has $.desc; 40 | has Bool $!negated = False; 41 | 42 | method ok { $.true } 43 | method not { $!negated = not $!negated; self } 44 | method eq(Mu \exp) { ($!negated ?? &isnt !! &is )( $!got, exp, $!desc ) } 45 | method like(Mu \exp) { ($!negated ?? &unlike !! &like)( $!got, exp, $!desc ) } 46 | method true { ($!negated ?? &nok !! &ok )( $!got, $!desc ) } 47 | method approx { !!! 'TODO' } 48 | method eqv(Mu \exp) { if $!negated { ok($!got !=== exp, $!desc) 49 | or diag "expected: anything except {exp.raku}\n got: $!got" } 50 | else { is-deeply $!got, exp, $!desc }} 51 | method deeply(Mu \exp) { $.eqv(exp) } 52 | method cmp { !!! 'TODO' } 53 | } 54 | 55 | use MONKEY-TYPING; 56 | augment class Block { 57 | multi method is(&b:) { TestBlock.new(:got(b), :desc(&b.WHY))} 58 | multi method is(&b: Str() $s) { TestBlock.new(:got(b), :desc(&b.WHY)).eq($s)} 59 | multi method isn't(&b:) { TestBlock.new(:got(b), :desc(&b.WHY)).not} 60 | multi method isn't(&b: Str() $s) { TestBlock.new(:got(b), :desc(&b.WHY)).not.eq($s)}} 61 | -------------------------------------------------------------------------------- /lib/Test/Fluent/README.md: -------------------------------------------------------------------------------- 1 | # _::Test::Fluent 2 | 3 | Provides a thin (and currently incomplete) wrapper over the Core 4 | [`Test`](https://docs.raku.org/type/Test) module that lets you write tests in a more fluent style 5 | inspired by the [Fluent Assertions](https://fluentassertions.com/) (.NET’s) and 6 | [Chai](https://fluentassertions.com/) (JS) packages. Additionally, `Test::Fluent` lets you set the 7 | descriptions for these tests in doc comments ([declarator 8 | blocks](https://docs.raku.org/language/pod#index-entry-declarator_blocks_#=)) rather than as a 9 | string: 10 | 11 | ```raku 12 | # with Raku's Test: 13 | unlike escape-str($str), //, 14 | "Escaped strings don't contain invalid characters"; 15 | 16 | # with Test::Fluent: 17 | #| Escaped strings don't contain invalid characters 18 | { escape-str($str) }.is.not.like: //; 19 | ``` 20 | 21 | Specifically, `Test::Fluent` works by 22 | [augmenting](https://docs.raku.org/language/typesystem#Augmenting_a_class) `Block`s with two 23 | new methods. (Note: in general, augmenting a built-in class is a very bad idea. But doing so in 24 | test code is significantly less likely to cause conflicts – I advise against using this approach 25 | in non-test code). 26 | 27 | The primary method that `Test::Fluent` adds is `.is`. `.is` begins a test chain, letting you use 28 | all of the test methods described below. Once in the test chain, you can use the `.not` method to 29 | invert the result of a test (i.e., results that would have passed now fail; those that would have 30 | failed now pass). The other method `Test::Fluent` adds to `Block`s is `.isn't`, which is simply a 31 | contraction of `.is.not`. 32 | 33 | Additionally, `Test::Fluent` re-exports `&plan`, `&done-testing`, `&subtest`, `&diag`, `&skip-rest`, 34 | and `&bail-out` functions from the core `Test` module (because these functions do not logically 35 | belong in a test chain). 36 | 37 | ## Provided methods 38 | 39 | `Test::Fluent` currently provides the following methods for use in a test chain: 40 | 41 | * `not` - inverts the meaning of the test result 42 | * `.true` - is invocant true? 43 | * `.ok` - synonym for `.true` 44 | * `eq` - is the invocant `eq` (as a `Str`) to the `Str` argument? 45 | * `like` - is the invocant a match for the `Regex` argument? 46 | * `eqv` - is the invocant `eqv` to the argument? 47 | * `deeply` - synonym for `.eqv` 48 | 49 | Note: This list is incomplete/a WIP; the plan is to also wrap the remaining functions from the core 50 | `Test` module. 51 | -------------------------------------------------------------------------------- /lib/Text/Paragraphs/Paragraphs.rakumod: -------------------------------------------------------------------------------- 1 | unit module Text::Paragraphs; 2 | 3 | my token bullet { [@(<- + *>) | [ \d\d? '.' ]] \s* } 4 | my token non-bullet { <-[- + *] - [\d]> } 5 | my token bullet-prefix { ^^ $=(\h*) } 6 | my token indent-prefix { ^^ \h* } 7 | my token blank-line { ^^ \h* [\n | $ ] } 8 | my token rest-of-line { \N* [\n | $ ] } 9 | my token continuation-line($indent) { 10 | \h ** {$indent} [\S & ] } 11 | 12 | our proto paragraphs(|) is export {*} 13 | 14 | multi paragraphs(Str $_, :$chomp=True, :$limit is copy =∞, :(:$pos is copy = 0) --> Seq()) { 15 | gather until $pos ≥ .chars or $limit-- ≤ 0 { 16 | if m:p($pos)• • { my $indent = ..chars..$_.chars with $; 17 | m:p($pos)• 18 | * • } 19 | elsif m:p($pos)• • { my $indent = $.chars; 20 | m:p($pos)• <( 21 | * 22 | * )> • } 23 | $chomp ?? take(~$/.trim-trailing) !! take(~$/) andthen $pos = $/.to } 24 | } 25 | 26 | multi paragraphs(Cool $c, :$chomp=True, :$limit=∞) { $c.Str.¶graphs: :$chomp:$limit } 27 | multi paragraphs(IO::Path $p, :$chomp=True, :$limit=∞, :$enc = 'utf8', :$nl-in = ["\x0A", "\r\n"]) { 28 | my $handle = $p.open(:$enc:$nl-in) andthen LEAVE try .close; 29 | eager $handle.¶graphs: :$chomp:$limit } 30 | multi paragraphs(IO::Handle $h, :$chomp=True, :$limit is copy =∞, :$close) { 31 | # A limited bit of laziness (closser to &split's laziness than &lines) 32 | flat gather for $h.comb(/.*? [\n | $]/, :$close) { 33 | for .¶graphs: :$chomp:$limit { $limit--; .take } 34 | last if $limit ≤ 0} 35 | } 36 | -------------------------------------------------------------------------------- /lib/Text/Paragraphs/README.md: -------------------------------------------------------------------------------- 1 | # _::Text::Paragraphs 2 | 3 | Provides a `paragraphs` function analogous to Raku’s `lines`. This function splits a `Str` or the 4 | contents of a file into paragraphs. 5 | 6 | It can detect two types of paragraphs. The first type is a sequence of lines separated by blank 7 | lines. For example, this text is formatted as three paragraphs of this type: 8 | 9 | ```text 10 | We do not all have to write like Faulkner, or program like Dijkstra. I 11 | will gladly tell people what my programming style is, and I will even 12 | tell them where I think their own style is unclear or makes me jump 13 | through mental hoops. 14 | 15 | But I do this as a fellow programmer, not as the Perl god. Some 16 | language designers hope to enforce style through various typographical 17 | means such as forcing (more or less) one statement per line. 18 | 19 | This is all very well for poetry, but I don't think I want to force 20 | everyone to write poetry in Perl. Such stylistic limits should be 21 | self-imposed, or at most policed by consensus among your buddies. 22 | ``` 23 | 24 | The second type of paragraph it can detect is indicated by an indented 25 | first line. Here's that same text (again as three paragraphs) 26 | formatted in that style: 27 | 28 | ```text 29 | We do not all have to write like Faulkner, or program like Dijkstra. I 30 | will gladly tell people what my programming style is, and I will even 31 | tell them where I think their own style is unclear or makes me jump 32 | through mental hoops. 33 | But I do this as a fellow programmer, not as the Perl god. Some 34 | language designers hope to enforce style through various typographical 35 | means such as forcing (more or less) one statement per line. 36 | This is all very well for poetry, but I don't think I want to force 37 | everyone to write poetry in Perl. Such stylistic limits should be 38 | self-imposed, or at most policed by consensus among your buddies. 39 | ``` 40 | 41 | `¶graphs` can distinguish between text that is has initial 42 | indentation indicating a paragraph from bulleted/numbered lists 43 | (where indentation does not indicate a paragraph break). Thus, the 44 | following is one paragraph: 45 | 46 | ```text 47 | Here are some available books, in alphabetical order: 48 | * Learning Raku, by brian d foy 49 | * Learning to program with Raku: First Steps, by JJ Merelo 50 | * Metagenomics, by Ken Youens-Clark 51 | * Parsing with Perl 6 Regexes and Grammars, by Moritz Lenz 52 | * Perl 6 at a Glance, by Andrew Shitov 53 | * Raku Fundamentals, by Moritz Lenz 54 | * Perl 6 Deep Dive, by Andrew Shitov 55 | * Think Perl 6: How to Think Like a Computer Scientist, by Laurent Rosenfeld. 56 | A list of books published or in progress is maintained in raku.org 57 | ``` 58 | -------------------------------------------------------------------------------- /lib/Text/Wrap/README.md: -------------------------------------------------------------------------------- 1 | # Text::Wrap 2 | 3 | This module exports a single function, `word-wrap`, which wraps text 4 | to a specified line length based on a simple greedy algorithm, and is 5 | intended to be used to format simple text for terminal output (e.g., 6 | error messages or human-readable output for CLI programs.) In 7 | particular, is designed to provide an alternative to Rakudo's 8 | [`Str.naive-word-wrapper`](https://github.com/rakudo/rakudo/blob/master/src/core.c/Str.pm6#L3614-L3659), 9 | a method that is marked as an `implementation-detail` and thus 10 | generally should not be relied on in production code. 11 | 12 | `word-wrap` is designed to do what you (probably) mean if given 13 | nothing but the text you want wrapped, which it will by default wrap to 14 | a width of 80 characters. For more customization, you can pass 15 | additional options and can tell `word-wrap` to operate in one of four 16 | modes. 17 | 18 | options TODO. 19 | 20 | Additionally, `word-wrap` can operate in the following four modes, 21 | each of which can be activated by passing the corresponding named 22 | argument. 23 | 24 | In `:reflow-all` mode, `word-wrap` removes all line breaks in the text 25 | and reflows it as a single block (this is the most direct equivalent 26 | to `.naive-word-wrapper`). In `:keep-paragraphs`, `word-wrap` 27 | keeps preserves the breaks between paragraphs, but fully reflows each 28 | paragraph. In `:keep-newlines`, `word-wrap` does not remove any of 29 | the line breaks in the text; all it does is wrap lines that are longer 30 | than the maximum line length. 31 | 32 | Finally, in `:smart` mode (the default) `word-wrap` attempts to do the 33 | right thing based on contextual heuristics. In general, this means 34 | that it behaves like `:keep-paragraphs` unless it detects evidence 35 | that a hard line break has semantic meaning (e.g., it appears to be 36 | part of a bulleted list). 37 | -------------------------------------------------------------------------------- /lib/Text/Wrap/Wrap.rakumod: -------------------------------------------------------------------------------- 1 | use Text::Paragraphs; 2 | unit module Text::Wrap; 3 | 4 | role WrapMode {}; class KeepParagraphs does WrapMode {}; 5 | class KeepNewlines does WrapMode {}; 6 | class ReflowAll does WrapMode {}; 7 | 8 | sub wrap-paragraph(Str $_, :$max-len, :$prefix) { 9 | m• ^ $=[ \s* ] $=[ .*? ] $=[ \s* ] $ •; 10 | 11 | my (&len, $paragraph-prefix) = (&display-length, $prefix || ~$); 12 | my $line-len = len $prefix; 13 | 14 | sub wrap-words-to-lines(@lines, $text ($word, $ws='')) { 15 | my $prefix = ~($paragraph-prefix ~~ /\n*<(\V*)>/); 16 | 17 | my @next-lines = gather if $line-len+len($word) ≤ $max-len { 18 | $line-len += len $word~$ws; 19 | take "@lines.tail()$word$ws" } 20 | else { $line-len = len $prefix~$word~$ws; 21 | take @lines.tail.trim-trailing; 22 | take "$prefix$word$ws" } 23 | [ |@lines.head(*-1), |@next-lines ] } 24 | 25 | my @words = $.trans("\n" => ' ', :s).comb(/\S+|\s+/).batch(2); 26 | $paragraph-prefix ~([''], |@words).reduce(&wrap-words-to-lines).join("\n") ~ $ 27 | } 28 | 29 | our proto wrap-words(|) is export {*} 30 | multi wrap-words( Str $_, Int :length($max-len)=80, Int :$indent=0, Str :$prefix=' 'x $indent, 31 | |modes (Bool :$keep-paragraphs=False, Bool :$reflow-all=False, Bool :$keep-newlines=False) 32 | --> Str:D) { 33 | PRE { only_one_mode_allowed: $keep-newlines + $keep-paragraphs + $reflow-all ≤ 1 } 34 | PRE { cannot_set_both_indent_and_prefix: $indent == 0 or ' 'x $indent eq $prefix } 35 | my WrapMode $mode = do with modes.hash.head.key // { ::(.split('-')».tc.join) } 36 | 37 | (do if $mode === KeepNewlines { .trim-leading.lines(:!chomp) } 38 | elsif $mode === ReflowAll { .trans("\n" => "\n", :s).lines(:!chomp)».trim-leading.join} 39 | elsif $mode === KeepParagraphs { .trim-leading.¶graphs: :!chomp}) 40 | ==> map({.&wrap-paragraph(:$max-len:$prefix)}) 41 | ==> join('') 42 | } 43 | 44 | #| Estimates a Str's display width. This is in theory impossible (it depends on the font) but in practice 45 | #| it typically works (most fonts behave reasonably). See https://stackoverflow.com/questions/3634627 46 | #| Note: Raku removes many 0-width codepoints for us by combining them during the NFC normalization 47 | sub display-length($_) is pure { 48 | sum .comb.map: { when 0x1160 ≤ .ord ≤ 0x11FF { 0 } # Hangul Jamo medial vowels 49 | when .uniprop('EastAsianWidth') eq 'W'|'F' { 2 } 50 | default { 1 }}} 51 | -------------------------------------------------------------------------------- /lib/_.rakumod: -------------------------------------------------------------------------------- 1 | need Self::Recursion; 2 | need Print::Dbg; 3 | need Pattern::Match; 4 | need Text::Paragraphs; 5 | need Text::Wrap; 6 | need Test::Fluent; 7 | need Test::Doctest::Markdown; 8 | 9 | my constant &_ = &Self::Recursion::term:<&_>; 10 | 11 | 12 | my constant @default-export-pairs = ('&term:<&_>' => &Self::Recursion::term:<&_>, 13 | '&dbg' => &Print::Dbg::dbg, 14 | '&choose' => &Pattern::Match::choose, 15 | '¶graphs' => &Text::Paragraphs::paragraphs, 16 | '&wrap-words' => &Text::Wrap::wrap-words); 17 | 18 | my constant @test-fluent-exports = <&plan &done-testing &subtest &diag &skip-rest &bail-out>; 19 | my constant @test-doctest-exports = <&doctest>; 20 | 21 | package EXPORT::DEFAULT { OUR::{.key} := .value for @default-export-pairs } 22 | package EXPORT::Test { OUR::{$_} := Test::Fluent::{$_} for @test-fluent-exports; 23 | OUR::{$_} := Test::Doctest::Markdown::{$_} for @test-doctest-exports; } 24 | package EXPORT::ALL { OUR::{.key} := .value for @default-export-pairs; 25 | OUR::{$_} := Test::Doctest::Markdown::{$_} for @test-doctest-exports; 26 | OUR::{$_} := Test::Fluent::{$_} for @test-fluent-exports} 27 | 28 | class X::Import::InvalidPos is X::Import::Positional { 29 | has %.exports; has $.invalid; 30 | method message { 31 | "Error while importing from '_':\n" 32 | ~ “Cannot import '$(+$.invalid == 1 ?? $.invalid !! "($.invald.join(", "))")' from _.\n” 33 | ~"_ exports:\n" ~%.exports.keys.join("\n").indent(4) } 34 | } 35 | 36 | proto EXPORT(|) {*} 37 | multi EXPORT { %().Map} 38 | multi EXPORT(*@requested-symbols where * > 0, *%n) { 39 | my %exports = (|OUR::EXPORT::ALL::.pairs, '&term:<&_>' => &_); 40 | my (@valid, @invalid); 41 | for @requested-symbols.map({ $_ eq '&_' ?? '&term:<&_>' !! $_}) -> $sym { 42 | if %exports{$sym}:p -> $_ { @valid.push: $_ } 43 | else { @invalid.push: $sym }} 44 | when ?@invalid { die X::Import::InvalidPos.new: :%exports:@invalid} 45 | default { @valid.Map } 46 | } 47 | -------------------------------------------------------------------------------- /t/01-selective-imports.rakutest: -------------------------------------------------------------------------------- 1 | use Test; 2 | 3 | plan 4; 4 | my Str @exported-fns = <&wrap-words &dbg &choose ¶graphs &term:<&_>>; 5 | 6 | subtest "Can use _ ok", { 7 | use-ok '_', "Full import works"; 8 | use _; 9 | ok MY::<&wrap-words>, 'use _ provides &wrap-words'; 10 | ok MY::<&dbg>, 'use _ provides &dbg'; 11 | ok MY::<&choose>, 'use _ provides &choose'; 12 | ok MY::<¶graphs>, 'use _ provides ¶graphs'; 13 | ok MY::<&term:<&_>>, 'use _ provides &term:<&_>'; 14 | 15 | } 16 | 17 | subtest "Selective imports of single fn", { 18 | use-ok "_ <$_>", "Can selectivly import '$_'" for @exported-fns } 19 | 20 | subtest "Selective imports of many random fns", { 21 | for @exported-fns.combinations(1..*).pick(5) { 22 | use-ok "_ <$_>", "Can selectivly import '$_'" }} 23 | 24 | throws-like {EVAL q[ use _ ]}, X::Import::Positional, "Invalid submodules throw"; 25 | -------------------------------------------------------------------------------- /t/PatternMatch/01-import.rakutest: -------------------------------------------------------------------------------- 1 | use Test; 2 | 3 | plan 2; 4 | subtest "Importing with 'use'", { 5 | { use _; 6 | &choose.&isa-ok: Code, "_ exports \&choose" } 7 | { use _ <&choose>; 8 | &choose.&isa-ok: Code, "_ allows selective use of \&choose" } 9 | { use Pattern::Match; 10 | &choose.&isa-ok: Code, "Pattern::Match exports \&choose" } 11 | } 12 | 13 | 14 | subtest "Importing with 'require'", { 15 | { require Pattern::Match; 16 | &Pattern::Match::choose.&isa-ok: Code, "Pattern::Match exposes \&Pattern::Match::choose" } 17 | } 18 | -------------------------------------------------------------------------------- /t/PatternMatch/02-basic.rakutest: -------------------------------------------------------------------------------- 1 | use Test; 2 | 3 | use Pattern::Match; 4 | 5 | plan 2; 6 | subtest "Handles basic patterns", { 7 | my @users = [ { :name }, 8 | { :name, :logged-in }, 9 | { :name, :logged-in, :dob(Date.new: now) } ]; 10 | my &matcher = { 11 | choose -> :$logged-in!, :$name, 12 | :$dob! where .day-of-year ~~ 13 | Date.new(now).day-of-year { "birthday" }, 14 | -> :$logged-in!, :$name { "logged-in" }, 15 | -> :$name { "logged-out" }, 16 | -> | { 'default' }} 17 | my @results = @users.map: &matcher; 18 | @results[0].&is: "logged-out", "Matches User 0"; 19 | @results[1].&is: "logged-in", "Matches User 1"; 20 | @results[2].&is: "birthday", "Matches User 2"; 21 | 22 | ok @users.pick(3).map(&matcher) ≡ , "Order doen't effect the number of matches"; 23 | matcher('Tom').&is: 'default', 'Uses default case'; } 24 | 25 | subtest "Handles basic unreachable paterns & non-matches", { 26 | my &matcher = { choose -> 400 { "bad-request" }, 27 | -> 404 { "not-found" } } 28 | 29 | (404, 400).map(&matcher).&is-deeply: , "Valid matches work"; 30 | { matcher 418 }.&throws-like: X::PatternMatch::NoMatch, "Non-matches throw correctly"; 31 | 32 | throws-like { given 404 { choose -> Int $status-code { $status-code }, 33 | -> 404 { 'not-found' }}}, 34 | X::PatternMatch::Unreachable, 35 | "Basic unreachable errors throw"; 36 | 37 | throws-like { given 404, 88 { choose -> Int $status-code, Int $user-id { $status-code }, 38 | -> 404, Int $user-id { 'not-found' }}}, 39 | X::PatternMatch::Unreachable, 40 | "Unreachable error 2 throw"; 41 | 42 | given 404 { (choose -> 500 { 'OK'}, 43 | -> 404 { 'not-found' }).&is: 'not-found', 44 | "Different literals don't shaddow each other" } 45 | 46 | todo "Introspection based on where clauses NYI"; 47 | throws-like { given 404 { choose -> $ where 400..500 { 'client-error' }, 48 | -> 404 { 'not-found' }}}, 49 | X::PatternMatch::Unreachable, 50 | "Handles more complex signature shadowing"; } 51 | -------------------------------------------------------------------------------- /t/PrintDbg/01-import.rakutest: -------------------------------------------------------------------------------- 1 | use Test; 2 | 3 | plan 2; 4 | subtest "Importing with 'use'", { 5 | { use _; 6 | &dbg.&isa-ok: Code, "_ exports \&dbg" } 7 | { use _ <&dbg>; 8 | &dbg.&isa-ok: Code, "_ allows selective use of \&dbg" } 9 | { use Print::Dbg; 10 | &dbg.&isa-ok: Code, "Print::Dbg exports \&dbg" } 11 | } 12 | 13 | 14 | subtest "Importing with 'require'", { 15 | { require Print::Dbg; 16 | &Print::Dbg::dbg.&isa-ok: Code, "Print::Dbg exposes \&Print::Dbg::dbg" } 17 | } 18 | -------------------------------------------------------------------------------- /t/PrintDbg/02-basic.rakutest: -------------------------------------------------------------------------------- 1 | use Test; 2 | use _; 3 | plan 10; 4 | 5 | do { 6 | # Bind $*OUT so we can check printed output 7 | temp $*ERR = my $msg = new( 8 | class { has $!txt handles ; 9 | method print(+a) { $!txt = a.join }}:); 10 | 11 | my token file { '[' $($?FILE.IO.basename) ':'<.ws> \d+ ']' <.ws> } 12 | 13 | dbg(my Int $i = 42).&is-deeply: 42, 'Returns an Int argument'; 14 | $msg.&like: / 'Int $i = 42'/, 'Prints an Int in a Scalar'; 15 | dbg($i, 5).&is-deeply: (42, 5), 'Returns a list argument'; 16 | $msg.&like: / '(Int $i=42, 5)'/, 'Prints a list of (Scalar Int, Int)'; 17 | dbg(1e0).&is-deeply: 1e0, 'Returns a Num argument'; 18 | $msg.&like: / '1e0'/, 'Prints a Num'; 19 | dbg($ = <1>).&is-deeply: <1>, 'Returns an IntStr argument'; 20 | $msg.&like: / 'IntStr $ = IntStr.new(1, "1")'/, 'Prints an IntStr'; 21 | dbg(<1 2 3>).&is-deeply: <1 2 3>, 'Returns a list of IntStrs'; 22 | $msg.&like: / '(IntStr.new(1, "1"),' .* '"3"))'/, 'Prints a list of IntStrs '; 23 | } 24 | -------------------------------------------------------------------------------- /t/SelfRecursion/01-import.rakutest: -------------------------------------------------------------------------------- 1 | use Test; 2 | 3 | plan 2; 4 | subtest "Importing with 'use'", { 5 | { use _; 6 | &_.&isa-ok: Code, "_ exports \&_" } 7 | { use _ <&_>; 8 | &_.&isa-ok: Code, "_ allows selective use of \&_" } 9 | { use Self::Recursion; 10 | &_.&isa-ok: Code, "Self::Recursion exports \&_" } 11 | } 12 | 13 | 14 | subtest "Importing with 'require'", { 15 | { require Self::Recursion; 16 | # Note: When importing directly from Self::Recursion, the &term:<&_> full-name is required 17 | &Self::Recursion::term:<&_>.&isa-ok: Code, "Self::Recursion exposes \&Self::Recursion::term:<&_>" } 18 | } 19 | -------------------------------------------------------------------------------- /t/SelfRecursion/02-basic.rakutest: -------------------------------------------------------------------------------- 1 | use Test; 2 | 3 | unit sub MAIN(Bool :b(:$bench), :r(:$runs)=1000); 4 | 5 | 6 | { use _; 7 | sub countdown($seconds) { if $seconds ≥ 0 { "$seconds…", |(&_($seconds - 1)) } 8 | else { "BLASTOFF!" } } 9 | 10 | countdown(5).&is-deeply: <5… 4… 3… 2… 1… 0… BLASTOFF!>, 11 | "Basic sub recurses"; 12 | } 13 | 14 | 15 | { use _ <&_>; 16 | proto count-up(|){{*}} 17 | multi count-up(Int) { "Ok, buster, you're in trouble now!" } 18 | multi count-up(Int $one-two-three where * ≤ 3) { $one-two-three, |&_($one-two-three + 1) } 19 | 20 | (try count-up(1)).&is-deeply: (1, 2, 3, "Ok, buster, you're in trouble now!"), 21 | "Basic multi recurses"; 22 | } 23 | 24 | { use Self::Recursion; 25 | multi fib(0) { 0 } 26 | multi fib(1) { 1 } 27 | multi fib($n where * > 0) { &_($n-1) + &_($n-2) } 28 | 29 | throws-like {fib 4}, X::_::Unsupported; 30 | } 31 | 32 | 33 | if not $bench {skip "benchmark test (run it with -b)", 1} 34 | else { 35 | use _; 36 | my @a; 37 | sub simple-fn($n) { @a.push($n); 38 | &_($n - 1) if $n > 0 } 39 | 40 | my $start-time = now; 41 | simple-fn($runs); 42 | @a.elems.&is: $runs+1, "Deep regression is run the right number of times"; 43 | (@a.head(3), @a.tail(3)).&is-deeply: (($runs, $runs-1, $runs-2).Seq, (2, 1, 0).Seq), 44 | "Deep regression returns correct results"; 45 | diag "Ran $runs calls in &sprintf('%.3f', now - $start-time) seconds"; 46 | } 47 | 48 | plan 4; 49 | -------------------------------------------------------------------------------- /t/TestDoctestMarkdown/02-basic.rakutest: -------------------------------------------------------------------------------- 1 | use _ :Test; 2 | plan 2; 3 | 4 | my $test-md = q:to/§md/; 5 | # Test markdown file 6 | 7 | Some content. And some more. And then, 8 | maybe, an example: 9 | 10 | ```raku 11 | say 5 + 37; # OUTPUT: «42» 12 | say 'snafu'.uc # OUTPUT: «SNAFU» 13 | ``` 14 | 15 | See how that worked? the text after the example asks. 16 | 17 | If not, maybe this example will help? 18 | ```raku 19 | sub no-output($a) { "this function isn't called"} 20 | ``` 21 | 22 | See what I mean? 23 | §md 24 | 25 | 26 | 27 | 28 | doctest($test-md); 29 | -------------------------------------------------------------------------------- /t/TestFluent/02-basic.rakutest: -------------------------------------------------------------------------------- 1 | use _ :Test; 2 | 3 | plan 7; 4 | 5 | #| .is.not.like passes with regex 6 | { 5 + 11 }.is.not.like(/18/); 7 | #| .isn't.like passes with regex 8 | { 5 + 11 }.isn't.like(/18/); 9 | 10 | #| .is.like passes with regex 11 | { 5 + 11 }.is.like(/16/); 12 | 13 | #| .is passes with Str 14 | { 'foo'.uc }.is('FOO'); 15 | 16 | #| .is.eq passes with Str 17 | { 'FOO'.lc }.is.eq('foo'); 18 | 19 | #| .is.true passes 20 | { True}.is.true; 21 | 22 | #| .isn't.eqv passes 23 | {5}.isn't.eqv: 51; 24 | -------------------------------------------------------------------------------- /t/TextParagraphs/01-import.rakutest: -------------------------------------------------------------------------------- 1 | use Test; 2 | 3 | plan 2; 4 | subtest "Importing with 'use'", { 5 | { use _; 6 | ¶graphs.&isa-ok: Code, "_ exports \¶graphs" } 7 | { use _ <¶graphs>; 8 | ¶graphs.&isa-ok: Code, "_ allows selective use of \¶graphs" } 9 | { use Text::Paragraphs; 10 | ¶graphs.&isa-ok: Code, "Text::Paragraphs exports \¶graphs" } 11 | } 12 | 13 | 14 | subtest "Importing with 'require'", { 15 | { require Text::Paragraphs; 16 | &Text::Paragraphs::paragraphs.&isa-ok: Code, "Text::Paragraphs exposes \&Text::Paragraphs::paragraphs" } 17 | } 18 | -------------------------------------------------------------------------------- /t/TextParagraphs/02-basic.rakutest: -------------------------------------------------------------------------------- 1 | use _; 2 | use Test; 3 | plan 3; 4 | 5 | sub tmp-file(:(:$_ = $?FILE.IO)) { 6 | "test-data.from.{.parent.basename}.{.basename}.ln$(callframe(1).line.Str).txt" } 7 | 8 | subtest "Basic ¶ with bullets", { 9 | my $in = q:to/§eof/; 10 | Here are some available books, in alphabetical order: 11 | * Learning Raku, by brian d foy 12 | * Learning to program with Raku: First Steps, by JJ Merelo 13 | * Metagenomics, by Ken Youens-Clark 14 | * Parsing with Perl 6 Regexes and Grammars, by Moritz Lenz 15 | * Perl 6 at a Glance, by Andrew Shitov 16 | * Raku Fundamentals, by Moritz Lenz 17 | * Perl 6 Deep Dive, by Andrew Shitov 18 | * Think Perl 6: How to Think Like a Computer Scientist, by Laurent Rosenfeld. 19 | A list of books published or in progress is maintained in raku.org. 20 | §eof 21 | 22 | $in.lines[1..^*-1].join("\n").¶graphs.&is-deeply: 23 | $in.lines[1..^*-1], "Splits bullets into ¶¶"; 24 | $in.¶graphs.&is-deeply: $in.lines, "Splits bullets and headers into ¶¶"; 25 | 26 | $in.¶graphs(:limit(3)).&is-deeply: 27 | $in.lines[^3], ":limit works when spliting bullets and headers into ¶¶"; 28 | $in.¶graphs(:!chomp).&is-deeply: 29 | $in.lines(:!chomp), ":!chomp keeps ␤ in bullets"; 30 | $in.¶graphs(:!chomp:limit(4)).&is-deeply: 31 | $in.lines(:!chomp)[^4], ":!chomp and :limit can be used together with bullets"; 32 | 33 | indir $*TMPDIR, { 34 | my $path = tmp-file; 35 | spurt $path, $in; 36 | $path.IO.¶graphs.&is-deeply: $in.lines, "Spliting bullets and headers works with a Path"; 37 | $path.IO.open.¶graphs.&is-deeply: $in.lines, "Spliting bullets and headers works with a Handle"; 38 | $path.IO.¶graphs(:!chomp).&is-deeply: 39 | $in.lines(:!chomp), "Keeping ␤ in bullets with :!chomp works with a Path"; 40 | $path.IO.open.¶graphs(:!chomp).&is-deeply: 41 | $in.lines(:!chomp), "Keeping ␤ in bullets with :!chomp works with a Handle"; 42 | unlink $path; } 43 | } 44 | 45 | 46 | 47 | 48 | subtest "Basic ¶, seperated by blank lines", { 49 | my $in = q:to/§eof/; 50 | The sub form, which takes $*ARGFILES by default, will apply the lines method to the 51 | object that's the first argument, and pass it the rest of the arguments. 52 | 53 | The method will return a Seq each element of which is a line from the handle (that is 54 | chunks delineated by .nl-in). If the handle's .chomp attribute is set to True, then 55 | characters specified by .nl-in will be stripped from each line. 56 | 57 | Reads up to $limit lines, where $limit can be a non-negative Int, Inf, or Whatever 58 | (which is interpreted to mean Inf). If :$close is set to True, will close the handle 59 | when the file ends or $limit is reached. Subroutine form defaults to $*ARGFILES, if 60 | no handle is provided. 61 | 62 | Attempting to call this method when the handle is in binary mode will result in 63 | X::IO::BinaryMode exception being thrown. 64 | 65 | §eof 66 | 67 | $in.¶graphs.&is-deeply: $in.trim.split("\n\n", :skip-empty), "Splits basic ¶"; 68 | $in.¶graphs(:!chomp).&is-deeply: $in.comb(/.*?\n\n/), "Keeps ␤ with :!chomp"; 69 | 70 | indir $*TMPDIR, { 71 | my $path = tmp-file; 72 | spurt $path, $in; 73 | $path.IO.¶graphs.&is-deeply: 74 | $in.trim.split("\n\n", :skip-empty), "Spliting basic ¶ works with a Path"; 75 | $path.IO.¶graphs(:limit(2)).&is-deeply: 76 | $in.trim.split("\n\n", :skip-empty)[^2], ":limit(2) works when spliting basic ¶ with a Path"; 77 | $path.IO.¶graphs(:limit(3)).&is-deeply: 78 | $in.trim.split("\n\n", :skip-empty)[^3], ":limit(3) works when spliting basic ¶ with a Path"; 79 | $path.IO.open.¶graphs.&is-deeply: 80 | $in.trim.split("\n\n", :skip-empty), "Spliting basic ¶ works with a Handle"; 81 | $path.IO.¶graphs(:!chomp).&is-deeply: 82 | $in.comb(/.*?\n\n/), "Keeping ␤ in basic ¶ with :!chomp works with a Path"; 83 | $path.IO.open.¶graphs(:!chomp).&is-deeply: 84 | $in.comb(/.*?\n\n/), "Keeping ␤ in basic ¶ with :!chomp works with a Handle"; 85 | unlink $path; } 86 | } 87 | 88 | subtest "Basic ¶, seperated by indentation", { 89 | my $in = q:to/§eof/; 90 | The sub form, which takes $*ARGFILES by default, will apply the lines method to the 91 | object that's the first argument, and pass it the rest of the arguments. 92 | The method will return a Seq each element of which is a line from the handle (that is 93 | chunks delineated by .nl-in). If the handle's .chomp attribute is set to True, then 94 | characters specified by .nl-in will be stripped from each line. 95 | Reads up to $limit lines, where $limit can be a non-negative Int, Inf, or Whatever 96 | (which is interpreted to mean Inf). If :$close is set to True, will close the handle 97 | when the file ends or $limit is reached. Subroutine form defaults to $*ARGFILES, if 98 | no handle is provided. 99 | Attempting to call this method when the handle is in binary mode will result in 100 | X::IO::BinaryMode exception being thrown. 101 | 102 | §eof 103 | 104 | $in.¶graphs.&is-deeply: 105 | $in.split(" ", :skip-empty)».trim-trailing, "Splits basic indented ¶"; 106 | $in.¶graphs(:!chomp).&is-deeply: 107 | $in.split(" ", :skip-empty), "Keeps ␤ with :!chomp in basic indented ¶"; 108 | 109 | indir $*TMPDIR, { 110 | my $path = tmp-file; 111 | spurt $path, $in; 112 | $path.IO.¶graphs.&is-deeply: 113 | $in.split(" ", :skip-empty)».trim-trailing, "Splits basic indented ¶ with a Path"; 114 | $path.IO.¶graphs(:limit(3)).&is-deeply: 115 | $in.split(" ", :skip-empty)».trim-trailing[^3], ":limit(3) works with basic indented ¶ with a Path"; 116 | $path.IO.open.¶graphs.&is-deeply: 117 | $in.split(" ", :skip-empty)».trim-trailing, "Splits basic indented ¶ with a Handle"; 118 | $path.IO.¶graphs(:!chomp).&is-deeply: 119 | $in.split(" ", :skip-empty), "Keeps ␤ with :!chomp in basic indented ¶ with a Path"; 120 | $path.IO.open.¶graphs(:!chomp).&is-deeply: 121 | $in.split(" ", :skip-empty), "Keeps ␤ with :!chomp in basic indented ¶ with a Handle"; 122 | unlink $path; } 123 | } 124 | -------------------------------------------------------------------------------- /t/TextWrap/01-import.rakutest: -------------------------------------------------------------------------------- 1 | use Test; 2 | 3 | plan 2; 4 | subtest "Importing with 'use'", { 5 | { use _; 6 | &wrap-words.&isa-ok: Code, "_ exports \&wrap-words" } 7 | { use _ <&wrap-words>; 8 | &wrap-words.&isa-ok: Code, "_ allows selective use of \&wrap-words" } 9 | { use _; 10 | &wrap-words.&isa-ok: Code, "Text::Wrap exports \&wrap-words" } 11 | } 12 | 13 | 14 | subtest "Importing with 'require'", { 15 | { require Text::Wrap; 16 | &Text::Wrap::wrap-words.&isa-ok: Code, "Text::Wrap exposes \&_::wrap-words" } 17 | } 18 | -------------------------------------------------------------------------------- /t/TextWrap/02-basic.rakutest: -------------------------------------------------------------------------------- 1 | use Test; 2 | use _; 3 | 4 | plan 4; 5 | subtest "Wraps line to various lengths", { 6 | my ($s80, $s72, $s60, $s30) = q:to/§end/.split("\n\n"); 7 | A naive word wrapper intended to be used for aligning error messages. Naive in 8 | the sense that it assumes all graphemes are the same width, and words that are 9 | too long for a line, will simply live on a line of their own, even if that is 10 | longer than the given maximum width. 11 | 12 | A naive word wrapper intended to be used for aligning error messages. 13 | Naive in the sense that it assumes all graphemes are the same width, and 14 | words that are too long for a line, will simply live on a line of their 15 | own, even if that is longer than the given maximum width. 16 | 17 | A naive word wrapper intended to be used for aligning error 18 | messages. Naive in the sense that it assumes all graphemes 19 | are the same width, and words that are too long for a line, 20 | will simply live on a line of their own, even if that is 21 | longer than the given maximum width. 22 | 23 | A naive word wrapper intended 24 | to be used for aligning error 25 | messages. Naive in the sense 26 | that it assumes all graphemes 27 | are the same width, and words 28 | that are too long for a line, 29 | will simply live on a line of 30 | their own, even if that is 31 | longer than the given maximum 32 | width. 33 | 34 | §end 35 | 36 | my $in = $s80.lines.join(' '); 37 | 38 | wrap-words($in, :length(80)).&is: $s80, "Wraps basic text down to 80 chars"; 39 | wrap-words($in, :length(72)).&is: $s72, "Wraps basic text down to 72 chars"; 40 | wrap-words($in, :length(60)).&is: $s60, "Wraps basic text down to 60 chars"; 41 | wrap-words($in, :length(30)).&is: $s30, "Wraps basic text down to 30 chars"; 42 | wrap-words($s30, :length(80)).&is: $s80, "Wraps basic text up to 80 chars"; 43 | 44 | wrap-words($s30, :length(80), :keep-newlines).&is: $s30, "Only wraps long lines with :keep-newlines"; 45 | wrap-words("\n" ~$in, :length(80)).&is: $s80, "Wraps basic text that starts with a ␤"; 46 | } 47 | 48 | subtest "Applies indent/prefix", { 49 | my ($s1, $s2, $s3) = q:to/§natural-language-principles/.split: "\n\n"; 50 | We do not all have to write like Faulkner, or program like Dijkstra. I will 51 | gladly tell people what my programming style is, and I will even tell them where 52 | I think their own style is unclear or makes me jump through mental hoops. But I 53 | do this as a fellow programmer, not as the Perl god. Some language designers 54 | hope to enforce style through various typographical means such as forcing (more 55 | or less) one statement per line. This is all very well for poetry, but I don't 56 | think I want to force everyone to write poetry in Perl. Such stylistic limits 57 | should be self-imposed, or at most policed by consensus among your buddies. 58 | 59 | We do not all have to write like Faulkner, or program like Dijkstra. I 60 | will gladly tell people what my programming style is, and I will even 61 | tell them where I think their own style is unclear or makes me jump 62 | through mental hoops. But I do this as a fellow programmer, not as the 63 | Perl god. Some language designers hope to enforce style through various 64 | typographical means such as forcing (more or less) one statement per 65 | line. This is all very well for poetry, but I don't think I want to 66 | force everyone to write poetry in Perl. Such stylistic limits should be 67 | self-imposed, or at most policed by consensus among your buddies. 68 | 69 | # We do not all have to write like Faulkner, or program like Dijkstra. I will 70 | # gladly tell people what my programming style is, and I will even tell them 71 | # where I think their own style is unclear or makes me jump through mental 72 | # hoops. But I do this as a fellow programmer, not as the Perl god. Some 73 | # language designers hope to enforce style through various typographical means 74 | # such as forcing (more or less) one statement per line. This is all very well 75 | # for poetry, but I don't think I want to force everyone to write poetry in 76 | # Perl. Such stylistic limits should be self-imposed, or at most policed by 77 | # consensus among your buddies. 78 | 79 | §natural-language-principles 80 | 81 | my $in = $s1.lines.join(' '); 82 | 83 | wrap-words($in ).&is: $s1, "Wraps with no indentation"; 84 | wrap-words($in, :indent(8) ).&is: $s2, "Wraps with indentation of 8"; 85 | wrap-words($in, :prefix(' # ')).&is: $s3, "Wraps with custom prefix"; 86 | } 87 | 88 | subtest "Handles paragraph", { 89 | 90 | my ($s1, $s2) = (q:to/§readme/, q:to/§reflowed/); 91 | I want to build reliable software – software that works well, delights its 92 | users, and that isn't subject to major security flaws. To that end, I have two 93 | beliefs (well, ok, I have _lots_ of beliefs, but two that I'd like to focus on 94 | now): 95 | 96 | * Software is more reliable when it's composed of small pieces, each of 97 | which is responsible for [only one 98 | task](https://en.wikipedia.org/wiki/Unix_philosophy). 99 | 100 | * Software is difficult to reason about – and therefore dificult to build 101 | well – when it has too many moving parts or systems become too big. 102 | 103 | In many instances, these two beliefs complement one another. 104 | §readme 105 | I want to build reliable software – software that works well, delights its 106 | users, and that isn't subject to major security flaws. To that end, I have two 107 | beliefs (well, ok, I have _lots_ of beliefs, but two that I'd like to focus on 108 | now): * Software is more reliable when it's composed of small pieces, each of 109 | which is responsible for [only one 110 | task](https://en.wikipedia.org/wiki/Unix_philosophy). * Software is difficult to 111 | reason about – and therefore dificult to build well – when it has too many 112 | moving parts or systems become too big. In many instances, these two beliefs 113 | complement one another. 114 | §reflowed 115 | 116 | wrap-words($s1, :keep-paragraphs).&is: $s1, ":keep-paragraphs preserves paragraphs and indention"; 117 | wrap-words($s1, ).&is: $s1, "Smart mode preserves paragraphs and indention"; 118 | wrap-words($s1, :reflow-all ).&is: $s2, "Merges paragraphs with :reflow-all"; 119 | 120 | } 121 | 122 | subtest "Handles lists", { 123 | 124 | my ($s1, $s2) = (q:to/§changelog/, q:to/§numbered-list/); 125 | + Changes: 126 | + Make the `sprintf` method show its format string on error 127 | + The `test` named parameter of the `dir` routine now handles Junctions 128 | + Improve error message for the `X::Pragma::CannotPrecomp` exception 129 | + Efficiency: 130 | + Numerous small speed-ups and memory-related improvements 131 | §changelog 132 | 1. First item in a list with fairly long items that take up enough space in the 133 | line to be worth wrapping. 134 | 2. Second item in a list with fairly long items that take up enough space in 135 | the line to be worth wrapping. 136 | 3. Third item in a list with fairly long items that take up enough space in the 137 | line to be worth wrapping. 138 | 139 | Some text after that list 140 | §numbered-list 141 | 142 | wrap-words($s1, :keep-newlines ).&is: $s1, "Keeps manual formatting with :keep-newlines"; 143 | wrap-words($s1 ).&is: $s1, "Smart mode handles a simple bulleted list with '+' "; 144 | wrap-words($s1.trans('+' => '*')).&is: $s1.trans('+' => '*'), 145 | "Smart mode handles a simple bulleted list with '*' "; 146 | wrap-words($s1.trans('+' => '-')).&is: $s1.trans('+' => '-'), 147 | "Smart mode handles a simple bulleted list with '-' "; 148 | wrap-words($s2).&is: $s2, "Wraps long bulleted list with numerals"; 149 | } 150 | -------------------------------------------------------------------------------- /t/TextWrap/03-intermediate.rakutest: -------------------------------------------------------------------------------- 1 | use Test; 2 | use _; 3 | plan 1; 4 | 5 | subtest "Handles basic wide Unicode", { 6 | my ($s1, $s2) = (q:to/§wide/, q:to/§hani/); 7 | 0 1 2 3 4 5 6 7 8 9 10 11 12 8 | 0 1 2 3 4 5 6 7 8 9 9 | a B C D E F G H I J 10 | K L M N O P Q R S T 11 | U V W X Y Z a b c d 12 | e f g h i j k l m n 13 | o p q r s t u v w x 14 | y z 15 | §wide 16 | 泰然自若 to stay clam down . . . . . . . 17 | 日月星辰 the sun, the moon and stars . . 18 | 按部就班 to do things in logical order . 19 | 四海兄弟 friends all around the world. . 20 | 心想事成 all wishes come true. . . . . . 21 | §hani 22 | say &wrap-words; 23 | wrap-words($s1.lines.join(' ') ~"\n", :length(30)).&is: $s1, "Handles basic wide unicode"; 24 | wrap-words($s2.lines.join(' ') ~"\n", :length(40)).&is: $s2, "Handles characters from the Han script"; 25 | } 26 | --------------------------------------------------------------------------------