-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
5b70b68
commit a962313
Showing
18 changed files
with
867 additions
and
3 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
%% title: How to Make a Perl 6 Module (Bit Rot Thursday) | ||
%% date: 2016-02-04 | ||
|
||
Happy [Bit Rot Thursday](http://blogs.perl.org/users/zoffix_znet/2016/01/bit-rot-thursday.html)! This week I'm taking care of fixing minor issues with packaging of my Perl 6 modules, and so, I'll talk about the general process of | ||
releasing a Perl 6 module. Let's dive in! | ||
|
||
## Prelude | ||
|
||
Perl 6 is a brand new language, so there's yet no well-established module system like what Perl 5 has, but there is a work in progress. Thus, I'll first describe the process for our temporary GitHub-based system and then I'll talk about the PAUSE-based system that's being worked on. | ||
|
||
There are some [tools and helpers available](http://doc.perl6.org/language/modules-extra) to help with the process of module development, but they are beyond the | ||
scope of this post. | ||
|
||
## Terms (and no conditions) | ||
|
||
* **Ecosystem**—collection of Perl 6 modules and scripts, viewable at [modules.perl6.org](http://modules.perl6.org/) with META-data listed in the | ||
[perl6/ecosystem repo](https://github.com/perl6/ecosystem/) | ||
* [**panda**](https://github.com/tadzik/panda/)—a Perl 6 module installer | ||
* [**zef**](https://github.com/ugexe/zef)—an alternative Perl 6 module installer | ||
* [**repo**](https://en.wikipedia.org/wiki/Software_repository)—the files of a project hosted somewhere, like on GitHub | ||
* **PR**—abbreviation for "Pull Request"; a request on GitHub to apply a change to some files in a project | ||
|
||
## So, you want to write a Perl 6 module? | ||
|
||
If so, awesome! Congratulations on your decision to become a member of the elite, exclusive, limited-time, offer-expires-soon team of about 130 developers who currently own the 549 modules that comprise the | ||
[Perl 6 Ecosystem](http://modules.perl6.org/). | ||
|
||
There are plenty of things that need to be written and if you're still having | ||
trouble coming up with ideas for something to code, check out the | ||
[Most Wanted list](https://github.com/perl6/perl6-most-wanted/blob/master/most-wanted/modules.md). | ||
|
||
You can publish Perl 6 modules as well as scripts (executables). | ||
|
||
## The Files | ||
|
||
|
||
META6.json | ||
README.md | ||
.travis.yml | ||
.gitignore | ||
bin/baker.p6 | ||
lib/Bread/Baker.pm6 | ||
resources/recipe.txt | ||
t/00-test-bake.t | ||
xt/00-ensure-bread-is-tasty.t | ||
|
||
|
||
The above shows the possible files and directories your distribution would | ||
have. The `bin/` directory is for executables, `lib/` is for modules, | ||
`resources/` is for additional resources, such as images or templates, | ||
`t/` is for tests to be run by the user, and `xt/` is for your author tests | ||
that are not run as part of the installation process. The documentation can be | ||
included in the same file as code in POD6 format, but since the current system uses | ||
GitHub, a README.md makes it much easier to read the docs. | ||
|
||
Also, you are encouraged to enable [Travis](https://travis-ci.org/) testing, hence the | ||
included `.travis.yml` file. You can use either a [simple config file](https://docs.travis-ci.com/user/languages/perl6) or [a more advanced version written by ugexe](https://github.com/ugexe/P6TCI). | ||
|
||
Most important of all is the `META6.json` file. It's a distribution metafile | ||
in JSON format that specifies what stuff your distro provides, as well as its | ||
prerequisites and authorship information. You can learn what all the keys are for | ||
in [S22 Speculation](http://design.perl6.org/S22.html#META6.json) or look at | ||
[a sample META file](https://raw.githubusercontent.com/zoffixznet/perl6-IO-MiddleMan/master/META6.json). This is a place where many errors happen, so I encourage you to use [Test::META](http://modules.perl6.org/dist/Test::META) that will spot all the common mistakes. | ||
|
||
Lastly, `.gitignore` is a file where you can list things for `git` to ignore and not include in your repo. For a start, you'll want to add single line `lib/.precomp` into it. This is the directory created by Rakudo to store precompiled files when you run your tests, for example, and you don't need to store it anywhere. | ||
|
||
## Add to Ecosystem (The Now) | ||
|
||
Currently, the authors host their modules as repos on | ||
[GitHub](https://github.com/), so place your files there. It'll require some understanding of [how to use git](http://www.learnenough.com/git-tutorial). | ||
|
||
Grab a link to | ||
the **raw** view of your META file. You can get to it by clicking the `"Raw"` | ||
button in the top, right corner of the file view on GitHub. It'll be a link | ||
akin to `https://raw.githubusercontent.com/zoffixznet/perl6-IO-MiddleMan/master/META6.json` | ||
|
||
<img alt="arrow.png" src="http://blogs.perl.org/users/zoffix_znet/arrow.png" width="589" height="301" class="mt-image-none" style="" /> | ||
|
||
Go to the [META.list file in perl6/ecosystem repo](https://github.com/perl6/ecosystem/blob/master/META.list). You can edit that file directly (and submit a PR) by clicking the pencil icon in top, right corner on GitHub, or fork the repo and submit a PR using other means. In that file, on a separate line, add the link to your dist's META file. | ||
|
||
After 1–2 hours after your PR is merged, the build cron job will list your | ||
module on [modules.perl6.org](http://modules.perl6.org). If it's still | ||
missing, check the [build log](http://modules.perl6.org/update.log) for any | ||
errors; you can just search the page for term `[error]` | ||
|
||
Keep in mind that `panda` doesn't fetch a new ecosystem list on each run, | ||
so if you want to install your freshly-added module, you need to run | ||
`panda update` first. | ||
|
||
## Add to Ecosystem (The Future) | ||
|
||
Perl 5's model goes something like this: you upload stuff on PAUSE, it gets | ||
propagated to all sorts of mirrors (CPAN), you can search for things using | ||
[MetaCPAN](https://metacpan.org/), and you install those things from one | ||
of the mirrors using a CPAN client, like `cpanm`. Wouldn't it be sweet for | ||
Perl 6 folks to get in on that action? | ||
|
||
Well, you can! Unless you're reading this after | ||
the [world was destroyed by a nuclear catastrophe](https://www.youtube.com/watch?v=SZTKyOj8gjM), you can log in onto [PAUSE](http://pause.perl.org/) **right this second** and upload a Perl 6 dist. | ||
|
||
Providing your dist looks proper and contains `META6.json` file, all you need | ||
to do is choose `Perl6` as the `Target Directory` on the [dist upload page](https://pause.perl.org/pause/authenquery?ACTION=add_uri). | ||
|
||
Now, just because you uploaded a Perl 6 dist doesn't mean it'll show up on | ||
[MetaCPAN](https://metacpan.org/); it's the whole point of specifying | ||
the `Perl6` target dir. There will be a Perl 6 version of the MetaCPAN hosted elsewhere. That MetaCPAN will be a modified version of the Perl 5's MetaCPAN under the hood. | ||
|
||
Currently, that work is being done by brave pioneers like jdv79, ranguard, and | ||
anyone else who I left out due to my ignorance. Having more Volunteers would | ||
certainly be helpful, and if you seek fame and recognition, you should stop | ||
by [#perl6-toolchain channel on irc.Freenode.net](irc://irc.freenode.net/#perl6-toolchain) and offer a helping hand. | ||
|
||
Hopefully, you found this article helpful and I await your contributions to the Perl 6 Ecosystem! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
%% title: .rotor: The King of List Manipulation | ||
%% date: 2016-01-29 | ||
|
||
Rotor. The word makes a mechanic think about brakes, an electrical engineer about motors, and a fan of [Red Letter Media YouTube channel](https://www.youtube.com/watch?v=s76vZATqLrE) about poorly executed films. But to a Perl 6 programmer, [`.rotor`](http://docs.perl6.org/routine/rotor) is a powerful tool for list operations. | ||
|
||
## Break up into chunks | ||
|
||
At its simplest, `.rotor` takes an integer and breaks up a list into sublists with that many elements: | ||
|
||
say <a b c d e f g h>.rotor: 3; | ||
# OUTPUT: ((a b c) (d e f)) | ||
|
||
We have a list of 8 elements, we called `.rotor` on it with argument `3` and we received two [Lists](http://docs.perl6.org/type/List), with 3 elements each. The last two elements of the original list we | ||
not included, because they don't make up a complete, 3-element list. That can be rectified, however, using | ||
the `:partial` named argument set to `True`: | ||
|
||
say <a b c d e f g h>.rotor: 3, :partial; | ||
# OUTPUT: ((a b c) (d e f) (g h)) | ||
say <a b c d e f g h>.rotor: 3, :partial(True); | ||
# OUTPUT: ((a b c) (d e f) (g h)) | ||
say <a b c d e f g h>.rotor: 3, :partial(False); | ||
# OUTPUT: ((a b c) (d e f)) | ||
|
||
Here's what we've learned so far, used as a crude means to fit a chunk of text into 10-column width: | ||
|
||
"foobarberboorboozebazmeow".comb.rotor(10, :partial)».join».say | ||
# OUTPUT: | ||
# foobarberb | ||
# oorboozeba | ||
# zmeow | ||
|
||
We broke up the string into individual letters with `.comb` method, then we `.rotor` them into 10-element lists, | ||
specifying that `:partial` lists are fine too, and lastly we use a pair of hyper method calls to `.join` and `.say` each of those sublists. | ||
|
||
## Mind The Gap | ||
|
||
Say, you're receiving input: you get a word, its French translation, and its Spanish translation, and so on for a whole bunch of words. You want to output only a particular language, so we need to somehow skip over some items in our list. `.rotor` to the rescue! | ||
|
||
Specifying a [Pair](http://docs.perl6.org/type/Pair) of integers as the argument makes `.rotor` break up the list into `$key` elements, with `$value` gap in between. Easier to see in the example: | ||
|
||
say ^10 .rotor: 3 => 1, :partial; | ||
# OUTPUT: ((0 1 2) (4 5 6) (8 9)) | ||
say ^10 .rotor: 2 => 2, :partial; | ||
# OUTPUT: ((0 1) (4 5) (8 9)) | ||
|
||
On the first line, we have a range of integers from `0` to `9`, we're asking `.rotor` to break that up into lists | ||
of 3 elements (including partial lists) and use a gap of `1`. And indeed, you can see the output is missing number | ||
`3` as well as `7`. Those are the gaps we skipped. In the second example, we've increased the gap to `2`, and broke | ||
up the list into 2-element sublists: the `2`, `3`, `6`, and `7` are the numbers that fell into gaps and were not included. Back to our exquisitely convoluted translations program: | ||
|
||
enum <English French Spanish>; | ||
say join " ", <Good Bon Buenos morning matin días>[French..*].rotor: 1 => 2; | ||
# OUTPUT: Bon matin | ||
|
||
We cheatsy-doodle with an `enum` and then use the `[LANG..*]` to toss the head of the list. The `French` in our | ||
example is `enum`erated into integer `1`, which means `[1..*]` gets rid of the first element. Then, we use `.rotor` to make | ||
1-element lists with a 2-element gap. This makes it skip over the words for languages we're not interested in. | ||
|
||
Now, I'm sure some in the audence are currently throwing tomatoes at me and saying I'm going mental with my examples here... Let's look at something more real-worldly. | ||
|
||
## Overlaps | ||
|
||
You have a list and you want to Do Things™ based on whether the next item is the same as the current one. Typically, | ||
this would involve a loop and a variable holding the index. You check the `index+1`, while also checking you've | ||
not reached the upper boundary of the list. Sounds tedious. Let's use `.rotor` instead! | ||
|
||
We've already learned above that using a Pair we can introduce gaps, but what if we make the gap negative? It actually works! | ||
|
||
say <a a b c c c d>.rotor: 2 => -1; | ||
# OUTPUT: ((a a) (a b) (b c) (c c) (c c) (c d)) | ||
say <a a b c c c d>.rotor(2 => -1).map: {$_[0] eq $_[1] ?? "same" !! "different"}; | ||
# OUTPUT: (same different different same same different) | ||
|
||
On the first line, we're just printing the results from `.rotor` to see what they look like and on the second line, | ||
we're performing the actual comparison and acting accordingly. Looking at the first result: we get 2-element lists, | ||
where the first element is the element from the original list and the second element is the one that follows it. That | ||
is, were we to print just the first elements of our sublists, we'd receive our original list back, minus the last element. The second elements are all just a bonus! | ||
|
||
## All Out | ||
|
||
A single `Int` or a `Pair` are not the only thing `.rotor` can accept. You can specify additional positional parameters that are `Int`s or `Pair`s to break up lists into sublists of different sizes, with different gaps between | ||
them. | ||
|
||
Say, I have a custom daemon that creates logs about users that access it. The log is in plain text, each record | ||
follows the other. Records are multi-line and always look something like this (two records + separator shown): | ||
|
||
IP: 198.0.1.22 | ||
Login: suser | ||
Time: 1454017107 | ||
Resource: /report/accounting/x23gs | ||
Input: x=42,y=32 | ||
Output: success | ||
=================================================== | ||
IP: 198.0.1.23 | ||
Login: nanom | ||
Time: 1454027106 | ||
Resource: /report/systems/boot | ||
Input: mode=standard | ||
Output: success | ||
|
||
Each item contains a "header" with user information and resource they tried to access, as well as the "operation" | ||
they wanted to execute. In addition, each item is separated by a double-line. I would like to print the header and | ||
the executed operation, and I want `Resource:` to be present in both. | ||
|
||
To parse this, we could use [Grammars](http://docs.perl6.org/language/grammars), but `.rotor` can do the trick too: | ||
|
||
for 'report.txt'.IO.lines».indent(4).rotor( 4 => -1, 3 => 1 ) -> $head, $op { | ||
.say for "Header:", |$head, | ||
"Operation:", |$op, ''; | ||
} | ||
|
||
# OUTPUT: | ||
# Header: | ||
# IP: 198.0.1.22 | ||
# Login: suser | ||
# Time: 1454017107 | ||
# Resource: /report/accounting/x23gs | ||
# Operation: | ||
# Resource: /report/accounting/x23gs | ||
# Input: x=42,y=32 | ||
# Output: success | ||
# | ||
# Header: | ||
# IP: 198.0.1.23 | ||
# Login: nanom | ||
# Time: 1454027106 | ||
# Resource: /report/systems/boot | ||
# Operation: | ||
# Resource: /report/systems/boot | ||
# Input: mode=standard | ||
# Output: success | ||
|
||
We fetch lines from file `report.txt` with `'report.txt'.IO.lines`. To make output prettier, we indent each line | ||
with 4 spaces by calling `.indent(4)` on each item using the hyper operator (`»`). Now comes `.rotor`! | ||
We use it to break up lines into repeating chunks of 4 and 3 items: that's items for our header | ||
and our operation. After grabbing the 4-element chunk, we use a negative gap to backtrack and include `Resource:` | ||
line in our operation chunk as well. In the 3-element chunk, we use a positive gap, to skip over the separator line. | ||
|
||
Afterwards, we use a `for` loop and give it two variables with `-> $head, $op`, so it loops over two items at a time. | ||
Inside the loop, we merely print each log item onto the screen. Since `$head` and `$op` are Lists, we use the pipe | ||
(`|`) character to [slip](http://docs.perl6.org/type/Slip) them in. | ||
|
||
Keep in mind, the pattern you supply to `.rotor` can be dynamically generated! Here we use a sine to generate it: | ||
|
||
say ^92 .rotor( | ||
(0.2, 0.4 ... 3).map: (10 * *.sin).Int # pattern we supply to .rotor | ||
).join: "\n"' | ||
|
||
# OUTPUT: | ||
# 0 | ||
# 1 2 3 | ||
# 4 5 6 7 8 | ||
# 9 10 11 12 13 14 15 | ||
# 16 17 18 19 20 21 22 23 | ||
# 24 25 26 27 28 29 30 31 32 | ||
# 33 34 35 36 37 38 39 40 41 | ||
# 42 43 44 45 46 47 48 49 50 | ||
# 51 52 53 54 55 56 57 58 59 | ||
# 60 61 62 63 64 65 66 67 68 | ||
# 69 70 71 72 73 74 75 76 | ||
# 77 78 79 80 81 82 | ||
# 83 84 85 86 87 | ||
# 88 89 90 | ||
# 91 | ||
|
||
So whether you're an electrician, mechanic, or anyone else, I hope you'll find `.rotor` a useful multipurpose tool. | ||
|
||
## Update 1: | ||
|
||
It's been pointed out to me that | ||
|
||
"foobarberboorboozebazmeow".comb.rotor(10, :partial)».join».say | ||
|
||
Is better written as | ||
|
||
"foobarberboorboozebazmeow".comb(10)».say |
File renamed without changes.
File renamed without changes.
File renamed without changes.
Oops, something went wrong.