├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── .perlcriticrc
├── .perlfiles
├── .perltidyrc
├── .update-manifest.exclude
├── COPYING.md
├── Changes
├── MANIFEST
├── MANIFEST.SKIP
├── Makefile.PL
├── README.md
├── bench
└── globs
├── bin
├── expand-tail
├── fperl
├── perlrepl
└── repl2test
├── critic-todo
├── docs
├── HACKING.md
├── TODO.md
├── blog
│ ├── index.md
│ ├── perl-weekly-challenges-113.md
│ └── perl_weekly_challenges_114.md
├── contact.md
├── design.md
├── howto.md
├── ideas.md
├── intro.md
├── links.md
├── mailing_list.md
└── names.md
├── examples
├── README.md
├── csv-to-json
├── csv_to_xml
├── csv_to_xml-example.csv
├── csv_to_xml_short
├── dbi
├── definitionlists
├── diff_to_html
├── existing_files
├── fibs
├── find-maildirs
├── functional-classes
├── gen-csv
├── goto
├── hiring-without-whiteboards
├── log-sensors
├── logic
├── logwatch
├── logwatch_exampleconfig.pl
├── maplines
├── maplines-example.pl
├── pdf-to-html
├── perl-weekly-challenges
│ ├── .gitignore
│ ├── 111-1-search_matrix
│ ├── 113-1-represent_integer
│ ├── 113-1-represent_integer_haskell.hs
│ ├── 113-2-recreate_binary_tree
│ ├── 114-1-Next_Palindrome_Number
│ └── Makefile
├── predicates
├── primes
├── sendprepare
├── skip
├── template
├── treestat
└── youtube-extract
├── functional_XML
├── README.md
├── TODO.md
├── t
│ ├── div
│ ├── stream
│ └── stream_
├── test
└── testlazy
├── htmlgen
├── FunctionalPerl
│ └── Htmlgen
│ │ ├── Cost.pm
│ │ ├── FileUtil.pm
│ │ ├── Htmlparse.pm
│ │ ├── Linking.pm
│ │ ├── MarkdownPlus.pm
│ │ ├── Mediawiki.pm
│ │ ├── Nav.pm
│ │ ├── PXMLMapper.pm
│ │ ├── PathTranslate.pm
│ │ ├── PathUtil.pm
│ │ ├── PerlTidy.pm
│ │ ├── Sourcelang.pm
│ │ ├── Toc.pm
│ │ ├── UriUtil.pm
│ │ └── default_config.pm
├── README.md
├── TODO.md
├── gen
└── htmlgen.css
├── intro
├── basics
├── more_tailcalls
└── tailcalls
├── lib
├── Chj
│ ├── Backtrace.pm
│ ├── BinHexOctDec.pm
│ ├── BuiltinTypePredicates.pm
│ ├── CPAN
│ │ └── ModulePODUrl.pm
│ ├── Class
│ │ ├── Array.pm
│ │ └── methodnames.pm
│ ├── Destructor.pm
│ ├── HTTP
│ │ └── Daemon.pm
│ ├── IO
│ │ ├── Command.pm
│ │ ├── CommandCommon.pm
│ │ ├── Dir.pm
│ │ ├── File.pm
│ │ ├── Pipe.pm
│ │ ├── PipelessCommand.pm
│ │ ├── Tempdir.pm
│ │ ├── Tempfile.pm
│ │ └── WrappedFile.pm
│ ├── IsPerl.pm
│ ├── Linux
│ │ └── LmSensors.pm
│ ├── NamespaceClean.pm
│ ├── NamespaceCleanAbove.pm
│ ├── Package
│ │ └── OfPath.pm
│ ├── Packages.pm
│ ├── Serialize.pm
│ ├── TEST.pm
│ ├── TerseDumper.pm
│ ├── Unix
│ │ ├── Exitcode.pm
│ │ └── Signal.pm
│ ├── Util
│ │ └── AskYN.pm
│ ├── chompspace.pm
│ ├── constructorexporter.pm
│ ├── noTEST.pm
│ ├── pp.pm
│ ├── ruse.pm
│ ├── singlequote.pm
│ ├── singlequote
│ │ └── t.pm
│ ├── tempdir.pm
│ ├── time_this.pm
│ ├── xIO.pm
│ ├── xIOUtil.pm
│ ├── xhome.pm
│ ├── xopen.pm
│ ├── xopendir.pm
│ ├── xoutpipe.pm
│ ├── xperlfunc.pm
│ ├── xperlfunc
│ │ └── t.pm
│ ├── xpipe.pm
│ └── xtmpfile.pm
├── FP
│ ├── AST
│ │ └── Perl.pm
│ ├── Abstract
│ │ ├── Compare.pm
│ │ ├── Equal.pm
│ │ ├── Id.pm
│ │ ├── Interface.pm
│ │ ├── Map.pm
│ │ ├── Pure.pm
│ │ ├── Sequence.pm
│ │ ├── Sequence
│ │ │ └── t.pm
│ │ └── Show.pm
│ ├── Array.pm
│ ├── Array
│ │ └── Mixin.pm
│ ├── Array_sort.pm
│ ├── BigInt.pm
│ ├── Carp.pm
│ ├── Char.pm
│ ├── Cmp.pm
│ ├── Collection.pm
│ ├── Combinators.pm
│ ├── Combinators2.pm
│ ├── Currying.pm
│ ├── DBI.pm
│ ├── Div.pm
│ ├── Docstring.pm
│ ├── Docstring
│ │ └── t.pm
│ ├── DumperEqual.pm
│ ├── Either.pm
│ ├── Equal.pm
│ ├── Equal
│ │ └── t.pm
│ ├── Failure.pm
│ ├── Git
│ │ └── Repository.pm
│ ├── Hash.pm
│ ├── HashSet.pm
│ ├── IOStream.pm
│ ├── Id.pm
│ ├── Interface.pm
│ ├── Interfaces.pm
│ ├── Iptables
│ │ └── Rules.pm
│ ├── JSON.pm
│ ├── Lazy.pm
│ ├── Lazy
│ │ └── t.pm
│ ├── List.pm
│ ├── List
│ │ └── t.pm
│ ├── Memoizing.pm
│ ├── Mixin
│ │ └── Utils.pm
│ ├── MutableArray.pm
│ ├── Ops.pm
│ ├── Optional.pm
│ ├── OrderedCollection.pm
│ ├── Path.pm
│ ├── Path
│ │ └── t.pm
│ ├── Predicates.pm
│ ├── PureArray.pm
│ ├── PureArray
│ │ └── t.pm
│ ├── PureHash.pm
│ ├── RegexMatch.pm
│ ├── Repl.pm
│ ├── Repl
│ │ ├── AutoTrap.pm
│ │ ├── Dependencies.pm
│ │ ├── Repl.pm
│ │ ├── Stack.pm
│ │ ├── StackPlus.pm
│ │ ├── Trap.pm
│ │ ├── WithRepl.pm
│ │ └── corefuncs.pm
│ ├── Show.pm
│ ├── Show
│ │ └── t.pm
│ ├── SortedPureArray.pm
│ ├── Stream.pm
│ ├── StrictList.pm
│ ├── Struct.pm
│ ├── Struct
│ │ ├── Equal.pm
│ │ └── Show.pm
│ ├── Text
│ │ └── CSV.pm
│ ├── Trampoline.pm
│ ├── TransparentLazy.pm
│ ├── Trie.pm
│ ├── Trie
│ │ └── t.pm
│ ├── Untainted.pm
│ ├── Values.pm
│ ├── Weak.pm
│ ├── Weak
│ │ └── t.pm
│ ├── autobox.pm
│ ├── fix.pm
│ ├── noLazy.pm
│ └── url_.pm
├── FunctionalPerl.pm
├── PXML.pm
└── PXML
│ ├── Element.pm
│ ├── HTML5.pm
│ ├── Preserialize.pm
│ ├── Preserialize
│ └── t.pm
│ ├── SVG.pm
│ ├── Serialize.pm
│ ├── Serialize
│ └── t.pm
│ ├── Tags.pm
│ ├── Util.pm
│ └── XHTML.pm
├── licenses
└── artistic_license_2.0.md
├── meta
├── FunctionalPerl
│ ├── Dependencies.pm
│ ├── Dependencies
│ │ └── ChjBin.pm
│ ├── Indexing.pm
│ ├── ModuleList.pm
│ └── TailExpand.pm
├── bin
│ └── gpg
├── bisect-modules
├── check-hardcoded-perl
├── code-reformat
├── copyrightyearcheck
├── critic
├── dependencycheck
├── find-perl.pl
├── install-development-dependencies-on-debian
├── perlfiles
├── pre-commit
├── readin.pl
├── release
├── t-check
├── tail-expand
├── test-modules
├── test.pl
├── update-manifest
├── update-pod
└── with-profiling
├── t-extra
├── evil-env.t
└── out-of-order.t
├── t-slow
└── csvstreams.t
├── t
├── csv_to_xml.expected
├── csv_to_xml.t
├── dbi.t
├── examples-csv-to-json.data
│ ├── a.csv
│ ├── a.json
│ ├── a.mint
│ ├── a_auto-integers.json
│ └── a_auto-numbers.json
├── examples-csv-to-json.t
├── examples-fibs.t
├── examples-functional-classes.t
├── examples-hiring-without-whiteboards.t
├── examples-logic.t
├── examples-perlweekly-111-1.t
├── examples-perlweekly-113-1.t
├── examples-perlweekly-113-2.t
├── examples-primes.t
├── examples-sendprepare
├── functional_XML-t-div.t
├── functional_XML-test.expected
├── functional_XML-test.t
├── htmlgen.t
├── intro-basics.t
├── maintainer
│ └── perhaps
├── perl-goto-leak.t
├── perl-weaken-coderef-correctness.t
├── perl-weaken-coderef.t
├── perl
│ ├── __SUB__-gc
│ ├── goto-leak
│ ├── weaken-coderef
│ ├── weaken-coderef-alternative
│ ├── weaken-coderef-alternative-FP
│ ├── weaken-coderef-alternative-__SUB__
│ ├── weaken-coderef-alternative-fix
│ ├── weaken-coderef-alternative-local
│ └── weaken-coderef-simplified
├── pod_snippets.t
├── predicates.t
├── repl.t
├── require_and_run_tests.t
├── skip-internal.t
├── skip-leak.t
├── skip.input
├── skip.t
├── testlazy.expected
├── testlazy.t
├── testlazy10.expected
├── testlazy10.t
└── trampoline-fix.t
├── testmem.pl
└── website
├── FP-logo.png
├── FP.css
├── gen
├── gen-config.pl
├── logo.pl
└── sync
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | # This sets up the page shown at [1]--continuous integration via
2 | # GitHub's Actions[2]. Feel free to add particular environments you
3 | # feel worthy of testing and proving useful in addition to those from
4 | # CPAN Testers, but let's keep in mind that, unlike GitHub, CPAN
5 | # Testers is a community effort and probably valuable as a way to
6 | # remain independent from big companies. And please let's make sure
7 | # that nobody *has* to use GitHub to contribute to the project or
8 | # becomes a second class contributor if they refuse to use it.
9 |
10 | # [1] https://github.com/pflanze/functional-perl/actions
11 | # [2] https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions
12 |
13 | name: CI
14 |
15 | on: [push]
16 |
17 | jobs:
18 | perl-job:
19 | runs-on: ubuntu-latest
20 | container:
21 | image: perl:${{ matrix.perl-version }}
22 | strategy:
23 | fail-fast: false
24 | matrix:
25 | perl-version:
26 | - '5.32'
27 | - 'latest'
28 | name: Perl ${{ matrix.perl-version }}
29 | steps:
30 | - uses: actions/checkout@v2
31 | - name: Regular tests
32 | run: |
33 | cpanm --installdeps --notest .
34 | perl Makefile.PL
35 | make
36 | make test
37 | - name: Extended tests
38 | run: |
39 | cpanm --notest PadWalker FP::Repl::Dependencies Method::Signatures Text::Markdown Moo Perl::Tidy Term::ReadLine::Gnu
40 | perl Makefile.PL
41 | make
42 | make test
43 |
44 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | README.xhtml
2 | t/testlazy-out
3 | out.xhtml
4 | t/test-a.csv
5 | t/test-a.xml
6 | t/examples-csv-to-json.data/
7 | .expansion-*
8 | /website/www/
9 | MYMETA.yml
10 | MYMETA.json
11 | Makefile
12 | Makefile.old
13 | blib/
14 | pm_to_blib
15 | *.tar.gz
16 | *.tar.gz.[1-9]
17 | *.tar.gz.[1-9][0-9]
18 | website/.ModulePODUrl-cache/
19 | /SIGNATURE
20 |
21 | # expansions from meta/tail-expand
22 | .*/
23 | .test.pl
24 |
25 | # Backup files from perltidy
26 | *.bak
27 |
28 | # t/pod_snippets.t outputs:
29 | .xIO-test-out
30 |
--------------------------------------------------------------------------------
/.perlcriticrc:
--------------------------------------------------------------------------------
1 | # https://metacpan.org/pod/Perl::Critic::Community
2 | # Or, 'freenode' in older version.
3 | theme = freenode
4 | severity = 1
5 |
--------------------------------------------------------------------------------
/.perltidyrc:
--------------------------------------------------------------------------------
1 | # NOTE: I'm using perltidy v20200110. Also see docs/HACKING.md
2 |
3 | -pbp # Start with Perl Best Practices
4 | -iob # Ignore old breakpoints
5 | -l=80 # characters per line
6 | -vt=0 # Less vertical tightness
7 | -pt=2 # parenthesis tightness
8 | -bt=1 # brace tightness
9 | -bbt=1 # block brace tightness
10 | -sbt=2 # square bracket tightness
11 | -isbc # Don't indent comments without leading space
12 | -nst # Don't output to STDOUT
13 | -nasc # Do not add semicolons to tail expressions (they are used to
14 | # indicate that the value is explicitly used as the return value
15 | -ce # --cuddled-else
16 | -i=4 # columns per indentation level
17 | -conv # --converge
18 | -tso # --tight-secret-operators 0+ +0 ()x!! ~~<> ,=> =( )=
19 | -nsfs # --nospace-for-semicolon Semicolons within for loops
20 |
21 | -b # --backup-and-modify-in-place
22 | #-w # Show all warnings
23 |
--------------------------------------------------------------------------------
/.update-manifest.exclude:
--------------------------------------------------------------------------------
1 | .gitignore
2 | examples/perl-weekly-challenges/.gitignore
3 | .update-manifest.exclude
4 | website/sync
5 | meta/release
6 | .github/workflows/ci.yml
7 |
--------------------------------------------------------------------------------
/MANIFEST.SKIP:
--------------------------------------------------------------------------------
1 | ^PATCHES
2 | ^\.git
3 | ^website/www
4 | ^website/.ModulePODUrl-cache
5 | ^.*\.bak
6 | ^.*~
7 | ^.*\.tar\.gz
8 | ^blib
9 | ^\.tmp
10 | ^\.htmlgen
11 | ^\.update-manifest\.exclude
12 | ^intro/\.expansion-more_tailcalls
13 | ^Makefile
14 | ^meta/release
15 | ^MYMETA
16 | ^\.xIO-test-out
17 | ^nohup
18 | ^out\.xhtml
19 | ^pm_to_blib
20 | ^website/sync
21 |
--------------------------------------------------------------------------------
/bench/globs:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env perl
2 |
3 | # Copyright (c) 2015 Christian Jaeger, copying@christianjaeger.ch
4 | # This is free software. See the file COPYING.md that came bundled
5 | # with this file.
6 |
7 | # This file checks whether passing global functions as *foo instead of
8 | # \&foo incurs (much) overhead.
9 |
10 | use strict;
11 | use warnings;
12 | use warnings FATAL => 'uninitialized';
13 |
14 | # Find modules from the functional-perl working directory (not
15 | # installed)
16 | use Cwd 'abs_path';
17 | our ($mydir, $myname);
18 |
19 | BEGIN {
20 | my $location = (-l $0) ? abs_path($0) : $0;
21 | $location =~ /(.*?)([^\/]+?)_?\z/s or die "?";
22 | ($mydir, $myname) = ($1, $2);
23 | }
24 | use lib "$mydir/../lib";
25 |
26 | # for development/debugging
27 | #use Chj::ruse; # get the 'ruse' procedure which will reload modules;
28 | # since we're putting the meat of the program into the
29 | # main file, this won't help us here.
30 | use Chj::Backtrace; # show backtraces when an error happens
31 | use FP::Repl; # get the 'repl' procedure.
32 | use Chj::TEST;
33 |
34 | # ------------------------------------------------------------------
35 | use Chj::time_this;
36 | use FP::Div "inc";
37 |
38 | our $n = 1500000;
39 |
40 | sub t {
41 | my ($f) = @_;
42 | my $z = 0;
43 | for (1 .. $n) {
44 | $z = &$f($z)
45 | }
46 | $z
47 | }
48 |
49 | sub tim {
50 | time_this { t *inc } " *";
51 | time_this { t \&inc } "\\&";
52 | }
53 |
54 | tim for 1 .. 3;
55 |
56 | #perhaps_run_tests "main" or repl;
57 |
--------------------------------------------------------------------------------
/bin/fperl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env perl
2 |
3 | # Copyright (c) 2015-2019 Christian Jaeger, copying@christianjaeger.ch
4 | # This is free software. See the file COPYING.md that came bundled
5 | # with this file.
6 |
7 | # load repl with most of the more interesting functional-perl packages
8 | # loaded
9 |
10 | use strict;
11 | use warnings;
12 | use warnings FATAL => 'uninitialized';
13 |
14 | use Cwd 'abs_path';
15 | our ($mydir, $myname);
16 |
17 | BEGIN {
18 | my $location = (-l $0) ? abs_path($0) : $0;
19 | $location =~ /(.*?)([^\/]+?)_?\z/s or die "?";
20 | ($mydir, $myname) = ($1, $2);
21 | }
22 |
23 | my $repl = "$mydir/perlrepl";
24 |
25 | exec $^X, $repl, qw(
26 | --name fperl
27 | -t
28 | -m Method::Signatures
29 | -m Function::Parameters=:strict
30 | -m Sub::Call::Tail
31 | -M FunctionalPerl=:all
32 | ), @ARGV or exit 127;
33 |
34 |
--------------------------------------------------------------------------------
/docs/HACKING.md:
--------------------------------------------------------------------------------
1 | Check the [functional-perl website](http://functional-perl.org/) for
2 | properly formatted versions of these documents.
3 |
4 | ---
5 |
6 | # Guidelines to hack on the functional-perl project
7 |
8 | See also [[design]].
9 |
10 |
11 | ## Style
12 |
13 | * `XXX` in comments in source code is used to mark important
14 | unfinished work, `XX` is used for "should probably be
15 | improved, but not essential under normal working conditions".
16 | In text files, 'todo' is used.
17 |
18 | * `perltidy` v20200110 is used to format all Perl code. To have this
19 | done automatically for each commit, run `cd .git/hooks && ln -s
20 | ../../meta/pre-commit`.
21 |
22 | ## Testing
23 |
24 | ### Perl issues
25 |
26 | Tests that depend on the Perl core being fixed are only run if the
27 | `TEST_PERL` env variable is true. I.e. run
28 |
29 | TEST_PERL=1 make test
30 |
31 | or similar.
32 |
33 |
--------------------------------------------------------------------------------
/docs/blog/index.md:
--------------------------------------------------------------------------------
1 | Check the [functional-perl website](http://functional-perl.org/) for
2 | properly formatted versions of these documents.
3 |
4 | ---
5 |
6 | # The Functional Perl blog
7 |
8 | Articles, just like all pages on this website, can be edited via the
9 | 'edit' link near the top right of each page. You can also send
10 | [me](mailto:ch@christianjaeger.ch) your change by email. If you've got
11 | any comments, please just send them to me by
12 | [email](mailto:ch@christianjaeger.ch) until I've added proper
13 | commenting functionality. If you indicate that it's for the public,
14 | I'll post it here, with your first name and initial of the last name
15 | but I won't post your mail address.
16 |
17 | ## Entries
18 |
19 | * [The Perl Weekly Challenges, #113](perl-weekly-challenges-113.md) (2021/05/23)
20 |
21 | * [The Perl Weekly Challenges, #114](perl_weekly_challenges_114.md) (2021/05/30)
22 |
23 |
--------------------------------------------------------------------------------
/docs/contact.md:
--------------------------------------------------------------------------------
1 | Check the [functional-perl website](http://functional-perl.org/) for
2 | properly formatted versions of these documents.
3 |
4 | ---
5 |
6 | # Contact
7 |
8 | The project is currently led by [Christian
9 | Jaeger](http://leafpair.com/contact).
10 |
11 | See [[mailing_list]] for list and IRC details.
12 |
--------------------------------------------------------------------------------
/docs/mailing_list.md:
--------------------------------------------------------------------------------
1 | Check the [functional-perl website](http://functional-perl.org/) for
2 | properly formatted versions of these documents.
3 |
4 | ---
5 |
6 | # Community
7 |
8 | ## Mailing list
9 |
10 | Send user and development questions and bug reports to the public
11 | mailing list at
12 | [functional-perl-list@functional-perl.org](mailto:functional-perl-list@functional-perl.org). A
13 | subscription is not needed to post. Messages that are considered by
14 | the spam filter to look somewhat spammy are moderated.
15 |
16 | To subscribe to the list, send an empty email to
17 | [functional-perl-list-subscribe@functional-perl.org](mailto:functional-perl-list-subscribe@functional-perl.org). To
18 | unsubscribe, send an empty email to
19 | [functional-perl-list-unsubscribe@functional-perl.org](mailto:functional-perl-list-unsubscribe@functional-perl.org).
20 |
21 | Be reminded that Gmail and some other large ISPs are filtering mail
22 | very aggressively for spam, and may deliver mails from this server to
23 | the spam folder first. If necessary, please check there and move the
24 | mails out of the spam folder to train the service. If that doesn't
25 | help, please [tell](//contact.md).
26 |
27 | ## IRC
28 |
29 | Christian (pflanze) is always logged onto `#functional-perl` on
30 | `irc.perl.org` as well as `irc.libera.chat`; mention his nickname
31 | and he will get a notification via email, although he is currently not
32 | seeing that immediately anymore. You may also find him on `#perl` on
33 | either network.
34 |
35 |
--------------------------------------------------------------------------------
/docs/names.md:
--------------------------------------------------------------------------------
1 | Check the [functional-perl website](http://functional-perl.org/) for
2 | properly formatted versions of these documents.
3 |
4 | ---
5 |
6 | There are many function and module names that have doubtful names. If
7 | you've got some comments on some names, like how the same or similar
8 | functions are named in other languages or ideas on how they could be
9 | named for more consistency, please [tell](//mailing_list.md).
10 |
11 | This list of doubtful names is not exhaustive.
12 |
13 | - is it badly inconsistent to have names like `map_with_tail` but have
14 | the tail-taking function be named `rest`?
15 | - should `FORCE` from `FP::Lazy` be renamed to `Force` to avoid the
16 | potential conflict with `use PXML::Tags 'force'` ?
17 | - rename `PXML` to FXML (functional XML)?
18 | - `array_to_hash_group_by`
19 | - `compose_1side`
20 | - `pxml_map_elements_exhaustively`
21 | - should `stream_iota` be renamed or have different arguments? Compare
22 | with APL etc. Also `stream_range`, `stream_step_range`.
23 | - `FP::Repl::WithRepl`, `WithRepl_eval`, `FP::Repl::Trap`
24 | - `FP::Struct`: rename to `FP::Class` or should that name remain
25 | reserved for a new implementation on top of `Moose` or something?
26 | - should `null` always be used, including instead of `empty_trie`
27 | etc. (i.e. rename those to `null_trie` etc.)?
28 | - `poptionally` in `FP::Optional`
29 | - `flip2of3`, `rot3right`, `rot3left` in `FP::Combinators`
30 |
31 | Also:
32 |
33 | - should the 'x' prefix often used in procedure/function names to
34 | signify exceptions on errors be dropped, since we're using `maybe_`
35 | or `perhaps_` if it's not signaling exceptions? E.g. Clojure uses
36 | `lineseq` and implies exceptions: should we use such "nice and
37 | short" names, too? This one looks a bit too short, `file_lines` may
38 | be more like it. And imply exceptions? Or is it still worthwhile to
39 | remind that there are cases in rather normal execution that throw
40 | exceptions? (Also, xopen etc. are special since there the x is
41 | necessary to differentiate from the x-less builtin of the same
42 | name.)
43 |
44 |
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | Check the [functional-perl website](http://functional-perl.org/) for
2 | properly formatted versions of these documents.
3 |
4 | ---
5 |
6 | # Examples
7 |
8 | Some examples showing the possibilities using [functional-perl](../README.md).
9 |
10 | * [`fibs`](fibs) and [`primes`](primes) show translations of Haskell programs.
11 |
12 | * [`gen-csv`](gen-csv) and [`csv_to_xml`](csv_to_xml) (and the shorter
13 | variant [`csv_to_xml_short`](csv_to_xml_short)) show how to stream
14 | number series into and from CSV files and into XML.
15 |
16 | * [`diff_to_html`](diff_to_html) hows how to generate (X)HTML.
17 |
18 | * [`skip`](skip) shows how to implement a sliding window (look-ahead) as a
19 | pure function (that can easily be tested) and then uses it for I/O
20 |
21 | * [`pdf-to-html`](pdf-to-html) is a practical, small and rather clean
22 | example reading directories and generating HTML. It also shows
23 | how to wrap non-functional Perl builtins (regex matching) in pure
24 | functions.
25 |
26 |
27 | These are really just test suites, but perhaps still instructive:
28 |
29 | * [`dbi`](dbi) shows/tests usage of `FP::DBI`
30 |
31 | * [`predicates`](predicates) shows/tests `FP::Predicates`
32 |
33 | Copy [`template`](template) to create your own script.
34 |
35 |
36 | ## See also
37 |
38 | * [Htmlgen](../htmlgen/README.md), the script that generates this
39 | website.
40 |
41 | * For a real program using these modules, see
42 | [ml2json](http://ml2json.christianjaeger.ch), although it still
43 | bundles a much older version of the functional-perl libraries (todo:
44 | update to use the current functional-perl instead)
45 |
46 |
--------------------------------------------------------------------------------
/examples/csv_to_xml-example.csv:
--------------------------------------------------------------------------------
1 | A;B;C;D
2 | 1;2;"3";"4"
3 | 4;3.3;"foo";"bar"
4 |
--------------------------------------------------------------------------------
/examples/csv_to_xml_short:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env perl
2 |
3 | # Copyright (c) 2015 Christian Jaeger, copying@christianjaeger.ch
4 | # This is free software. See the file COPYING.md that came bundled
5 | # with this file.
6 |
7 | use strict;
8 | use warnings;
9 | use warnings FATAL => 'uninitialized';
10 |
11 | # find modules from functional-perl working directory (not installed)
12 | use Cwd 'abs_path';
13 | our ($mydir, $myname);
14 |
15 | BEGIN {
16 | my $location = (-l $0) ? abs_path($0) : $0;
17 | $location =~ /(.*?)([^\/]+?)_?\z/s or die "?";
18 | ($mydir, $myname) = ($1, $2);
19 | }
20 | use lib "$mydir/../lib";
21 |
22 | sub usage {
23 | print "usage: $myname csvfile xmlfile
24 |
25 | Variant of csv_to_xml with shorter code. Only supports one csvfile.
26 |
27 | ";
28 | exit(@_ ? 1 : 0);
29 | }
30 |
31 | use Getopt::Long;
32 | our $verbose = 0;
33 | GetOptions("verbose" => \$verbose, "help" => sub {usage},) or exit 1;
34 | usage unless @ARGV == 2;
35 |
36 | our ($inpath, $outpath) = @ARGV;
37 |
38 | use FP::Text::CSV qw(csv_file_to_rows);
39 | use PXML::Serialize;
40 |
41 | # create tag functions for the following XML tag names. Casing is
42 | # preserved for the output, but the tag functions are all-uppercase
43 | # (to try to avoid name conflicts and for better visibility) and
44 | # replace the minus with the underscore.
45 | use PXML::Tags qw(myexample protocol-version records record a b c d);
46 |
47 | # create a data structure describing an XML document, partially lazily
48 | MYEXAMPLE(
49 | PROTOCOL_VERSION("0.123"),
50 | RECORDS( # read lazy list of rows from CSV file
51 | csv_file_to_rows($inpath, { eol => "\n", sep_char => ";" })
52 |
53 | # skip the header row
54 | ->rest
55 |
56 | # map rows to XML elements
57 | ->map(
58 | sub {
59 | my ($a, $b, $c, $d) = @{ $_[0] };
60 | RECORD A($a), B($b), C($c), D($d)
61 | }
62 | )
63 | )
64 | )
65 |
66 | # print data structure to disk, forcing its evaluation as needed
67 | ->xmlfile($outpath);
68 |
69 | # XXX this may not actually use constant memory on your Perl. Work
70 | # still needs to be done.
71 |
72 |
--------------------------------------------------------------------------------
/examples/dbi:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env perl
2 |
3 | # Copyright (c) 2015-2019 Christian Jaeger, copying@christianjaeger.ch
4 | # This is free software. See the file COPYING.md that came bundled
5 | # with this file.
6 |
7 | use strict;
8 | use warnings;
9 | use warnings FATAL => 'uninitialized';
10 |
11 | # find modules from functional-perl working directory (not installed)
12 | use Cwd 'abs_path';
13 | our ($mydir, $myname);
14 |
15 | BEGIN {
16 | my $location = (-l $0) ? abs_path($0) : $0;
17 | $location =~ /(.*?)([^\/]+?)_?\z/s or die "?";
18 | ($mydir, $myname) = ($1, $2);
19 | }
20 | use lib "$mydir/../lib";
21 |
22 | use Chj::TEST
23 | use => "DBD::CSV",
24 | use => "FP::DBI"; # 'DBI' really, indirectly
25 | use PXML::XHTML ":all";
26 | use FP::Weak;
27 |
28 | use Chj::Backtrace;
29 |
30 | our $db = FP::DBI->connect("dbi:CSV:");
31 |
32 | $db->{csv_sep_char} = ";";
33 |
34 | our $get = $db->prepare("select * from examples/csv_to_xml-example.csv");
35 |
36 | TEST {
37 | $get->execute();
38 | my $s = $get->row_stream;
39 | TABLE(TH($s->first->map (\&TD)),
40 | $s->rest->take(10)->map (sub { TR($_[0]->map (\&TD)) }))->string;
41 | }
42 | '
';
44 |
45 | TEST {
46 | $get->execute();
47 | my $s = $get->hash_stream;
48 | [Keep($s)->length, $s->second]
49 | }
50 | [2, { a => 4, b => 3.3, c => "foo", d => "bar" }];
51 |
52 | # interlock tests:
53 |
54 | TEST {
55 | $get->execute;
56 | my $s = $get->array_stream;
57 | $s->first
58 | }
59 | [1, 2, 3, 4];
60 |
61 | TEST_EXCEPTION {
62 | $get->execute;
63 | my $s = $get->array_stream;
64 | $get->execute;
65 | $s->first
66 | }
67 | "stream was interrupted by another execute or stream request";
68 |
69 | TEST_EXCEPTION {
70 | $get->execute;
71 | my $s = $get->array_stream;
72 | my $s2 = $get->hash_stream;
73 | $s->first
74 | }
75 | "stream was interrupted by another execute or stream request";
76 |
77 | # XX leak tests?
78 |
79 | perhaps_run_tests __PACKAGE__ or do {
80 | require FP::Repl;
81 | FP::Repl::repl();
82 | }
83 |
--------------------------------------------------------------------------------
/examples/existing_files:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env perl
2 |
3 | # Copyright (c) 2015 Christian Jaeger, copying@christianjaeger.ch
4 | # This is free software. See the file COPYING.md that came bundled
5 | # with this file.
6 |
7 | use strict;
8 | use warnings;
9 | use warnings FATAL => 'uninitialized';
10 |
11 | #use Sub::Call::Tail;
12 |
13 | # find modules from functional-perl working directory (not installed)
14 | use Cwd 'abs_path';
15 | our ($mydir, $myname);
16 |
17 | BEGIN {
18 | my $location = (-l $0) ? abs_path($0) : $0;
19 | $location =~ /(.*?)([^\/]+?)_?\z/s or die "?";
20 | ($mydir, $myname) = ($1, $2);
21 | }
22 | use lib "$mydir/../lib";
23 |
24 | sub usage {
25 | print "usage: $myname ...
26 | ";
27 | exit 1;
28 | }
29 |
30 | use Getopt::Long;
31 | our $verbose = 0;
32 | GetOptions("verbose" => \$verbose, "help" => sub {usage},) or exit 1;
33 | usage if @ARGV;
34 |
35 | # Perl code from:
36 | # http://perl6maven.com/from-iterative-to-functional-perl6-code
37 |
38 | # use v6;
39 | #
40 | # my @paths = < /tmp /var/tmp >;
41 | # my $filename = 'temp123';
42 | # my @ext = ;
43 | # my @existing-files := grep *.IO.e,
44 | # (@paths X~ "/$filename." X~ @ext);
45 |
46 | use FP::Stream ":all";
47 | use FP::List qw(list list_to_string);
48 | use FP::Ops qw(the_method unary_operator);
49 |
50 | {
51 | my $paths = stream(qw< /tmp /var/tmp >);
52 | my $filename = 'temp123';
53 | my $ext = list(qw);
54 |
55 | # `the_method` corresponds to Perl 6's `*`, but we don't have an
56 | # IO::e method, and cartesian_product doesn't do the string join,
57 | # thus resort to a manual closure:
58 | my $existing_files = stream_filter sub { -e join("", @_) },
59 | stream_cartesian_product($paths, list("/$filename."), $ext);
60 | }
61 |
62 | # or
63 |
64 | my $paths = stream(qw< /tmp /var/tmp >);
65 | my $filename = 'temp123';
66 | my $ext = list(qw);
67 | my $all_paths = $paths->cartesian_product(list("/$filename."), $ext)
68 | ->map(\&list_to_string);
69 | my $existing_files = $all_paths->filter(unary_operator "-e");
70 |
71 | use FP::Repl::Trap;
72 | use FP::Repl;
73 | repl;
74 |
75 | # at the repl, enter 'F $all_paths', 'F $existing_files'
76 |
--------------------------------------------------------------------------------
/examples/gen-csv:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env perl
2 |
3 | # Copyright (c) 2015-2020 Christian Jaeger, copying@christianjaeger.ch
4 | # This is free software. See the file COPYING.md that came bundled
5 | # with this file.
6 |
7 | use strict;
8 | use warnings;
9 | use warnings FATAL => 'uninitialized';
10 | use experimental "signatures";
11 |
12 | # find modules from functional-perl working directory (not installed)
13 | use Cwd 'abs_path';
14 | our ($mydir, $myname);
15 |
16 | BEGIN {
17 | my $location = (-l $0) ? abs_path($0) : $0;
18 | $location =~ /(.*?)([^\/]+?)_?\z/s or die "?";
19 | ($mydir, $myname) = ($1, $2);
20 | }
21 | use lib "$mydir/../lib";
22 |
23 | sub usage {
24 | print "usage: $myname outpath numrows
25 |
26 | Write some useless number series in CSV format.
27 |
28 | Purpose: example and test for lazy list (streaming) code (check for
29 | leaks/memory retention).
30 |
31 | ";
32 | exit(@_ ? 1 : 0);
33 | }
34 |
35 | use Getopt::Long;
36 | our $verbose = 0;
37 | GetOptions("verbose" => \$verbose, "help" => sub {usage},) or exit 1;
38 | usage unless @ARGV == 2;
39 |
40 | our ($path, $numrows) = @ARGV;
41 |
42 | use FP::Text::CSV qw(rows_to_csv_file);
43 | use FP::List ":all";
44 | use FP::Lazy ":all";
45 | use FP::Stream ":all";
46 | use FP::Ops qw(add);
47 | use FP::fix;
48 | use FP::Currying;
49 |
50 | sub fibs () {
51 |
52 | # (Also see the [[fibs]] example script; unlike there, where
53 | # global scope is used, here we don't use Keep around $fibs as
54 | # it's correct when the local variable, which goes out of scope,
55 | # is deleted)
56 | my $fibs;
57 | $fibs = cons 1, cons 1, lazy { $fibs->stream_zip_with(\&add, rest $fibs) };
58 | $fibs
59 | }
60 |
61 | #sub exps($base, $start)
62 | sub exps;
63 | *exps = uncurry fun($base) {
64 | fix sub ($rec, $x) {
65 | lazy { cons $x, &$rec($x * $base) }
66 | }
67 | };
68 |
69 | sub series () {
70 | my $a = 1.01;
71 | my $b = 0.99;
72 | my $ns = rest stream_iota;
73 | my $as = exps $a, $a;
74 | my $bs = exps $b, $b;
75 | my $fibs = fibs;
76 |
77 | (["n", "$a^n", "$b^n", "fib"], stream_zip $ns, $as, $bs, $fibs)
78 | }
79 |
80 | sub page ($title, $rows) {
81 | cons $title, $rows->take($numrows)
82 | }
83 |
84 | # Because of an unresolved issue (apparently in the Perl interpreter),
85 | # the resulting stream has to be assigned to a lexical variable first,
86 | # or the stream head would be held on to and the process would run out
87 | # of memory:
88 | my $page = page(series);
89 |
90 | rows_to_csv_file $page, $path, +{ eol => "\n", sep_char => ";" };
91 |
92 |
--------------------------------------------------------------------------------
/examples/goto:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env perl
2 |
3 | use strict;
4 | use warnings FATAL => 'uninitialized';
5 |
6 | use strict;
7 | use warnings;
8 | use warnings FATAL => 'uninitialized';
9 |
10 | #use Sub::Call::Tail;
11 |
12 | # find modules from functional-perl working directory (not installed)
13 | use Cwd 'abs_path';
14 | our ($mydir, $myname);
15 |
16 | BEGIN {
17 | my $location = (-l $0) ? abs_path($0) : $0;
18 | $location =~ /(.*?)([^\/]+?)_?\z/s or die "?";
19 | ($mydir, $myname) = ($1, $2);
20 | }
21 | use lib "$mydir/../lib";
22 |
23 | sub sumofsquares {
24 | my ($from, $to) = @_;
25 | my $tot = 0;
26 | for (my $i = $from; $i <= $to; $i++) {
27 | $tot + = $i * $i;
28 | }
29 | $tot
30 | }
31 |
32 | sub Gsumofsquares {
33 | my ($from, $to) = @_;
34 | my $tot = 0;
35 | my $i = $from;
36 | test:
37 | goto calculate if $i <= $to;
38 | return $tot;
39 | calculate:
40 | $tot + = $i * $i;
41 | $i++;
42 | goto test;
43 | }
44 |
45 | use Chj::TEST ":all";
46 |
47 | TEST {
48 | [map { [sumofsquares(@$_), Gsumofsquares(@$_)] }
49 | ([0, 4], [1, 5], [3, 7], [-2, 4])]
50 | }
51 | [[30, 30], [55, 55], [135, 135], [35, 35]];
52 |
53 | use FP::Repl;
54 | repl;
55 |
--------------------------------------------------------------------------------
/examples/logwatch_exampleconfig.pl:
--------------------------------------------------------------------------------
1 | use experimental "signatures";
2 |
3 | +{
4 | logfile => "logwatch_example.log",
5 | match => sub($line) {
6 | $line =~ /yes/
7 | },
8 | collecttime => 4,
9 | report => sub($path) {
10 | print " == REPORT: ======= \n";
11 | system "cat", $path;
12 | print "================== \n";
13 | unlink $path;
14 | },
15 | }
16 |
--------------------------------------------------------------------------------
/examples/maplines:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env perl
2 |
3 | # Copyright (c) 2015 Christian Jaeger, copying@christianjaeger.ch
4 | # This is free software. See the file COPYING.md that came bundled
5 | # with this file.
6 |
7 | use strict;
8 | use warnings;
9 | use warnings FATAL => 'uninitialized';
10 |
11 | #use Sub::Call::Tail;
12 |
13 | # find modules from functional-perl working directory (not installed)
14 | use Cwd 'abs_path';
15 | our ($mydir, $myname);
16 |
17 | BEGIN {
18 | my $location = (-l $0) ? abs_path($0) : $0;
19 | $location =~ /(.*?)([^\/]+?)_?\z/s or die "?";
20 | ($mydir, $myname) = ($1, $2);
21 | }
22 | use lib "$mydir/../lib";
23 |
24 | sub usage {
25 | print "error: @_\n" if @_;
26 | print "usage: $myname mappingsfile.pl
27 |
28 | Read mail addresses from stdin, map using the mapping function
29 | returned in the 'map' field of the hashtable returned as the last
30 | value from mappingsfile.pl
31 |
32 | Options:
33 | --sort-result sort according to result value from the
34 | mapping function, using the function in the 'cmp'
35 | field of the configuration hashtable as the
36 | comparison function (default: case sensitive
37 | string search).
38 | ";
39 | exit 1;
40 | }
41 |
42 | use Getopt::Long;
43 | our $verbose = 0;
44 | my $opt_sort_result;
45 | GetOptions(
46 | "verbose" => \$verbose,
47 | "help" => sub {usage},
48 | "sort-result" => \$opt_sort_result,
49 | ) or exit 1;
50 | usage unless @ARGV == 1;
51 |
52 | my ($mappingfile) = @ARGV;
53 |
54 | use FP::IOStream ":all";
55 | use Chj::xopen qw(glob_to_fh);
56 | use FP::Ops qw(cut_method unary_operator);
57 | use Chj::TEST;
58 |
59 | #use FP::Repl::Trap; # or Chj::Backtrace
60 |
61 | my $config = require $mappingfile;
62 | ref($config) eq "HASH"
63 | or usage "invalid mappingfile, does not return a hash ref";
64 |
65 | my ($mapfn, $cmp) = @$config{ "map", "cmp" };
66 |
67 | my $in = glob_to_fh(*STDIN);
68 | my $out = glob_to_fh(*STDOUT);
69 |
70 | my $lines = fh_to_lines $in;
71 |
72 | my $mapped = $lines->map (
73 | sub {
74 | my ($line) = @_;
75 | chomp $line;
76 | &$mapfn($line)
77 | }
78 | );
79 |
80 | my $result = do {
81 | my $l = $mapped->filter(unary_operator 'defined');
82 | $opt_sort_result ? $l->sort($cmp) : $l
83 | };
84 |
85 | $result->for_each(cut_method $out, "xprintln");
86 |
87 | $out->xclose;
88 |
89 | #use FP::Repl; repl;
90 |
--------------------------------------------------------------------------------
/examples/maplines-example.pl:
--------------------------------------------------------------------------------
1 | use strict;
2 | use warnings;
3 | use warnings FATAL => 'uninitialized';
4 |
5 | use FP::Array_sort qw(on);
6 | use FP::Ops qw(string_lc string_cmp);
7 |
8 | my $m = +{
9 | 'ch@christianjaeger.ch' => "Christian Jaeger",
10 | 'foo@example.com' => "Mr Example",
11 | 'baz@example.com' => undef, # drop
12 | };
13 |
14 | +{
15 | map => sub {
16 | my ($addr) = @_;
17 | if (exists $$m{$addr}) {
18 | $$m{$addr}
19 | } else {
20 | die "unknown address: '$addr'";
21 | }
22 | },
23 | cmp => on(\&string_lc, \&string_cmp),
24 | }
25 |
--------------------------------------------------------------------------------
/examples/perl-weekly-challenges/.gitignore:
--------------------------------------------------------------------------------
1 | *.hi
2 | *.o
3 | 113-1-represent_integer_haskell
4 |
--------------------------------------------------------------------------------
/examples/perl-weekly-challenges/Makefile:
--------------------------------------------------------------------------------
1 | 113-1-represent_integer_haskell: 113-1-represent_integer_haskell.hs
2 | ghc -fdiagnostics-color=always -Wall -O2 113-1-represent_integer_haskell.hs
3 |
--------------------------------------------------------------------------------
/examples/template:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env perl
2 |
3 | # Copyright (c) 2016-2020 Christian Jaeger, copying@christianjaeger.ch
4 | # This is free software. See the file COPYING.md that came bundled
5 | # with this file.
6 |
7 | use strict;
8 | use warnings;
9 | use warnings FATAL => 'uninitialized';
10 |
11 | #use experimental "signatures";
12 |
13 | #use Sub::Call::Tail;
14 |
15 | # find modules from functional-perl working directory (not installed)
16 | use Cwd 'abs_path';
17 | our ($mydir, $myname);
18 |
19 | BEGIN {
20 | my $location = (-l $0) ? abs_path($0) : $0;
21 | $location =~ /(.*?)([^\/]+?)_?\z/s or die "?";
22 | ($mydir, $myname) = ($1, $2);
23 | }
24 | use lib "$mydir/../lib";
25 |
26 | sub usage {
27 | print "usage: $myname ...
28 | ";
29 | exit 1;
30 | }
31 |
32 | use Getopt::Long;
33 | our $verbose = 0;
34 | GetOptions("verbose" => \$verbose, "help" => sub {usage},) or exit 1;
35 | usage if @ARGV;
36 |
37 | # example module use:
38 | use FP::List ":all";
39 | use FP::Array ":all";
40 | use FP::Array_sort ":all";
41 | use Chj::TEST;
42 |
43 | # for development/debugging
44 | use Chj::ruse;
45 | use FP::Repl::Trap; # or Chj::Backtrace
46 | use FP::Repl;
47 |
48 | # add your own code.... (possibly adding `repl` calls within, too;
49 | # also, it's better to put most code in modules, and use `ruse` to
50 | # reload them (main is [currently?] not reloaded by `ruse`))
51 |
52 | # during development:
53 | repl;
54 |
--------------------------------------------------------------------------------
/functional_XML/TODO.md:
--------------------------------------------------------------------------------
1 | Check the [functional-perl website](http://functional-perl.org/) for
2 | properly formatted versions of these documents.
3 |
4 | ---
5 |
6 | # PXML Todo
7 |
8 | (See also functional-perl [[TODO]].)
9 |
10 | * Handle tag and attribute names safely? For example:
11 |
12 | PXML::Element->new("<>", {"/" => 1}, [])
13 |
14 | will currently serialize to something like <<> /="1"><>>
15 |
16 | * use `HTML::Element`, as base class? Of course that won't work for
17 | general XML. `XML::LibXML` for the latter? Well.. Perhaps
18 | parametrizable?
19 |
20 | * how are the rules with regards to URL escaping? No escaping, right?
21 | So should actually be fine? Check in detail, tests.
22 |
23 | * tests are scattered to functional_XML/{test,testlazy},
24 | functional_XML/t/*, and lib/PXML/Preserialize/t.pm and
25 | lib/PXML/Serialize/t.pm, which is probably too much of a mess.
26 |
27 | * clean up `PXML::Serialize`, it's an awful mess now (undo all those
28 | useless constant optimizations)
29 |
30 | * make a proper hierarchy (`PXML::Element` and PXML::Body (in `PXML`)
31 | should probably have a common base class), move code to proper
32 | locations.
33 |
34 | * optimization: examine whether it would be worthwhile to use mapping
35 | functions that reuse inputs if unchanged
36 |
37 |
--------------------------------------------------------------------------------
/functional_XML/t/div:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env perl
2 |
3 | # Copyright (c) 2014-2015 Christian Jaeger, copying@christianjaeger.ch
4 | # This is free software. See the file COPYING.md that came bundled
5 | # with this file.
6 |
7 | use strict;
8 | use warnings;
9 | use warnings FATAL => 'uninitialized';
10 |
11 | our ($mydir, $myname);
12 |
13 | BEGIN {
14 | $0 =~ /(.*?)([^\/]+)\z/s or die "?";
15 | ($mydir, $myname) = ($1, $2);
16 | }
17 | use lib "$mydir/../../lib";
18 |
19 | use Chj::TEST ":all";
20 | use PXML::XHTML ":all";
21 | use PXML::Serialize "pxml_print_fragment_fast";
22 | use FP::Lazy;
23 | use FP::List;
24 | use FP::Stream;
25 | use FP::Array ":all"; # XXX inconsistency in export directives
26 | use FP::PureArray;
27 |
28 | TEST_STDOUT {
29 | pxml_print_fragment_fast(A({ href => "hah" }, "Hello ", I("World", B("!"))),
30 | *STDOUT{IO})
31 | }
32 | 'Hello World!';
33 |
34 | TEST {
35 | A(
36 | { href => "hah" },
37 | "Hello ",
38 | I("World", undef, B("!")),
39 | cons(" ", string_to_stream("You're great."))
40 | )->text
41 | }
42 | 'Hello World! You\'re great.';
43 |
44 | TEST { P(list)->string }
45 | '';
46 |
47 | TEST { P(list 1, 2, B(3))->string }
48 | '123
';
49 |
50 | TEST {
51 | P(stream 1, 2, lazy { B(3) })->string
52 | }
53 | '123
';
54 |
55 | TEST { P(array 1, 2, B(3))->string }
56 | '123
';
57 |
58 | TEST { P(purearray 1, 2, B(3))->string }
59 | '123
';
60 |
61 | TEST { P(cons "Hello", "World")->string }
62 | 'HelloWorld
';
63 |
64 | TEST { P(cons I("Hello"), B(cons null, "World"))->string }
65 | 'HelloWorld
';
66 |
67 | use Chj::Backtrace;
68 |
69 | perhaps_run_tests(__PACKAGE__) or do {
70 | require FP::Repl;
71 | FP::Repl::repl();
72 | };
73 |
--------------------------------------------------------------------------------
/functional_XML/t/stream:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # XX replace with Perl script if this were to be added to (activated
4 | # in) the test suite!
5 |
6 | set -eu
7 |
8 | ulimit -S -v 20000
9 |
10 | exec "$0"_
11 |
--------------------------------------------------------------------------------
/functional_XML/t/stream_:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env perl
2 |
3 | # Copyright (c) 2015 Christian Jaeger, copying@christianjaeger.ch
4 | # This is free software. See the file COPYING.md that came bundled
5 | # with this file.
6 |
7 | use strict;
8 | use warnings;
9 | use warnings FATAL => 'uninitialized';
10 |
11 | our ($mydir, $myname);
12 |
13 | BEGIN {
14 | $0 =~ /(.*?)([^\/]+)\z/s or die "?";
15 | ($mydir, $myname) = ($1, $2);
16 | }
17 |
18 | use lib "$mydir/../../lib";
19 |
20 | use FP::Stream ":all";
21 | use Chj::TEST ':all';
22 |
23 | TEST { stream_length stream_iota 0, 1000000 }
24 | 1000000;
25 |
26 | run_tests;
27 |
--------------------------------------------------------------------------------
/functional_XML/test:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env perl
2 |
3 | # Copyright (c) 2015 Christian Jaeger, copying@christianjaeger.ch
4 | # This is free software. See the file COPYING.md that came bundled
5 | # with this file.
6 |
7 | use strict;
8 | use warnings;
9 | use warnings FATAL => 'uninitialized';
10 |
11 | use Cwd 'abs_path';
12 | our ($mydir, $myname);
13 |
14 | BEGIN {
15 | my $location = (-l $0) ? abs_path($0) : $0;
16 | $location =~ /(.*?)([^\/]+?)_?\z/s or die "?";
17 | ($mydir, $myname) = ($1, $2);
18 | }
19 | use lib "$mydir/../lib";
20 |
21 | use PXML::XHTML ':all';
22 | use PXML::Serialize;
23 |
24 | use utf8;
25 |
26 | sub page {
27 | my ($title, $mtime, $main) = @_;
28 | HTML(
29 | HEAD(TITLE($title)),
30 | BODY(
31 | $main,
32 | HR(),
33 | P(
34 | "By ",
35 | A({ href => "http://christianjaeger.ch" }, "Christian Jaeger"),
36 | ", last modified at ",
37 | gmtime($mtime) . "",
38 | " (or something)."
39 | )
40 | )
41 | )
42 | }
43 |
44 | our $numbers = { 1 => "one", 2 => "two", 3 => "three" };
45 |
46 | sub examplepage {
47 | my ($title) = @_;
48 | page(
49 | "example page - $title",
50 | @ARGV ? $ARGV[0] : time,
51 | [
52 | H1($title),
53 | P(
54 | "Garçon méchanique, \"1 < 2\" is true. ",
55 | A({ href => "\"1 < 2\"" }, "this will be 404")
56 | ),
57 | TABLE(
58 | { border => 1 },
59 | map { TR(TD($_), TD($$numbers{$_})) } (1 .. 3)
60 | )
61 | ]
62 | )
63 | }
64 |
65 | open my $o, ">:utf8", "out.xhtml" or die $!;
66 | pxml_xhtml_print examplepage("Hello World"), $o, "en";
67 | close $o or die $!;
68 |
69 |
--------------------------------------------------------------------------------
/functional_XML/testlazy:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env perl
2 |
3 | # Copyright (c) 2015 Christian Jaeger, copying@christianjaeger.ch
4 | # This is free software. See the file COPYING.md that came bundled
5 | # with this file.
6 |
7 | use strict;
8 | use warnings;
9 | use warnings FATAL => 'uninitialized';
10 |
11 | use Cwd 'abs_path';
12 | our ($mydir, $myname);
13 |
14 | BEGIN {
15 | my $location = (-l $0) ? abs_path($0) : $0;
16 | $location =~ /(.*?)([^\/]+?)_?\z/s or die "?";
17 | ($mydir, $myname) = ($1, $2);
18 | }
19 | use lib "$mydir/../lib";
20 |
21 | use PXML::XHTML ':all';
22 | use FP::Lazy;
23 | use FP::List;
24 | use PXML::Serialize;
25 |
26 | use utf8;
27 |
28 | $| = 1;
29 |
30 | sub countdown {
31 | my ($i) = @_;
32 | lazyLight {
33 |
34 | #sleep 1;
35 | if ($i >= 0) {
36 | cons(P($i), countdown($i - 1));
37 | } else {
38 | null
39 |
40 | # XX should test undef here, too
41 | }
42 | }
43 | }
44 |
45 | sub page {
46 | my ($title, $mtime, $main) = @_;
47 | HTML(
48 | HEAD(TITLE($title)),
49 | BODY(
50 | $main,
51 | HR(),
52 | P(
53 | "By ",
54 | A({ href => "http://christianjaeger.ch" }, "Christian Jaeger"),
55 | ", last modified at ",
56 | gmtime($mtime) . "",
57 | " (or something)."
58 | )
59 | )
60 | )
61 | }
62 |
63 | our $numbers = { 1 => "one", 2 => "two", 3 => "three" };
64 |
65 | sub examplepage {
66 | my ($title) = @_;
67 | page(
68 | "example page - $title",
69 | $ENV{T} // time,
70 | [
71 | H1($title),
72 | P(
73 | "Garçon méchanique, \"1 < 2\" is true. ",
74 | A({ href => "\"1 < 2\"" }, "this will be 404")
75 | ),
76 | TABLE(
77 | { border => 1 },
78 | map { TR(TD($_), TD($$numbers{$_})) } (1 .. 3)
79 | ),
80 | countdown($ENV{N} || 1e9),
81 | ]
82 | )
83 | }
84 |
85 | pxml_xhtml_print examplepage("Hello World"), *STDOUT{IO}, "en";
86 |
87 |
--------------------------------------------------------------------------------
/htmlgen/FunctionalPerl/Htmlgen/Cost.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2014-2019 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | FunctionalPerl::Htmlgen::Cost
13 |
14 | =head1 SYNOPSIS
15 |
16 | =head1 DESCRIPTION
17 |
18 |
19 | =head1 NOTE
20 |
21 | This is alpha software! Read the status section in the package README
22 | or on the L.
23 |
24 | =cut
25 |
26 | package FunctionalPerl::Htmlgen::Cost;
27 | use strict;
28 | use warnings;
29 | use warnings FATAL => 'uninitialized';
30 | use experimental "signatures";
31 | use Sub::Call::Tail;
32 |
33 | #use Exporter "import";
34 | #@EXPORT = qw();
35 | #@EXPORT_OK = qw();
36 | #%EXPORT_TAGS = (all => [@EXPORT,@EXPORT_OK]);
37 |
38 | package FunctionalPerl::Htmlgen::Cost::_::Cost {
39 | use FP::Array ":all";
40 |
41 | use FP::Struct [qw(name is_purchaseable basecosts val)];
42 |
43 | sub cost ($self, $index) {
44 | $$self{_cost} ||= do {
45 | add($self->val,
46 | map { $$index{$_}->cost($index) } @{ $self->basecosts });
47 | }
48 | }
49 | _END_
50 | }
51 |
52 | package FunctionalPerl::Htmlgen::Cost::_::Totalcost {
53 | use FP::Array_sort ":all";
54 |
55 | use FP::Struct [qw(costs)];
56 |
57 | sub range($self) {
58 | @{ $$self{costs} } or die "no costs given"; #
59 | my $index;
60 | for (@{ $$self{costs} }) {
61 | if (defined(my $name = $_->name)) {
62 | $$index{$name} = $_
63 | }
64 | }
65 | my $purchaseable = [grep { $_->is_purchaseable } @{ $$self{costs} }];
66 | @$purchaseable or die "no purchaseable costs"; #
67 | local our $all
68 | = array_sort($purchaseable, on the_method("cost", $index),
69 | \&real_cmp);
70 | ( @$all == 1
71 | ? $$all[0]->cost($index)
72 | : $$all[0]->cost($index) . ".." . $$all[-1]->cost($index))
73 | }
74 | _END_
75 | }
76 |
77 |
--------------------------------------------------------------------------------
/htmlgen/FunctionalPerl/Htmlgen/FileUtil.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2014-2015 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | FunctionalPerl::Htmlgen::FileUtil
13 |
14 | =head1 SYNOPSIS
15 |
16 | =head1 DESCRIPTION
17 |
18 |
19 | =head1 NOTE
20 |
21 | This is alpha software! Read the status section in the package README
22 | or on the L.
23 |
24 | =cut
25 |
26 | package FunctionalPerl::Htmlgen::FileUtil;
27 | use strict;
28 | use warnings;
29 | use warnings FATAL => 'uninitialized';
30 | use experimental "signatures";
31 | use Sub::Call::Tail;
32 | use Exporter "import";
33 |
34 | our @EXPORT = qw();
35 | our @EXPORT_OK = qw(existingpath_or create_parent_dirs);
36 | our %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]);
37 |
38 | # lib?
39 | sub existingpath_or(@paths) {
40 | for (@paths) {
41 | return $_ if -e $_
42 | }
43 | die "none of the paths exist: @paths";
44 | }
45 |
46 | use POSIX qw(EEXIST ENOENT);
47 | use Chj::xperlfunc qw(dirname xmkdir);
48 |
49 | # XX how is this different from xmkdir_p ?
50 | sub create_parent_dirs ($path0, $path0_to_outpath) {
51 | $path0 = dirname $path0;
52 | my $outpath = &$path0_to_outpath($path0);
53 | if (mkdir $outpath) {
54 |
55 | # ok, return
56 | } elsif ($! == EEXIST) {
57 |
58 | # ok, return
59 | } elsif ($! == ENOENT) {
60 | create_parent_dirs($path0, $path0_to_outpath);
61 | xmkdir $outpath;
62 | } else {
63 | die "mkdir '$outpath': $!";
64 | }
65 | }
66 |
67 | 1
68 |
--------------------------------------------------------------------------------
/htmlgen/FunctionalPerl/Htmlgen/PXMLMapper.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2014-2019 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | FunctionalPerl::Htmlgen::PXMLMapper - base class for PXML mappers
13 |
14 | =head1 SYNOPSIS
15 |
16 | =head1 DESCRIPTION
17 |
18 | Holding context that may be needed by mapping functions that change
19 | the PXML representing a page in htmlgen.
20 |
21 | =head1 NOTE
22 |
23 | This is alpha software! Read the status section in the package README
24 | or on the L.
25 |
26 | =cut
27 |
28 | package FunctionalPerl::Htmlgen::PXMLMapper;
29 |
30 | use strict;
31 | use warnings;
32 | use warnings FATAL => 'uninitialized';
33 | use experimental "signatures";
34 |
35 | use Sub::Call::Tail;
36 | use FP::Docstring;
37 | use FP::Predicates;
38 |
39 | use FP::Struct [
40 | [\&is_nonnullstring, "path0"],
41 | [\&is_procedure, "maybe_have_path0"],
42 | [\&is_procedure, "perhaps_filename_to_path0"],
43 | [maybe(\&is_procedure), "map_code_body"],
44 | [instance_of("FunctionalPerl::Htmlgen::PathTranslate"), "pathtranslate"],
45 | ];
46 |
47 | sub match_element_names($self) {
48 | __ 'method () -> [string] -- array of element names';
49 | die "abstract class, missing implementation";
50 | }
51 |
52 | sub map_element ($self, $e, $uplist) {
53 | __
54 | 'method ($e: PXML::Element, $uplist: list_of PXML::Element) -> PXML::Element';
55 | die "abstract class, missing implementation";
56 | }
57 |
58 | _END_
59 |
--------------------------------------------------------------------------------
/htmlgen/FunctionalPerl/Htmlgen/PerlTidy.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2019-2021 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | FunctionalPerl::Htmlgen::PerlTidy -- code formatting for Perl snippets
13 |
14 | =head1 SYNOPSIS
15 |
16 | =head1 DESCRIPTION
17 |
18 | =head1 SEE ALSO
19 |
20 | This is a L
21 |
22 | =head1 NOTE
23 |
24 | This is alpha software! Read the status section in the package README
25 | or on the L.
26 |
27 | =cut
28 |
29 | package FunctionalPerl::Htmlgen::PerlTidy;
30 |
31 | use strict;
32 | use warnings;
33 | use warnings FATAL => 'uninitialized';
34 | use experimental "signatures";
35 |
36 | use FP::Show;
37 | use Perl::Tidy;
38 | use FunctionalPerl::Htmlgen::Htmlparse ":all";
39 | use FunctionalPerl::Htmlgen::Sourcelang;
40 |
41 | sub tidyhtml {
42 | my ($source) = @_;
43 | my ($dest, $errorfile);
44 | my $error = Perl::Tidy::perltidy(
45 | argv => '--html -ntoc',
46 | source => \$source,
47 | destination => \$dest,
48 | errorfile => \$errorfile
49 | );
50 | if ($error) {
51 | warn "perltidy error: " . show($error) . " (" . show($errorfile) . ")";
52 | ()
53 | } else {
54 | htmlparse $dest, "pre"
55 | }
56 | }
57 |
58 | use FP::Struct [] => "FunctionalPerl::Htmlgen::PXMLMapper";
59 |
60 | sub match_element_names($self) { [qw(code)] }
61 |
62 | sub map_element ($self, $e, $uplist) {
63 |
64 | # warn "hm: "
65 | # . show($e->name)
66 | # . ", uplist= "
67 | # . show($uplist->map(the_method "name"));
68 | if (not $uplist->is_null and $uplist->first->lcname eq "pre") {
69 | my $txt = $e->text;
70 | if (sourcelang($txt) eq "Perl") {
71 | my $pre = tidyhtml $txt;
72 |
73 | #use FP::Repl;repl;
74 | $pre->body
75 | } else {
76 |
77 | # do not handle this element, leave up to pointer_eq to
78 | # detect that
79 | $e
80 | }
81 | } else {
82 |
83 | # do not handle this element, leave up to pointer_eq to detect
84 | # that
85 | $e
86 | }
87 | }
88 |
89 | _END_ # _END__ for dev
90 |
--------------------------------------------------------------------------------
/htmlgen/FunctionalPerl/Htmlgen/UriUtil.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2014-2019 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | FunctionalPerl::Htmlgen::UriUtil
13 |
14 | =head1 SYNOPSIS
15 |
16 | =head1 DESCRIPTION
17 |
18 |
19 | =head1 NOTE
20 |
21 | This is alpha software! Read the status section in the package README
22 | or on the L.
23 |
24 | =cut
25 |
26 | package FunctionalPerl::Htmlgen::UriUtil;
27 | use strict;
28 | use warnings;
29 | use warnings FATAL => 'uninitialized';
30 | use experimental "signatures";
31 | use Sub::Call::Tail;
32 | use Exporter "import";
33 |
34 | our @EXPORT = qw();
35 | our @EXPORT_OK = qw(uri_add URI_is_internal);
36 | our %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]);
37 |
38 | use FP::Docstring;
39 | use Chj::TEST;
40 | use URI;
41 |
42 | sub uri_add ($base, $rel) {
43 | __ '($basestr,$relstr) -> $str ' . '-- (via URI.pm)';
44 | URI->new($rel)->abs(URI->new($base)) . ""
45 | }
46 |
47 | TEST { uri_add "http://bar.com/baz/", "/zoo#hm" } "http://bar.com/zoo#hm";
48 | TEST { uri_add "http://bar.com/baz/", "zoo" } "http://bar.com/baz/zoo";
49 | TEST { uri_add "http://bar.com/baz", "zoo" } "http://bar.com/zoo";
50 | TEST { uri_add "http://bar.com/baz/#ax", "#bx" } "http://bar.com/baz/#bx";
51 |
52 | # Instead of monkey-patching into the URI package, use a local
53 | # name. (We're in need of lexical method definitions!)
54 | sub URI_is_internal($self) {
55 | not defined $self->scheme
56 | }
57 |
58 | 1
59 |
--------------------------------------------------------------------------------
/htmlgen/FunctionalPerl/Htmlgen/default_config.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2014-2015 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | FunctionalPerl::Htmlgen::default_config
13 |
14 | =head1 SYNOPSIS
15 |
16 | =head1 DESCRIPTION
17 |
18 |
19 | =head1 NOTE
20 |
21 | This is alpha software! Read the status section in the package README
22 | or on the L.
23 |
24 | =cut
25 |
26 | package FunctionalPerl::Htmlgen::default_config;
27 | use strict;
28 | use warnings;
29 | use warnings FATAL => 'uninitialized';
30 | use experimental "signatures";
31 | use Exporter "import";
32 |
33 | our @EXPORT = qw($default_config);
34 | our @EXPORT_OK = qw();
35 | our %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]);
36 |
37 | use Chj::xperlfunc qw(basename);
38 |
39 | sub default__is_indexpath0($path0) {
40 | my $bn = lc basename($path0);
41 | $bn eq "index.md" or $bn eq "readme.md"
42 | }
43 |
44 | our $default_config = +{ is_indexpath0 => \&default__is_indexpath0, };
45 |
46 |
--------------------------------------------------------------------------------
/htmlgen/TODO.md:
--------------------------------------------------------------------------------
1 | Check the [functional-perl website](http://functional-perl.org/) for
2 | properly formatted versions of these documents.
3 |
4 | ---
5 |
6 | # Htmlgen Todo
7 |
8 | (See also functional-perl [[TODO]].)
9 |
10 | - automatically clean target directory of files that are not generated
11 | anymore? (But then maybe some files are there on purpose? Check git
12 | ls-files?)
13 |
14 | - also auto-link other files than modules in code?
15 |
--------------------------------------------------------------------------------
/lib/Chj/BinHexOctDec.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2003-2019 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | Chj::BinHexOctDec
13 |
14 | =head1 SYNOPSIS
15 |
16 | use Chj::BinHexOctDec;
17 | my $num = Chj::BinHexOctDec->bin("1001010010");
18 | is ref($num), "Chj::BinHexOctDec";
19 | is $num->dec, 594;
20 |
21 | use FP::Equal 'is_equal';
22 | is_equal [ map { $num->$_ } qw(dec bin hex oct) ],
23 | [ 594, "1001010010", "252", "1122" ];
24 |
25 |
26 |
27 | =head1 DESCRIPTION
28 |
29 | Conversions between number bases.
30 |
31 | The methods are overloaded both as class methods (to convert *from*
32 | that base) and object method (*to* that base).
33 |
34 | Note: does not die on invalid input. (Should this be considered a
35 | bug?)
36 |
37 | =head1 METHODS
38 |
39 | =head1 NOTE
40 |
41 | This is alpha software! Read the status section in the package README
42 | or on the L.
43 |
44 | =cut
45 |
46 | package Chj::BinHexOctDec;
47 |
48 | use strict;
49 | use warnings;
50 | use warnings FATAL => 'uninitialized';
51 |
52 | sub bin {
53 | my $this = shift;
54 | if (ref $this) {
55 | sprintf('%b', $$this)
56 | } else {
57 | my $data = oct('0b' . shift);
58 | bless \$data, $this
59 | }
60 | }
61 |
62 | sub dec {
63 | my $this = shift;
64 | if (ref $this) {
65 | $$this
66 | } else {
67 | my $data = shift;
68 | bless \$data, $this
69 | }
70 | }
71 |
72 | sub oct {
73 | my $this = shift;
74 | if (ref $this) {
75 | sprintf('%o', $$this)
76 | } else {
77 | my $data = oct('0' . shift);
78 | bless \$data, $this
79 | }
80 | }
81 |
82 | sub hex {
83 | my $this = shift;
84 | if (ref $this) {
85 | sprintf('%x', $$this)
86 | } else {
87 | my $data = hex(shift); # oct('0x'.shift); should work as well
88 | bless \$data, $this
89 | }
90 | }
91 |
92 | 1
93 |
--------------------------------------------------------------------------------
/lib/Chj/BuiltinTypePredicates.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2015-2020 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | Chj::BuiltinTypePredicates
13 |
14 | =head1 SYNOPSIS
15 |
16 | =head1 DESCRIPTION
17 |
18 | Predicates that are useful/needed outside of the `FP` namespace
19 | tree. Also, to avoid circular dependency on Chj::TEST.
20 |
21 | =head1 SEE ALSO
22 |
23 | L
24 |
25 | =head1 NOTE
26 |
27 | This is alpha software! Read the status section in the package README
28 | or on the L.
29 |
30 | =cut
31 |
32 | package Chj::BuiltinTypePredicates;
33 | use strict;
34 | use warnings;
35 | use warnings FATAL => 'uninitialized';
36 | use Exporter "import";
37 |
38 | our @EXPORT = qw(is_filehandle);
39 | our @EXPORT_OK = qw();
40 | our %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]);
41 |
42 | use Scalar::Util 'reftype';
43 | use FP::Carp;
44 |
45 | # for tests, see FP::Predicates
46 |
47 | sub is_filehandle {
48 | @_ == 1 or fp_croak_arity 1;
49 | my ($v) = @_;
50 |
51 | # NOTE: never returns true for strings, even though plain strings
52 | # naming globals containing filehandles in their IO slot will work
53 | # for IO, too! Let's just leave that depreciated and
54 | # 'non-working', ok?
55 |
56 | # NOTE 2: also this only returns true for *references* to globs,
57 | # not globs themselves (which could also be used as in
58 | # `(*STDOUT)->print( "Huh\n")`). Let's just leave bare globs as
59 | # buckets for any of the variable types perl has, and not assume
60 | # it's meant to be a filehandle, ok? (Or is that inconsistent with
61 | # treating `\*STDOUT` as filehandle? But there's no way around
62 | # this one, as that's what `open my $out, ..` gives, and we do
63 | # check that the IO slot is actually set in this case.) (hm could
64 | # take reference to the bare glob and treat it the same then,
65 | # though; but still.)
66 |
67 | if (defined(my $rt = reftype($v))) {
68 | (($rt eq "GLOB" and *{$v}{IO}) or $rt eq "IO") ? 1 : '';
69 |
70 | # explicitely return '' instead of undef
71 | } else {
72 | ''
73 | }
74 | }
75 |
76 | 1
77 |
--------------------------------------------------------------------------------
/lib/Chj/Destructor.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2015-2019 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | Chj::Destructor
13 |
14 | =head1 SYNOPSIS
15 |
16 | use Chj::Destructor;
17 |
18 | my $z = 0;
19 | {
20 | my $x = ["foo", Destructor { $z++ }];
21 | }
22 | is $z, 1;
23 |
24 |
25 | =head1 DESCRIPTION
26 |
27 | Util to help debug or test memory deallocation.
28 |
29 | =head1 SEE ALSO
30 |
31 | Implements: L, L
32 |
33 | End.pm, but that one does not type-check the destructor argument
34 | early, nor does it localize error variables in its DESTROY method.
35 |
36 | =head1 NOTE
37 |
38 | This is alpha software! Read the status section in the package README
39 | or on the L.
40 |
41 | =cut
42 |
43 | package Chj::Destructor;
44 | use strict;
45 | use warnings;
46 | use warnings FATAL => 'uninitialized';
47 | use Exporter "import";
48 |
49 | our @EXPORT = qw(Destructor);
50 | our @EXPORT_OK = qw();
51 | our %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]);
52 |
53 | {
54 |
55 | package Chj::_::Destructor;
56 | use FP::Predicates ":all";
57 | use FP::Struct [[\&is_procedure, "thunk"]], 'FP::Struct::Show',
58 | 'FP::Abstract::Pure';
59 |
60 | sub DESTROY {
61 | my ($self) = @_;
62 | local ($@, $!, $?, $^E, $.);
63 | $self->thunk->()
64 | }
65 | _END_
66 | }
67 |
68 | # Chj::_::Destructor::constructors->import -- no, special prototype:
69 | sub Destructor (&) {
70 | Chj::_::Destructor->new($_[0])
71 | }
72 |
73 | use Chj::TEST;
74 |
75 | TEST {
76 | my $z = 0;
77 | {
78 | my $x = ["foo", Destructor { $z++ }];
79 | }
80 | $z
81 | }
82 | 1;
83 |
84 | 1
85 |
--------------------------------------------------------------------------------
/lib/Chj/IO/Pipe.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2003-2014 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | Chj::IO::Pipe
13 |
14 | =head1 SYNOPSIS
15 |
16 | =head1 DESCRIPTION
17 |
18 | Inherits from Chj::IO::File.
19 |
20 | =head1 NOTE
21 |
22 | This is alpha software! Read the status section in the package README
23 | or on the L.
24 |
25 | =cut
26 |
27 | package Chj::IO::Pipe;
28 | @ISA = "Chj::IO::File";
29 | require Chj::IO::File;
30 | use strict;
31 | use warnings;
32 | use warnings FATAL => 'uninitialized';
33 |
34 | sub quotedname {
35 | "pipe"
36 | }
37 |
38 | 1;
39 |
--------------------------------------------------------------------------------
/lib/Chj/IO/PipelessCommand.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2015 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | Chj::IO::PipelessCommand
13 |
14 | =head1 SYNOPSIS
15 |
16 | use Chj::IO::PipelessCommand;
17 | use Chj::xopen qw(xopen_read);
18 | use Chj::xtmpfile;
19 |
20 | my $in = xopen_read $inpath;
21 | my $out = xtmpfile $outpath;
22 | my $c = Chj::IO::PipelessCommand
23 | ->new_with_in_out ($in,$out, $path, @args);
24 | # $c can't be read from or written to.
25 | $c->xxfinish;
26 |
27 | =head1 DESCRIPTION
28 |
29 |
30 | =head1 NOTE
31 |
32 | This is alpha software! Read the status section in the package README
33 | or on the L.
34 |
35 | =cut
36 |
37 | package Chj::IO::PipelessCommand;
38 |
39 | use strict;
40 | use warnings;
41 | use warnings FATAL => 'uninitialized';
42 |
43 | use base qw(
44 | Chj::IO::CommandCommon
45 | );
46 |
47 | sub new_with_in_out {
48 | my $class = shift;
49 | my $infh = shift;
50 | my $outfh = shift;
51 | my $self = bless {}, $class;
52 | $self->xlaunch3($infh, $outfh, undef, @_);
53 | }
54 |
55 | # override as NOOPs
56 | sub close { }
57 | sub xclose { }
58 |
59 | 1
60 |
--------------------------------------------------------------------------------
/lib/Chj/IO/WrappedFile.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2015 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | Chj::IO::WrappedFile
13 |
14 | =head1 SYNOPSIS
15 |
16 | use Chj::xopen 'fh_to_fh';
17 | my $fh = fh_to_fh ($some_pty_or_so);
18 | # which is the same as:
19 | my $fh2 = Chj::IO::WrappedFile->new ($some_pty_or_so);
20 |
21 | # $fh and $fh2 are Chj::IO::WrappedFile objects *containing*
22 | # $some_pty_or_so
23 | $fh->dup2(0) # etc., all Chj::IO::File methods
24 |
25 | =head1 DESCRIPTION
26 |
27 | This is a type wrapper to provide the Chj::IO::File methods for all
28 | kinds of Perl filehandles.
29 |
30 | =head1 NOTE
31 |
32 | This is alpha software! Read the status section in the package README
33 | or on the L.
34 |
35 | =cut
36 |
37 | package Chj::IO::WrappedFile;
38 |
39 | use strict;
40 | use warnings;
41 | use warnings FATAL => 'uninitialized';
42 |
43 | use base 'Chj::IO::File';
44 |
45 | sub new {
46 | my $class = shift;
47 | bless [@_], $class
48 | }
49 |
50 | sub fh {
51 | $_[0][0]
52 | }
53 |
54 | 1
55 |
--------------------------------------------------------------------------------
/lib/Chj/IsPerl.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2019-2020 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | Chj::IsPerl
13 |
14 | =head1 SYNOPSIS
15 |
16 | use Chj::IsPerl qw(is_perl_file);
17 | is is_perl_file(__FILE__), 1;
18 |
19 | =head1 DESCRIPTION
20 |
21 | Report whether a file is (primarily) holding Perl code.
22 |
23 | =head1 NOTE
24 |
25 | This is alpha software! Read the status section in the package README
26 | or on the L.
27 |
28 | =cut
29 |
30 | package Chj::IsPerl;
31 | use strict;
32 | use warnings;
33 | use warnings FATAL => 'uninitialized';
34 | use Exporter 'import';
35 |
36 | our @EXPORT_OK = qw(
37 | is_perl_exe_shebang
38 | is_perl_module_path
39 | is_perl_script_path
40 | is_perl_module
41 | is_perl_exe
42 | is_perl_file
43 | );
44 |
45 | sub fh_looks_perlish {
46 | 0 # don't go there, OK?
47 | }
48 |
49 | my $perl_re = qr(perl(?:5(?:\.\d+.*)?)?);
50 |
51 | sub is_perl_exe_shebang {
52 | my ($path) = @_;
53 | open my $in, "<", $path or die "'$path': $!";
54 | my $head = <$in>;
55 | defined $head or die "'$path': $!";
56 | if (my ($exe, $rest) = $head =~ m!^#\!(\S+)\s+(.*)!s) {
57 | ($exe =~ m!(^|/)$perl_re\z!s or $rest =~ m!(^|\S+/)$perl_re(?:\s|\z)!s)
58 | } else {
59 | 0
60 | }
61 | }
62 |
63 | sub is_perl_module_path {
64 | my ($path) = @_;
65 | scalar $path =~ m!\w\.pm\z!s
66 | }
67 |
68 | sub is_perl_script_path {
69 | my ($path) = @_;
70 | $path =~ m!\w\.pl\z!s or $path =~ m!(?:^|/)Makefile.PL\z!si
71 | }
72 |
73 | # And the main API:
74 |
75 | sub is_perl_module {
76 | my ($path) = @_;
77 | is_perl_module_path $path
78 | }
79 |
80 | sub is_perl_exe {
81 | my ($path) = @_;
82 | is_perl_script_path $path or is_perl_exe_shebang $path
83 | }
84 |
85 | sub is_perl_file {
86 | my ($path) = @_;
87 | is_perl_module $path or is_perl_exe $path
88 | }
89 |
90 | 1
91 |
--------------------------------------------------------------------------------
/lib/Chj/NamespaceClean.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2013-2015 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | Chj::NamespaceClean
13 |
14 | =head1 SYNOPSIS
15 |
16 | =head1 DESCRIPTION
17 |
18 | =head1 TODO
19 |
20 | Reuse pieces of L or similar instead?
21 |
22 | =head1 SEE ALSO
23 |
24 | L, L, L
25 |
26 | =head1 NOTE
27 |
28 | This is alpha software! Read the status section in the package README
29 | or on the L.
30 |
31 | =cut
32 |
33 | package Chj::NamespaceClean;
34 | use strict;
35 | use warnings;
36 | use warnings FATAL => 'uninitialized';
37 | use Exporter "import";
38 |
39 | our @EXPORT = qw(package_keys package_delete);
40 | our @EXPORT_OK = qw();
41 | our %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]);
42 |
43 | sub package_keys {
44 | my ($package) = @_;
45 | no strict 'refs';
46 | [
47 | map {
48 | if (my $c = *{"${package}::$_"}{CODE}) { [$_, $c] }
49 | else { () }
50 | } keys %{ $package . "::" }
51 | ]
52 | }
53 |
54 | my @slotnames = qw(SCALAR HASH ARRAY IO);
55 |
56 | sub package_delete {
57 | my ($package, $keys) = @_;
58 |
59 | #warn "package_delete '$package'";
60 | no strict 'refs';
61 | for (@$keys) {
62 | my ($key, $val) = @$_;
63 | no warnings 'once';
64 | my $val2 = *{"${package}::$key"}{CODE};
65 |
66 | # check val to be equal so that it will work with Chj::ruse
67 | if ($val2 and $val == $val2) {
68 |
69 | #warn "deleting ${package}::$key ($val)";
70 | my @v = map { *{"${package}::$key"}{$_} } @slotnames;
71 | delete ${ $package . "::" }{$key};
72 | for (@v) {
73 | *{"${package}::$key"} = $_ if defined $_
74 | }
75 | }
76 | }
77 | }
78 |
79 | # sub package_wipe {
80 | # my ($package) = @_;
81 | # package_delete $package, package_keys $package
82 | # }
83 |
84 | 1
85 |
--------------------------------------------------------------------------------
/lib/Chj/NamespaceCleanAbove.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2015 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | Chj::NamespaceCleanAbove
13 |
14 | =head1 SYNOPSIS
15 |
16 | sub foo { }
17 | use Bar;
18 | use Chj::NamespaceCleanAbove; # imports `_END_`
19 | sub baz {
20 | bar foo
21 | }
22 | _END_; # deletes `foo` and everything imported by `Bar`, but still lets
23 | # `baz` access them.
24 |
25 | =head1 DESCRIPTION
26 |
27 | =head1 TODO
28 |
29 | Reuse pieces of L or similar instead?
30 |
31 | =head1 SEE ALSO
32 |
33 | L, L
34 |
35 | =head1 NOTE
36 |
37 | This is alpha software! Read the status section in the package README
38 | or on the L.
39 |
40 | =cut
41 |
42 | package Chj::NamespaceCleanAbove;
43 |
44 | use strict;
45 | use warnings;
46 | use warnings FATAL => 'uninitialized';
47 |
48 | use Chj::NamespaceClean;
49 |
50 | sub import {
51 | my $_importpackage = shift;
52 | my $package = caller;
53 | my $keys = package_keys $package;
54 | no strict 'refs';
55 | *{"${package}::_END_"} = sub {
56 | package_delete $package, $keys;
57 | 1 # make _END_ work as the last statement in a module
58 | };
59 | }
60 |
61 | 1
62 |
--------------------------------------------------------------------------------
/lib/Chj/Package/OfPath.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2006-2014 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | Chj::Package::OfPath
13 |
14 | =head1 SYNOPSIS
15 |
16 | =head1 DESCRIPTION
17 |
18 | (Taken from chj-bin's perl_path2namespace.)
19 |
20 |
21 | =head1 NOTE
22 |
23 | This is alpha software! Read the status section in the package README
24 | or on the L.
25 |
26 | =cut
27 |
28 | package Chj::Package::OfPath;
29 | use strict;
30 | use warnings;
31 | use warnings FATAL => 'uninitialized';
32 | use Cwd 'abs_path';
33 | use Chj::singlequote;
34 | use Exporter "import";
35 |
36 | our @EXPORT_OK = qw(
37 | package_of_path
38 | package_of_path_or_package
39 | );
40 |
41 | our $DEBUG = 0;
42 |
43 | sub package_of_path {
44 | my ($path) = @_;
45 | $path =~ s{^\./}{};
46 | my $class = $path;
47 | $class =~ s/\.pm$//;
48 | $class =~ s|/|::|sg;
49 | if ($path =~ m{^/}) {
50 |
51 | # absolute
52 | } else {
53 | my $p = abs_path $path or die "abs_path '$path': $!";
54 | $path = $p;
55 | }
56 | warn "path=" . singlequote($path) if $DEBUG;
57 |
58 | open my $in, "<", $path or die "could not open '$path': $!";
59 |
60 | local $/;
61 | my $content = <$in>;
62 | close $in or die "closing '$path': $!";
63 | CHECK: {
64 | while ($content =~ m{\bpackage +([\w:]+)}g) {
65 | my $namespace = $1;
66 | if ($class =~ m/\Q$namespace\E$/) {
67 | warn "cutting '$class' down to '$namespace'\n" if $DEBUG;
68 | $class = $namespace;
69 | last CHECK;
70 | }
71 | }
72 | die "could not find any package definition in '$path' "
73 | . "matching its path";
74 | }
75 | $class
76 | }
77 |
78 | sub package_of_path_or_package {
79 | my ($path_or_package) = @_;
80 | if ($path_or_package =~ m{(\S+\.pm)}) {
81 | package_of_path($1)
82 | } elsif ($path_or_package =~ m{^(\w+\:\:)*\w+\z}s) {
83 | $path_or_package
84 | } elsif ($path_or_package =~ m{^(\w+/)*\w+\z}s) {
85 | $path_or_package =~ s|/|::|sg;
86 | $path_or_package
87 | } else {
88 | die "doesn't look sane: " . singlequote($path_or_package)
89 | }
90 | }
91 |
92 | 1
93 |
--------------------------------------------------------------------------------
/lib/Chj/TerseDumper.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2015-2019 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | Chj::TerseDumper
13 |
14 | =head1 SYNOPSIS
15 |
16 | use Chj::TerseDumper;
17 | my $foo = +{ foo => 1, bar => 10, baz => -1 };
18 | is terseDumper($foo), "XXX";
19 | is TerseDumper($foo), "XXX";
20 |
21 | =head1 DESCRIPTION
22 |
23 | Runs Data::Dumper's Dumper with $Data::Dumper::Terse set to 1.
24 |
25 | =head1 NOTE
26 |
27 | This is alpha software! Read the status section in the package README
28 | or on the L.
29 |
30 | =cut
31 |
32 | package Chj::TerseDumper;
33 | use strict;
34 | use warnings;
35 | use warnings FATAL => 'uninitialized';
36 | use Exporter "import";
37 |
38 | our @EXPORT = qw(TerseDumper terseDumper);
39 | our @EXPORT_OK = qw(UnsortedTerseDumper);
40 | our %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]);
41 |
42 | use Data::Dumper;
43 |
44 | sub UnsortedTerseDumper {
45 | local $Data::Dumper::Terse = 1;
46 | Dumper(@_)
47 | }
48 |
49 | sub TerseDumper {
50 | local $Data::Dumper::Sortkeys = 1;
51 | UnsortedTerseDumper(@_)
52 | }
53 |
54 | sub terseDumper {
55 | my $str = TerseDumper(@_);
56 | chomp $str;
57 | $str
58 | }
59 |
60 | 1
61 |
--------------------------------------------------------------------------------
/lib/Chj/Unix/Exitcode.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2007-2020 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | Chj::Unix::Exitcode
13 |
14 | =head1 SYNOPSIS
15 |
16 | =head1 DESCRIPTION
17 |
18 |
19 | =head1 NOTE
20 |
21 | This is alpha software! Read the status section in the package README
22 | or on the L.
23 |
24 | =cut
25 |
26 | package Chj::Unix::Exitcode;
27 | use strict;
28 | use warnings;
29 | use warnings FATAL => 'uninitialized';
30 | use Exporter "import";
31 |
32 | our @EXPORT = qw(exitcode);
33 | our @EXPORT_OK = qw(exitcode);
34 | our %EXPORT_TAGS = (all => \@EXPORT_OK);
35 |
36 | use FP::Carp;
37 |
38 | package Chj::Unix::Exitcode::Exitcode {
39 |
40 | use Chj::Unix::Signal;
41 |
42 | use Chj::Class::Array -fields => -publica => 'code',
43 | ;
44 |
45 | sub new {
46 | my $class = shift;
47 | my $s = $class->SUPER::new;
48 | ($$s[Code]) = @_;
49 | $s
50 | }
51 |
52 | sub as_string {
53 | my $s = shift;
54 | my $code = $$s[Code];
55 | if ($code < 256) {
56 | "signal $code (" . Chj::Unix::Signal->new($code)->as_string . ")"
57 | } else {
58 | if (($code & 255) == 0) {
59 | "exit value " . ($code >> 8)
60 | } else {
61 | warn "does this ever happen?";
62 | "both exit value and signal ($code)"
63 | }
64 | }
65 | }
66 |
67 | end Chj::Class::Array;
68 | }
69 |
70 | sub exitcode {
71 | @_ == 1 or fp_croak_arity 1;
72 | my ($code) = @_;
73 | Chj::Unix::Exitcode::Exitcode->new($code)->as_string;
74 | }
75 |
76 | 1
77 |
--------------------------------------------------------------------------------
/lib/Chj/Unix/Signal.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2007 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | Chj::Unix::Signal
13 |
14 | =head1 SYNOPSIS
15 |
16 | =head1 DESCRIPTION
17 |
18 |
19 | =head1 NOTE
20 |
21 | This is alpha software! Read the status section in the package README
22 | or on the L.
23 |
24 | =cut
25 |
26 | package Chj::Unix::Signal;
27 |
28 | use strict;
29 | use warnings;
30 | use warnings FATAL => 'uninitialized';
31 |
32 | use Chj::Class::Array -fields => -publica => 'number',
33 | ;
34 |
35 | sub new {
36 | my $class = shift;
37 | my $s = $class->SUPER::new;
38 | ($$s[Number]) = @_;
39 | $s
40 | }
41 |
42 | # how do we do reverse mapping? I did it already somewhere, I know.
43 | # and /bin/kill doesn't know all of them, bush eh bash is much better.
44 | # ah, man perlipc "Signals":
45 |
46 | our $inited = 0;
47 | our $signo;
48 | our $signame;
49 |
50 | sub MaybeInit {
51 | $inited ||= do {
52 | require Config;
53 | my $cfg = $Config::Config{sig_name};
54 | defined $cfg or die "No sigs?";
55 | my $i = 0;
56 | foreach my $name (split(' ', $cfg)) {
57 | $$signo{$name} = $i;
58 | $$signame[$i] = $name;
59 | $i++
60 | }
61 | 1
62 | }
63 | }
64 |
65 | sub as_string {
66 | my $s = shift;
67 | MaybeInit;
68 | my $maybe_str = $$signame[$$s[Number]];
69 | defined $maybe_str ? $maybe_str : ""
70 | }
71 |
72 | end Chj::Class::Array;
73 |
--------------------------------------------------------------------------------
/lib/Chj/Util/AskYN.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2003-2015 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | Chj::Util::AskYN
13 |
14 | =head1 SYNOPSIS
15 |
16 | use Chj::Util::AskYN;
17 |
18 | LP: {
19 | if (maybe_askyn "Do you want to retry?") {
20 | redo LP;
21 | }
22 | }
23 |
24 | =head1 DESCRIPTION
25 |
26 | Simply ask for a boolean question on stdout/stdin. Accept y/n, yes/no
27 | in english, german and french and return those as boolean
28 | true/false. If the user closes the input (using ctl-d), undef is
29 | returned.
30 |
31 | =head1 TODO
32 |
33 | Delete this and use something else?
34 |
35 | =head1 NOTE
36 |
37 | This is alpha software! Read the status section in the package README
38 | or on the L.
39 |
40 | =cut
41 |
42 | package Chj::Util::AskYN;
43 | use Exporter "import";
44 | our @EXPORT = qw(maybe_askyn);
45 |
46 | use strict;
47 | use warnings;
48 | use warnings FATAL => 'uninitialized';
49 |
50 | sub maybe_askyn {
51 | my ($maybe_prompt) = @_;
52 | local $| = 1;
53 | ASK: {
54 | if (defined $maybe_prompt) {
55 | print $maybe_prompt;
56 | }
57 | print " ";
58 | my $ans = ;
59 | if (defined $ans) {
60 | if ($ans =~ /^n(?:o|ein|ada|on)?$/i) {
61 | ''
62 | } elsif ($ans =~ /^(?:ja|yes|j|y|oui)$/i) {
63 | 1
64 | } else {
65 | print "Please answer with yes or no or their initials, "
66 | . "or the same in french or german.\n";
67 | redo ASK;
68 | }
69 | } else {
70 |
71 | # EOF, i.e. ctl-d
72 | print "\n";
73 | undef
74 | }
75 | }
76 | }
77 |
78 | 1
79 |
--------------------------------------------------------------------------------
/lib/Chj/chompspace.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2004-2020 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | Chj::chompspace
13 |
14 | =head1 SYNOPSIS
15 |
16 | =head1 DESCRIPTION
17 |
18 |
19 | =head1 NOTE
20 |
21 | This is alpha software! Read the status section in the package README
22 | or on the L.
23 |
24 | =cut
25 |
26 | package Chj::chompspace;
27 | use strict;
28 | use warnings;
29 | use warnings FATAL => 'uninitialized';
30 | use Exporter "import";
31 |
32 | our @EXPORT = qw(chompspace);
33 |
34 | #@EXPORT_OK = qw();
35 |
36 | use FP::Carp;
37 |
38 | sub chompspace {
39 | @_ == 1 or fp_croak_arity 1;
40 | my ($str) = @_;
41 | $str =~ s/^\s+//s;
42 | $str =~ s/\s+\z//s;
43 | $str
44 | }
45 |
46 | *Chj::chompspace = \&chompspace;
47 |
48 | 1;
49 |
--------------------------------------------------------------------------------
/lib/Chj/noTEST.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2013-2019 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | Chj::noTEST
13 |
14 | =head1 SYNOPSIS
15 |
16 | use Chj::noTEST; # instead of `use Chj::TEST;`
17 | TEST { foo() } "bar"; # will be ignored / garbage collected right away
18 |
19 | =head1 DESCRIPTION
20 |
21 | Disable TEST and TEST_STDOUT forms within a package, perhaps because
22 | they currently fail or are slow, or so that they never use memory.
23 |
24 | Note that you can alternatively ignore *all* test forms within the
25 | whole program by setting the TEST environment variable to 0. Also you
26 | can pass package names to `run_tests` to limit the tests to run to
27 | those within the given packages.
28 |
29 | =head1 SEE ALSO
30 |
31 | L
32 |
33 | =head1 NOTE
34 |
35 | This is alpha software! Read the status section in the package README
36 | or on the L.
37 |
38 | =cut
39 |
40 | package Chj::noTEST;
41 | use strict;
42 | use warnings;
43 | use warnings FATAL => 'uninitialized';
44 | use Exporter "import";
45 |
46 | our @EXPORT = qw(TEST TEST_STDOUT TEST_EXCEPTION GIVES perhaps_run_tests);
47 | our @EXPORT_OK = qw();
48 | our %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]);
49 |
50 | use Chj::TEST ();
51 |
52 | #*import = \&Chj::TEST::import;
53 |
54 | # to avoid the redefinition warning, use the glob:
55 | *import = *Chj::TEST::import;
56 |
57 | sub TEST (&$) { () }
58 |
59 | sub TEST_STDOUT (&$) { () }
60 |
61 | sub TEST_EXCEPTION (&$) { () }
62 |
63 | sub GIVES (&) { () }
64 |
65 | sub perhaps_run_tests { () }
66 |
67 | 1
68 |
--------------------------------------------------------------------------------
/lib/Chj/pp.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2015-2021 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | Chj::pp -- pretty printing as a debugging help
13 |
14 | =head1 SYNOPSIS
15 |
16 | use Chj::pp;
17 |
18 | print pp (1/2) + 1, "\n"; # prints "0.5\n" to stderr then "1.5\n" to stdout
19 |
20 | print pp_ ("x", 1/2) + 1, "\n"; # prints "x: 0.5\n" to stderr then see above
21 |
22 | =head1 DESCRIPTION
23 |
24 |
25 | =head1 NOTE
26 |
27 | This is alpha software! Read the status section in the package README
28 | or on the L.
29 |
30 | =cut
31 |
32 | package Chj::pp;
33 | use strict;
34 | use warnings;
35 | use warnings FATAL => 'uninitialized';
36 | use Exporter "import";
37 |
38 | our @EXPORT = qw(pp pp_);
39 | our @EXPORT_OK = qw();
40 | our %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]);
41 |
42 | use Data::Dumper;
43 | use Chj::TerseDumper;
44 |
45 | sub Dump {
46 | @_ > 1 ? Dumper(@_) : TerseDumper(@_)
47 | }
48 |
49 | sub pp {
50 | print STDERR Dump(@_);
51 | wantarray ? @_ : $_[-1] ## no critic
52 | }
53 |
54 | sub pp_ {
55 | my $msg = shift;
56 | print STDERR "$msg:", (@_ > 1 ? "\n" : " "), Dump(@_);
57 | wantarray ? @_ : $_[-1] ## no critic
58 | }
59 |
60 | 1
61 |
--------------------------------------------------------------------------------
/lib/Chj/singlequote/t.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2015 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | Chj::singlequote::t -- tests for Chj::singlequote
13 |
14 | =head1 SYNOPSIS
15 |
16 | # is tested by `t/require_and_run_tests`
17 |
18 | =head1 DESCRIPTION
19 |
20 |
21 | =head1 NOTE
22 |
23 | This is alpha software! Read the status section in the package README
24 | or on the L.
25 |
26 | =cut
27 |
28 | package Chj::singlequote::t;
29 |
30 | use strict;
31 | use warnings;
32 | use warnings FATAL => 'uninitialized';
33 |
34 | use Chj::singlequote ":all";
35 | use Chj::TEST;
36 |
37 | TEST {
38 | with_maxlen 9, sub { singlequote "Darc's place" }
39 | }
40 | "'Darc\\'s...'";
41 |
42 | 1
43 |
--------------------------------------------------------------------------------
/lib/Chj/tempdir.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2013-2020 by Christian Jaeger, ch at christianjaeger ch
3 | # This is free software, offered under the terms of the MIT License.
4 | # See the file COPYING that came bundled with this file.
5 | #
6 |
7 | =head1 NAME
8 |
9 | Chj::tempdir
10 |
11 | =head1 SYNOPSIS
12 |
13 | =head1 DESCRIPTION
14 |
15 | A simple tempdir procedure, without auto cleanup.
16 |
17 | =head1 NOTE
18 |
19 | This is alpha software! Read the status section in the package README
20 | or on the L.
21 |
22 | =cut
23 |
24 | package Chj::tempdir;
25 | use strict;
26 | use warnings FATAL => 'uninitialized';
27 | use Exporter "import";
28 |
29 | our @EXPORT = qw(tempdir);
30 | our @EXPORT_OK = qw();
31 | our %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]);
32 |
33 | use FP::Carp;
34 |
35 | sub tempdir {
36 | @_ == 1 or fp_croak_arity 1;
37 | my ($base) = @_;
38 | my $tries = 0;
39 | my $perhapsrnd = "";
40 | TRY: {
41 | my $path = "$base-${$}${perhapsrnd}";
42 | if (mkdir $path, 0700) {
43 | return $path
44 | } else {
45 | $tries++;
46 | $perhapsrnd = "-" . substr(rand, 2, 7);
47 | redo TRY if ($tries < 10);
48 | die "can't mkdir '$path': $!";
49 | }
50 | }
51 | }
52 |
53 | 1
54 |
--------------------------------------------------------------------------------
/lib/Chj/xoutpipe.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2003-2022 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | # Depends: ()
11 |
12 | =head1 NAME
13 |
14 | Chj::xoutpipe
15 |
16 | =head1 SYNOPSIS
17 |
18 | use Chj::xoutpipe;
19 | {
20 | my $p = xoutpipe "sendmail","-t";
21 | $p->xprint("From: $from\n");
22 | my $rv = $p->xfinish; # does close and waitpid, returns $?
23 | # see Chj::IO::Command for more methods.
24 | }
25 |
26 | =head1 DESCRIPTION
27 |
28 | Start external process with a writing pipe attached. Return the filehandle which
29 | is a Chj::IO::Command (which is a Chj::IO::Pipe which is a Chj::IO::File) object.
30 |
31 | =head1 SEE ALSO
32 |
33 | L, L, L
34 |
35 | =head1 NOTE
36 |
37 | This is alpha software! Read the status section in the package README
38 | or on the L.
39 |
40 | =cut
41 |
42 | package Chj::xoutpipe;
43 | use strict;
44 | use warnings;
45 | use warnings FATAL => 'uninitialized';
46 | use Chj::IO::Command;
47 | use Exporter 'import';
48 |
49 | our @EXPORT = qw(xoutpipe);
50 |
51 | sub xoutpipe {
52 | Chj::IO::Command->new_receiver(@_);
53 | }
54 | *Chj::xoutpipe = \&xoutpipe;
55 |
56 | 1
57 |
--------------------------------------------------------------------------------
/lib/Chj/xpipe.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2003-2021 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | Chj::xpipe
13 |
14 | =head1 SYNOPSIS
15 |
16 | use Chj::xpipe;
17 | my ($read,$write) = xpipe; # or xpipe READ,WRITE ? hmmm. not yet.
18 | $read->xclose;
19 | $write->xprint("Hello");
20 |
21 | =head1 DESCRIPTION
22 |
23 | Returns two Chj::IO::Pipe filehandles/objects.
24 |
25 | =head1 NOTE
26 |
27 | You should trap SIGPIPE or the program will exit before an exception
28 | is thrown.
29 |
30 | =head1 SEE ALSO
31 |
32 | L, L
33 |
34 | =head1 NOTE
35 |
36 | This is alpha software! Read the status section in the package README
37 | or on the L.
38 |
39 | =cut
40 |
41 | package Chj::xpipe;
42 | use strict;
43 | use warnings;
44 | use warnings FATAL => 'uninitialized';
45 | use Exporter 'import';
46 |
47 | our @EXPORT = qw(xpipe);
48 |
49 | use Chj::IO::Pipe;
50 | use Carp;
51 |
52 | sub xpipe {
53 | if (@_) {
54 | confess "form with arguments not yet supported";
55 | } else {
56 | my $r = Chj::IO::Pipe->new;
57 | my $w = Chj::IO::Pipe->new;
58 | pipe $r, $w or croak "xpipe: $!";
59 | ($r, $w)
60 | }
61 | }
62 |
63 | 1;
64 |
--------------------------------------------------------------------------------
/lib/Chj/xtmpfile.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2003-2020 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | Chj::xtmpfile
13 |
14 | =head1 SYNOPSIS
15 |
16 | =head1 DESCRIPTION
17 |
18 |
19 | =head1 NOTE
20 |
21 | This is alpha software! Read the status section in the package README
22 | or on the L.
23 |
24 | =cut
25 |
26 | package Chj::xtmpfile;
27 | use strict;
28 | use warnings;
29 | use warnings FATAL => 'uninitialized';
30 | use Exporter 'import';
31 |
32 | our @EXPORT = qw(xtmpfile);
33 |
34 | use Chj::IO::Tempfile;
35 |
36 | sub xtmpfile {
37 | unshift @_, 'Chj::IO::Tempfile';
38 | goto &Chj::IO::Tempfile::xtmpfile;
39 | }
40 |
41 | 1
42 |
--------------------------------------------------------------------------------
/lib/FP/Abstract/Compare.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2020 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | FP::Abstract::Compare - comparison protocol
13 |
14 | =head1 SYNOPSIS
15 |
16 | package FPCompareExample::Foo {
17 | use FP::Struct ["num"];
18 | sub FP_Compare_compare {
19 | my ($a, $b) = @_;
20 | # let's make the default sort order reversed for the sake
21 | # of a more interesting example:
22 | $b->num cmp $a->num
23 | }
24 | _END_
25 | }
26 |
27 | use FP::PureArray; use FP::Ops 'the_method'; use FP::Equal qw(is_equal);
28 | is_equal( purearray(2,3,-7,4,2,8)
29 | ->map(\&FPCompareExample::Foo::c::Foo)
30 | ->sortCompare
31 | ->map(the_method("num")),
32 | purearray(8, 4, 3, 2, 2, -7));
33 |
34 | =head1 DESCRIPTION
35 |
36 | Objects implementing this protocol can be ordered unambiguously.
37 |
38 | The `$a->FP_Compare_compare($b)` returns -1 if $a is to be ordered before
39 | $b (is smaller), 0 if they are to be ordered into the same position, 1
40 | if $a is to be ordered after $b.
41 |
42 | This method is used for the default sort order by the `sort` method
43 | offered on sequences.
44 |
45 | =head1 SEE ALSO
46 |
47 | L
48 |
49 | =head1 NOTE
50 |
51 | This is alpha software! Read the status section in the package README
52 | or on the L.
53 |
54 | =cut
55 |
56 | package FP::Abstract::Compare;
57 |
58 | use strict;
59 | use warnings;
60 | use warnings FATAL => 'uninitialized';
61 |
62 | sub FP_Interface__method_names { ("FP_Compare_compare") }
63 |
64 | 1
65 |
--------------------------------------------------------------------------------
/lib/FP/Abstract/Equal.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2019 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | FP::Abstract::Equal - equality protocol
13 |
14 | =head1 SYNOPSIS
15 |
16 | package FPEqualExample::Foo {
17 | sub new { my $class = shift; bless [@_], $class }
18 | sub FP_Equal_equal {
19 | my ($a, $b) = @_;
20 | # If you know you've got numbers in here only:
21 | $$a[0] == $$b[0]
22 | # For generic values, you would instead:
23 | #use FP::Equal;
24 | #equal($$a[0], $$b[0])
25 | }
26 | }
27 |
28 | use FP::Equal qw(equal); use FP::List;
29 |
30 | ok equal( list(10,20,30)->map
31 | (sub{ equal(FPEqualExample::Foo->new(20),
32 | FPEqualExample::Foo->new($_[0])) }),
33 | list('', 1, ''));
34 |
35 | =head1 DESCRIPTION
36 |
37 | Objects implementing this protocol can be compared using the functions
38 | from L, primarily C.
39 |
40 | The C function forces promises before doing further comparisons
41 | or passing them to the C method (only the immediate
42 | layer, not deeply). C is only ever called with the two
43 | arguments (self and one method argument) being references of,
44 | currently, the same type (C handles the other cases internally)
45 | (TODO: how to handle subtypes?). In better(?) words, C
46 | implementations can rely on the second argument supporting the same
47 | operations that the first one does (TODO: even into the future once
48 | accepting subtyping? This is *alpha*.) Likewise, C is
49 | not called if the arguments are both the same reference (in this case
50 | C simply returns true).
51 |
52 | =head1 TODO
53 |
54 | Handle circular data structures.
55 |
56 | =head1 SEE ALSO
57 |
58 | L
59 |
60 | =head1 NOTE
61 |
62 | This is alpha software! Read the status section in the package README
63 | or on the L.
64 |
65 | =cut
66 |
67 | package FP::Abstract::Equal;
68 |
69 | use strict;
70 | use warnings;
71 | use warnings FATAL => 'uninitialized';
72 |
73 | sub FP_Interface__method_names { ("FP_Equal_equal") }
74 |
75 | 1
76 |
--------------------------------------------------------------------------------
/lib/FP/Abstract/Id.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2021 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | FP::Abstract::Id - identity protocol
13 |
14 | =head1 SYNOPSIS
15 |
16 | =head1 DESCRIPTION
17 |
18 | This protocol handles identification for values (objects). An
19 | identifier is a string. Classes implementing this protocol must
20 | provide a `FP_Id_id' method that takes no other arguments and returns
21 | the identifier for the set of objects which are considered identical
22 | by the class.
23 |
24 | The identifier may be used for sorting (e.g. L uses it
25 | to determine the sort order of the elements in L::show). It
26 | doesn't need to be human readable (thus could be implemented via
27 | hashing) but it might be useful if it is.
28 |
29 | =head1 SEE ALSO
30 |
31 | L
32 |
33 | =head1 NOTE
34 |
35 | This is alpha software! Read the status section in the package README
36 | or on the L.
37 |
38 | =cut
39 |
40 | package FP::Abstract::Id;
41 | use strict;
42 | use utf8;
43 | use warnings;
44 | use warnings FATAL => 'uninitialized';
45 | use Exporter "import";
46 |
47 | sub FP_Interface__method_names {
48 | my $class = shift;
49 | qw(FP_Id_id)
50 | }
51 |
52 | 1
53 |
--------------------------------------------------------------------------------
/lib/FP/Abstract/Interface.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2019 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | FP::Abstract::Interface - protocol for an interface
13 |
14 | =head1 SYNOPSIS
15 |
16 | =head1 DESCRIPTION
17 |
18 | An interface is just a named base type that other types implement. Its
19 | purpose is:
20 |
21 | 1. to be used as a base type to declare a particular behaviour of the
22 | new type, and to define what that behaviour entails (currently just
23 | a set of method names that are expected to be implemented)
24 |
25 | 2. to have the defined behaviour (the set of methods) checked at load
26 | time of any module that implements the interface, and issue a
27 | warning if anything is missing.
28 |
29 | 3. to allow code to check at runtime whether objects conform to an
30 | interface (early dynamic error detection)
31 |
32 | Point (2) is implemented by `FP::Struct`: when defining a class via
33 | `FP::Struct`, each given parent class is checked for the
34 | implementation of a method `FP_Interface__method_names` via
35 | `can()`. This method, if present, is supposed to ignore arguments and
36 | return a list of the names of the set of methods that is required to
37 | implement the interface. This method is called once at load time of
38 | each module that defines such a class.
39 |
40 | =head1 FUTURE / TODO
41 |
42 | Not only declare and check the method names, but also the arity (how
43 | many arguments it takes), argument names, and, once FP::Types is done,
44 | optionally types.
45 |
46 | At that point, offer more methods for introspection (IDE support).
47 |
48 | Extend to generic functions that support the same "methods" on
49 | unblessed references or non-reference values.
50 |
51 | Perhaps (given some reason) rename FP::Abstract::* to FP::Protocol::*.
52 |
53 | =head1 SEE ALSO
54 |
55 | L
56 |
57 | =head1 NOTE
58 |
59 | This is alpha software! Read the status section in the package README
60 | or on the L.
61 |
62 | =cut
63 |
64 | package FP::Abstract::Interface;
65 |
66 | use strict;
67 | use warnings;
68 | use warnings FATAL => 'uninitialized';
69 |
70 | sub FP_Interface__method_names {
71 | my $class = shift;
72 |
73 | # If we're extending another interface (not the case here though),
74 | # we need to merge its interface definition with ours:
75 | ((), $class->SUPER::FP_Interface__method_names)
76 | }
77 |
78 | 1
79 |
--------------------------------------------------------------------------------
/lib/FP/Abstract/Map.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2019 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | FP::Abstract::Map - functional map protocol
13 |
14 | =head1 SYNOPSIS
15 |
16 | =head1 DESCRIPTION
17 |
18 | =head1 SEE ALSO
19 |
20 | L
21 |
22 | =head1 NOTE
23 |
24 | This is alpha software! Read the status section in the package README
25 | or on the L.
26 |
27 | =cut
28 |
29 | package FP::Abstract::Map;
30 | use strict;
31 | use warnings;
32 | use warnings FATAL => 'uninitialized';
33 |
34 | use Chj::NamespaceCleanAbove;
35 |
36 | sub FP_Interface__method_names {
37 | qw(ref perhaps_ref set)
38 | }
39 |
40 | _END_
41 |
--------------------------------------------------------------------------------
/lib/FP/Abstract/Pure.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2015-2020 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | FP::Abstract::Pure - functional purity protocol
13 |
14 | =head1 SYNOPSIS
15 |
16 | use FP::List;
17 | use Safe::Isa;
18 | my $v = list(1); # or any other data structure that implements
19 | # FP::Abstract::Pure
20 | is $v->$_isa("FP::Abstract::Pure"), 1;
21 |
22 | # but usually:
23 | use FP::Predicates;
24 | is_pure($v) # true if $v does (officially) not support mutation
25 |
26 | =head1 DESCRIPTION
27 |
28 | Base class for all data structures that don't allow mutation (by
29 | ordinary programs), i.e. are
30 | L.
31 |
32 | More precisely, those objects that don't have methods that when called
33 | make other methods non-functions.
34 |
35 | =head1 NOTE
36 |
37 | This is alpha software! Read the status section in the package README
38 | or on the L.
39 |
40 | =cut
41 |
42 | package FP::Abstract::Pure;
43 |
44 | use strict;
45 | use warnings;
46 | use warnings FATAL => 'uninitialized';
47 |
48 | sub FP_Interface__method_names { () }
49 |
50 | 1
51 |
--------------------------------------------------------------------------------
/lib/FP/Abstract/Show.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2019 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | FP::Abstract::Show - data constructor protocol
13 |
14 | =head1 SYNOPSIS
15 |
16 | package FPShowExample::Foo {
17 | sub new { my $class = shift; bless [@_], $class }
18 | sub FP_Show_show {
19 | my ($self, $show) = @_;
20 | # $show is for recursive use
21 | "FPShowExample::Foo->new(".join(", ",
22 | map { $show->($_) } @$self).")"
23 | }
24 | }
25 |
26 | use FP::Show;
27 |
28 | is show(FPShowExample::Foo->new("hey", new FPShowExample::Foo 5+5)),
29 | "FPShowExample::Foo->new('hey', FPShowExample::Foo->new(10))";
30 |
31 | =head1 DESCRIPTION
32 |
33 | For an introduction, see L.
34 |
35 | The reason that C is getting a C<$show> argument is to
36 | provide for (probably evil, though) context sensitive formatting, but
37 | more importantly to hopefully enable to do pretty-printing and cut-off
38 | features (this is *alpha* though, see whether this works out).
39 |
40 |
41 | =head1 TODO
42 |
43 | Handle circular data structures.
44 |
45 | Pretty-printing -- for this, probably move to returning FP::AST::Perl
46 | nodes instead of strings.
47 |
48 | Declare that non-pretty-printing show must only print one line?
49 |
50 | Cut-offs at configurable size
51 |
52 | Configuration for whether to force promises
53 |
54 | =head1 SEE ALSO
55 |
56 | L -- functions to access this protocol usefully
57 |
58 | L -- implemented by this protocol
59 |
60 | =head1 NOTE
61 |
62 | This is alpha software! Read the status section in the package README
63 | or on the L.
64 |
65 | =cut
66 |
67 | package FP::Abstract::Show;
68 |
69 | use strict;
70 | use warnings;
71 | use warnings FATAL => 'uninitialized';
72 |
73 | use base qw(FP::Mixin::Utils);
74 |
75 | sub FP_Interface__method_names { ("FP_Show_show") }
76 |
77 | 1
78 |
--------------------------------------------------------------------------------
/lib/FP/BigInt.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2019-2020 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | FP::BigInt
13 |
14 | =head1 SYNOPSIS
15 |
16 | use FP::Equal 'is_equal'; use FP::Show;
17 | use FP::BigInt;
18 |
19 | is_equal ref(bigint(13)), 'Math::BigInt';
20 | is_equal "".(bigint(10)**20), '100000000000000000000';
21 | is_equal bigint(13) / bigint(10), bigint('1');
22 |
23 | is show(bigint(7)), "bigint('7')";
24 |
25 | =head1 DESCRIPTION
26 |
27 | Loads L, monkey patches C and
28 | C methods into it, and exports the C
29 | constructor function.
30 |
31 | =head1 SEE ALSO
32 |
33 | L
34 |
35 | L, L -- implemented protocols
36 |
37 | =cut
38 |
39 | package FP::BigInt;
40 | use strict;
41 | use warnings;
42 | use warnings FATAL => 'uninitialized';
43 | use Exporter "import";
44 |
45 | our @EXPORT = qw(bigint);
46 | our @EXPORT_OK = qw();
47 | our %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]);
48 |
49 | use Math::BigInt;
50 | use FP::Carp;
51 |
52 | #use FP::Interfaces;
53 |
54 | sub bigint {
55 | @_ == 1 or fp_croak_arity 1;
56 | Math::BigInt->new($_[0])
57 | }
58 |
59 | package # Monkey patching; but using long ucfirst (TODO: go all
60 | # uppercase(?)) method names that should never conflict with
61 | # anybody else's. Use case doesn't really allow for
62 | # subclassing. Might look into implementing some sort of
63 | # lexical extensions of classes at some point.
64 | Math::BigInt {
65 |
66 | sub FP_Show_show {
67 | my $s = shift;
68 | "bigint('$s')"
69 | }
70 |
71 | sub FP_Equal_equal {
72 | my ($a, $b) = @_;
73 | $a == $b
74 | }
75 |
76 | # commented out to not modify Math::BigInt's @ISA, although it
77 | # would be good to have for consistency.
78 | #FP::Interfaces::implemented qw(FP::Abstract::Show
79 | # FP::Abstract::Equal);
80 | }
81 |
82 | 1
83 |
--------------------------------------------------------------------------------
/lib/FP/Char.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2013-2020 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | FP::Char - functions to handle individual characters
13 |
14 | =head1 SYNOPSIS
15 |
16 | =head1 DESCRIPTION
17 |
18 | Perl doesn't have a distinct data type for individual characters, any
19 | string containing 1 character is considered to be a char by
20 | FP::Char. (Creating references and blessing them for the sake of
21 | type safety seemed excessive.)
22 |
23 |
24 | =head1 NOTE
25 |
26 | This is alpha software! Read the status section in the package README
27 | or on the L.
28 |
29 | =cut
30 |
31 | package FP::Char;
32 | use strict;
33 | use warnings;
34 | use warnings FATAL => 'uninitialized';
35 | use Exporter "import";
36 |
37 | our @EXPORT = qw();
38 | our @EXPORT_OK = qw(is_char char_is_whitespace char_is_alphanumeric);
39 | our %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]);
40 |
41 | use FP::Carp;
42 |
43 | sub is_char {
44 | @_ == 1 or fp_croak_arity 1;
45 | my ($v) = @_;
46 | defined $v and not(ref $v) and length($v) == 1
47 | }
48 |
49 | sub char_is_whitespace {
50 |
51 | # not including non-breaking space, OK?
52 | $_[0] =~ /^[ \r\n\t\f]$/s
53 | }
54 |
55 | sub char_is_alphanumeric {
56 | $_[0] =~ /^[a-zA-Z0-9_]$/s
57 | }
58 |
59 | 1
60 |
61 |
--------------------------------------------------------------------------------
/lib/FP/Cmp.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2013-2022 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 | #
10 |
11 | =head1 NAME
12 |
13 | FP::Cmp - 3-way comparison helpers
14 |
15 | =head1 SYNOPSIS
16 |
17 | =head1 DESCRIPTION
18 |
19 | =head1 SEE ALSO
20 |
21 | =head1 NOTE
22 |
23 | This is alpha software! Read the status section in the package README
24 | or on the L.
25 |
26 | =cut
27 |
28 | package FP::Cmp;
29 | use strict;
30 | use utf8;
31 | use warnings;
32 | use warnings FATAL => 'uninitialized';
33 |
34 | #use experimental 'signatures';
35 | use Exporter "import";
36 |
37 | our @EXPORT = qw(cmp_complement cmp_then);
38 | our @EXPORT_OK = qw();
39 | our %EXPORT_TAGS = (default => \@EXPORT, all => [@EXPORT, @EXPORT_OK]);
40 |
41 | use FP::Carp;
42 | use Chj::TEST;
43 | use FP::Ops qw(binary_operator);
44 |
45 | # see also `complement` from FP::Predicates
46 | sub cmp_complement {
47 | @_ == 1 or fp_croak_arity 1;
48 | my ($cmp) = @_;
49 | sub {
50 | -&$cmp(@_)
51 | }
52 | }
53 |
54 | TEST {
55 | my $f = cmp_complement binary_operator "cmp";
56 | [
57 | map { &$f(@$_) } (
58 | [2, 4],
59 | [4, 2],
60 | [3, 3],
61 | ["abc", "bbc"],
62 | ["ab", "ab"],
63 | ["bbc", "abc"]
64 | )
65 | ]
66 | }
67 | [1, -1, 0, 1, 0, -1];
68 |
69 | sub cmp_then {
70 |
71 | # chain of cmp until one is non-0
72 | my @cmp = @_;
73 | sub {
74 | my ($a, $b) = @_;
75 | for my $cmp (@cmp) {
76 | if (my $res = $cmp->($a, $b)) {
77 | return $res
78 | }
79 | }
80 | 0
81 | }
82 | }
83 |
84 | 1
85 |
--------------------------------------------------------------------------------
/lib/FP/Combinators2.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2019 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | FP::Combinators2 - more function combinators
13 |
14 | =head1 SYNOPSIS
15 |
16 | use FP::Combinators2 ":all";
17 | use FP::Array qw(array); use FP::Equal ":all";
18 |
19 | my $ra = right_associate_(\&array, 0);
20 | is_equal $ra->(qw(a b c d)),
21 | ['a', ['b', ['c', 'd']]];
22 | is_equal $ra->(qw(a b)), ['a', 'b'];
23 | is_equal $ra->(qw(a)), 'a';
24 | is_equal $ra->(), 0;
25 |
26 | my $la = left_associate_(\&array, 0);
27 | is_equal $la->(qw(a b c d)),
28 | [[['a', 'b'], 'c'], 'd'];
29 | is_equal $la->(qw(a b)), ['a', 'b'];
30 | is_equal $la->(qw(a)), 'a';
31 | is_equal $la->(), 0;
32 |
33 |
34 | =head1 DESCRIPTION
35 |
36 | This is an extension of L for functions that need
37 | more dependencies and can't be put into the former because of circular
38 | dependencies.
39 |
40 | =head1 SEE ALSO
41 |
42 | L
43 |
44 | =head1 NOTE
45 |
46 | This is alpha software! Read the status section in the package README
47 | or on the L.
48 |
49 | =cut
50 |
51 | package FP::Combinators2;
52 | use strict;
53 | use warnings;
54 | use warnings FATAL => 'uninitialized';
55 | use Exporter "import";
56 |
57 | our @EXPORT = qw();
58 | our @EXPORT_OK = qw(
59 | right_associate_
60 | left_associate_);
61 | our %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]);
62 |
63 | #use Chj::TEST;
64 | use FP::PureArray;
65 | use FP::Combinators qw(flip);
66 | use FP::Carp;
67 |
68 | sub right_associate_ {
69 | @_ == 2 or fp_croak_arity 2;
70 | my ($op, $noop) = @_;
71 | sub {
72 | @_
73 | ? do {
74 | my $init = pop;
75 | purearray(@_)->fold_right($op, $init)
76 | }
77 | : $noop
78 | }
79 | }
80 |
81 | sub left_associate_ {
82 | @_ == 2 or fp_croak_arity 2;
83 | my ($op, $noop) = @_;
84 | my $op2 = flip $op;
85 | sub {
86 | @_
87 | ? do {
88 | my $init = shift;
89 | purearray(@_)->fold($op2, $init)
90 | }
91 | : $noop
92 | }
93 | }
94 |
95 | 1
96 |
--------------------------------------------------------------------------------
/lib/FP/Div.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2014-2020 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | FP::Div - various pure functions
13 |
14 | =head1 SYNOPSIS
15 |
16 | =head1 DESCRIPTION
17 |
18 |
19 | =head1 NOTE
20 |
21 | This is alpha software! Read the status section in the package README
22 | or on the L.
23 |
24 | =cut
25 |
26 | package FP::Div;
27 | use strict;
28 | use warnings;
29 | use warnings FATAL => 'uninitialized';
30 | use Exporter "import";
31 |
32 | our @EXPORT = qw();
33 | our @EXPORT_OK = qw(inc dec square average
34 | identity
35 | min max minmax
36 | Chomp
37 | );
38 | our %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]);
39 |
40 | use Chj::TEST;
41 | use FP::Carp;
42 |
43 | # XX should `indentity` pass multiple values, and this be called
44 | # `identity_scalar`? :
45 |
46 | sub identity {
47 | @_ == 1 or fp_croak_arity 1;
48 | $_[0]
49 | }
50 |
51 | sub inc {
52 | @_ == 1 or fp_croak_arity 1;
53 | $_[0] + 1
54 | }
55 |
56 | sub dec {
57 | @_ == 1 or fp_croak_arity 1;
58 | $_[0] - 1
59 | }
60 |
61 | sub square {
62 | @_ == 1 or fp_croak_arity 1;
63 | $_[0] * $_[0]
64 | }
65 |
66 | sub average {
67 | @_ == 2 or fp_croak_arity 2;
68 | ($_[0] + $_[1]) / 2
69 | }
70 |
71 | sub min {
72 | my $x = shift;
73 | for (@_) {
74 | $x = $_ if $_ < $x
75 | }
76 | $x
77 | }
78 |
79 | sub max {
80 | my $x = shift;
81 | for (@_) {
82 | $x = $_ if $_ > $x
83 | }
84 | $x
85 | }
86 |
87 | sub minmax {
88 | my $min = shift;
89 | my $max = $min;
90 | for (@_) {
91 | $min = $_ if $_ < $min;
92 | $max = $_ if $_ > $max;
93 | }
94 | ($min, $max)
95 | }
96 |
97 | # is there any better idea than ucfirst to distinguish from the
98 | # builtin? `fchomp` ?
99 | sub Chomp {
100 | @_ == 1 or fp_croak_arity 1;
101 | my ($str) = @_;
102 | chomp $str;
103 | $str
104 | }
105 |
106 | 1
107 |
--------------------------------------------------------------------------------
/lib/FP/Docstring/t.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2019-2021 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | FP::Docstring::t -- tests for FP::Docstring
13 |
14 | =head1 SEE ALSO
15 |
16 | L
17 |
18 | =cut
19 |
20 | package FP::Docstring::t;
21 | use strict;
22 | use utf8;
23 | use warnings;
24 | use warnings FATAL => 'uninitialized';
25 | use Exporter "import";
26 |
27 | our @EXPORT = qw();
28 | our @EXPORT_OK = qw();
29 | our %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]);
30 |
31 | use FP::Docstring;
32 | use Chj::TEST;
33 |
34 | # try to trick the parser:
35 | TEST {
36 | docstring(sub { __ "hi\');"; $_[0] + 1 })
37 | }
38 | 'hi\');';
39 | TEST {
40 | docstring(sub { __ "hi\");"; $_[0] + 1 })
41 | }
42 | 'hi");';
43 |
44 | # get the quoting right:
45 | TEST {
46 | docstring(sub { __ '($foo) -> hash'; $_[0] + 1 })
47 | }
48 | '($foo) -> hash';
49 | TEST {
50 | docstring(sub { __ '("$foo")'; $_[0] + 1 })
51 | }
52 | '("$foo")';
53 | TEST {
54 | docstring(sub { __ '(\'$foo\')'; $_[0] + 1 })
55 | }
56 | '(\'$foo\')';
57 | TEST {
58 | docstring sub {
59 | __ '($str, $token, {tokenargument => $value,..})-> $str
60 | re-insert hidden parts';
61 | 1
62 | }
63 | }
64 | '($str, $token, {tokenargument => $value,..})-> $str
65 | re-insert hidden parts';
66 |
67 | 1
68 |
--------------------------------------------------------------------------------
/lib/FP/DumperEqual.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2014-2020 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | FP::DumperEqual - equality
13 |
14 | =head1 SYNOPSIS
15 |
16 | use FP::DumperEqual;
17 |
18 | ok dumperequal [1, [2, 3]], [1, [1+1, 3]];
19 | ok not dumperequal [1, [2, 3]], [1, [1+2, 3]];
20 |
21 | my $s1 = "stringwithunicode";
22 | my $s2 = "stringwithunicode";
23 | utf8::decode($s2);
24 | # ok not dumperequal $s1, $s2;
25 | # ^-- hmm, it used to be the case that this gave false
26 | ok dumperequal_utf8 $s1, $s2;
27 |
28 |
29 | =head1 DESCRIPTION
30 |
31 | Deep structure equality comparison.
32 |
33 | NOTE: using Data::Dumper and thus slow.
34 |
35 | For a more proper solution, see FP::Equal
36 |
37 | =head1 SEE ALSO
38 |
39 | L
40 |
41 | =head1 NOTE
42 |
43 | This is alpha software! Read the status section in the package README
44 | or on the L.
45 |
46 | =cut
47 |
48 | package FP::DumperEqual;
49 | use strict;
50 | use warnings;
51 | use warnings FATAL => 'uninitialized';
52 | use Exporter "import";
53 |
54 | our @EXPORT = qw(dumperequal dumperequal_utf8);
55 | our @EXPORT_OK = qw();
56 | our %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]);
57 |
58 | use Data::Dumper;
59 | use FP::Carp;
60 |
61 | # XX these are expensive, of course. Better solution?
62 |
63 | sub dumperequal {
64 | local $Data::Dumper::Sortkeys = 1;
65 | my $v0 = shift;
66 | my $a0 = Dumper($v0);
67 | for (@_) {
68 | Dumper($_) eq $a0 or return '';
69 | }
70 | 1
71 | }
72 |
73 | sub dumperequal_utf8 {
74 | @_ == 2 or fp_croak_arity 2;
75 | local $Data::Dumper::Sortkeys = 1;
76 |
77 | # compare ignoring utf8 flags on strings
78 | local $Data::Dumper::Useperl = 1;
79 | my $v0 = shift;
80 | my $a0 = Dumper($v0);
81 | for (@_) {
82 | Dumper($_) eq $a0 or return '';
83 | }
84 | 1
85 | }
86 |
87 | 1
88 |
--------------------------------------------------------------------------------
/lib/FP/Id.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2021 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | FP::Id
13 |
14 | =head1 SYNOPSIS
15 |
16 | use FP::Id;
17 | is id("a"),"a";
18 | my $a = [];
19 | my $b = [];
20 | ok(id($a) eq id($a));
21 | ok not id($a) eq id($b);
22 | # Objects can implement FP::Abstract::Id to override using their
23 | # pointer as the id. *Or* should the default be the show() string?
24 |
25 | =head1 DESCRIPTION
26 |
27 | =head1 SEE ALSO
28 |
29 | L.
30 |
31 | =head1 NOTE
32 |
33 | This is alpha software! Read the status section in the package README
34 | or on the L.
35 |
36 | =cut
37 |
38 | package FP::Id;
39 | use strict;
40 | use utf8;
41 | use warnings;
42 | use warnings FATAL => 'uninitialized';
43 | use Exporter "import";
44 |
45 | our @EXPORT = qw(id);
46 | our @EXPORT_OK = qw();
47 | our %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]);
48 |
49 | use FP::Carp;
50 | use Scalar::Util qw(blessed);
51 |
52 | # XX confusion with the `identity` function? What other name would be
53 | # appropriate?
54 | sub id {
55 | @_ == 1 or fp_croak_arity 1;
56 | my ($v) = @_;
57 | if (blessed $v) {
58 | if (defined(my $m = $v->can("FP_Id_id"))) {
59 | $m->($v)
60 | } else {
61 | $v +0
62 | }
63 | } elsif (length ref $v) {
64 | $v +0
65 | } else {
66 | "$v"
67 | }
68 | }
69 |
70 | 1
71 |
--------------------------------------------------------------------------------
/lib/FP/Interfaces.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2019-2020 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | FP::Interfaces - implement interfaces
13 |
14 | =head1 SYNOPSIS
15 |
16 | See the synopsis of L for how to define an interface.
17 |
18 | {
19 | package Foo;
20 | use FP::Interfaces;
21 |
22 | sub foo { }
23 | sub fold { }
24 |
25 | FP::Interfaces::implemented qw(FP::Abstract::ExtendedSequence
26 | FP::Abstract::Pure);
27 | }
28 |
29 | =head1 DESCRIPTION
30 |
31 | This is just a wrapper around L to allow for multiple
32 | arguments and read as proper english.
33 |
34 | =head1 SEE ALSO
35 |
36 | L
37 |
38 | This implements: L
39 |
40 | =head1 NOTE
41 |
42 | This is alpha software! Read the status section in the package README
43 | or on the L.
44 |
45 | =cut
46 |
47 | package FP::Interfaces;
48 | use strict;
49 | use warnings;
50 | use warnings FATAL => 'uninitialized';
51 | use Exporter "import";
52 |
53 | our @EXPORT = qw();
54 | our @EXPORT_OK = qw();
55 | our %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]);
56 |
57 | use FP::Interface;
58 |
59 | # called fully qualified, i.e. FP::Interfaces::implemented (to avoid
60 | # namespace pollution in classes)
61 | sub implemented {
62 | my $caller = [caller];
63 | for my $interface (@_) {
64 | FP::Interface::require_package($interface);
65 | FP::Interface::implemented_with_caller($caller, $interface)
66 | }
67 | }
68 |
69 | 1
70 |
--------------------------------------------------------------------------------
/lib/FP/Lazy/t.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2013-2021 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | FP::Lazy::t -- tests for FP::Lazy
13 |
14 | =head1 SYNOPSIS
15 |
16 | =head1 DESCRIPTION
17 |
18 | Had to move them here to avoid dependency cycle.
19 |
20 | =cut
21 |
22 | package FP::Lazy::t;
23 |
24 | use strict;
25 | use warnings;
26 | use warnings FATAL => 'uninitialized';
27 |
28 | use FP::Lazy ":all";
29 | use Chj::TEST;
30 | use FP::Show;
31 | use FP::List;
32 |
33 | TEST {
34 | our $foo = "";
35 |
36 | sub moo {
37 | my ($bar) = @_;
38 | local $foo = "Hello";
39 | lazy {"$foo $bar"}
40 | }
41 | moo("you")->force
42 | }
43 | " you";
44 |
45 | TEST {
46 | show(lazy { 1 / 0 })
47 | }
48 | "lazy { 'DUMMY' }";
49 |
50 | TEST {
51 | show(lazyT { 1 / 0 } "Fun")
52 | }
53 | "lazyT { 'DUMMY' } 'Fun'";
54 |
55 | TEST {
56 | my $v = lazyT { cons(1, 2) } "FP::List::List";
57 | force $v;
58 | [is_promise($v), show($v)]
59 | }
60 | [1, 'improper_list(1, 2)'];
61 |
62 | # method dispatch logic:
63 |
64 | TEST {
65 | (lazyT { list("a") } "FP::List::List")->rest
66 | }
67 | null;
68 |
69 | TEST {
70 | (lazyT { list("a") } "FP::List::Pair")->rest
71 | }
72 | null;
73 |
74 | TEST_EXCEPTION {
75 | (lazyT { 1 / 0 } "FP::List::Null")->rest
76 | }
77 | 'can\'t take the rest of the empty list';
78 |
79 | 1
80 |
--------------------------------------------------------------------------------
/lib/FP/List/t.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2022-2023 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 | #
10 |
11 | =head1 NAME
12 |
13 | FP::List::t
14 |
15 | =head1 SYNOPSIS
16 |
17 | =head1 DESCRIPTION
18 |
19 | =head1 SEE ALSO
20 |
21 | =head1 NOTE
22 |
23 | This is alpha software! Read the status section in the package README
24 | or on the L.
25 |
26 | =cut
27 |
28 |
29 | package FP::List::t;
30 | use strict;
31 | use utf8;
32 | use warnings;
33 | use warnings FATAL => 'uninitialized';
34 | #use experimental 'signatures';
35 |
36 | use FP::List;
37 | use FP::PureArray;
38 | use Chj::TEST;
39 | use FP::Predicates qw(is_even);
40 |
41 | TEST { list(5, 7, 8, 9, 11, 13, 12, 10)->split(\&is_even) }
42 | list(purearray(5, 7), purearray(9, 11, 13), purearray());
43 | TEST { list(5, 7, 8, 9, 11, 13, 12, 10)->split(\&is_even, 1) }
44 | list(purearray(5, 7, 8), purearray(9, 11, 13, 12), purearray(10));
45 | TEST { list(12, 10, 11)->split(\&is_even) }
46 | list(purearray(), purearray(), purearray(11));
47 | TEST { list(12, 10)->split(\&is_even) }
48 | list(purearray(), purearray());
49 |
50 |
51 | 1
52 |
--------------------------------------------------------------------------------
/lib/FP/Mixin/Utils.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2019 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | FP::Mixin::Utils - utility methods
13 |
14 | =head1 SYNOPSIS
15 |
16 | use base 'FP::Mixin::Utils'; # or use parent or whatever
17 |
18 | =head1 DESCRIPTION
19 |
20 | Methods that can be useful to have on some classes or protocols.
21 |
22 | Currently implemented:
23 |
24 | C: call `F` from `FP::Stream` (useful in the repl to fully force
25 | a data structure)
26 |
27 | =head1 SEE ALSO
28 |
29 | L -- uses this
30 |
31 | =head1 NOTE
32 |
33 | This is alpha software! Read the status section in the package README
34 | or on the L.
35 |
36 | =cut
37 |
38 | package FP::Mixin::Utils;
39 |
40 | use strict;
41 | use warnings;
42 | use warnings FATAL => 'uninitialized';
43 |
44 | sub F {
45 | require FP::Stream;
46 | goto \&FP::Stream::F
47 | }
48 |
49 | 1
50 |
--------------------------------------------------------------------------------
/lib/FP/Repl.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2004-2022 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | FP::Repl - read-eval-print loop
13 |
14 | =head1 SYNOPSIS
15 |
16 | use FP::Repl;
17 | repl;
18 |
19 | # To change the default place for both the history and settings
20 | # files, set this env var to an absolute path to an existing dir:
21 | # $ENV{FP_REPL_HOME}= "/foo/bar";
22 |
23 | # pass parameters (any fields of the FP::Repl::Repl class):
24 | repl (skip => 3, # skip 3 caller frames (when the repl call is nested
25 | # within something you dont't want the user to see)
26 | tty => $fh, # otherwise repl tries to open /dev/tty, or if that fails,
27 | # uses readline defaults (which is somewhat broken?)
28 | # also, any fields of the FP::Repl::Repl class are possible:
29 | maxHistLen => 100, maybe_prompt => "foo>", maybe_package => "Foo::Bar",
30 | maybe_historypath => ".foo_history", pager => ["more"]
31 | # etc.
32 | );
33 |
34 | =head1 DESCRIPTION
35 |
36 | For a simple parameterless start of `FP::Repl::Repl`.
37 |
38 | =head1 SEE ALSO
39 |
40 | L: the class implementing this
41 |
42 | =head1 NOTE
43 |
44 | This is alpha software! Read the status section in the package README
45 | or on the L.
46 |
47 | =cut
48 |
49 | package FP::Repl;
50 | use strict;
51 | use warnings;
52 | use warnings FATAL => 'uninitialized';
53 | use Exporter "import";
54 |
55 | our @EXPORT = qw(repl);
56 | our @EXPORT_OK = qw();
57 | our %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]);
58 |
59 | use FP::Repl::Repl;
60 |
61 | sub repl {
62 | @_ % 2 and die "expecting even number of arguments";
63 | my %args = @_;
64 | my $maybe_skip = delete $args{skip};
65 | my $maybe_tty = delete $args{tty};
66 |
67 | my $r = FP::Repl::Repl->new;
68 |
69 | if (exists $args{maybe_settingspath}) {
70 | $r->set_maybe_settingspath(delete $args{maybe_settingspath});
71 | }
72 |
73 | $r->possibly_restore_settings;
74 |
75 | for (keys %args) {
76 | my $m = "set_$_";
77 | $r->$m($args{$_});
78 | }
79 |
80 | #$r->run ($maybe_skip);
81 | my $m = $r->can("run");
82 | @_ = ($r, $maybe_skip);
83 | goto &$m
84 | }
85 |
86 | *FP::Repl = \&repl;
87 |
88 | 1
89 |
--------------------------------------------------------------------------------
/lib/FP/Repl/AutoTrap.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2015-2020 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | FP::Repl::AutoTrap -- use FP::Repl::Trap on tty, Chj::Backtrace otherwise
13 |
14 | =head1 SYNOPSIS
15 |
16 | use FP::Repl::AutoTrap;
17 |
18 | =head1 DESCRIPTION
19 |
20 | This checks whether stdin and stdout are going to a tty, if so, then
21 | activate FP::Repl::Trap to trap errors in a repl, otherwise just activate
22 | Chj::Backtrace.
23 |
24 | This is activated at load time unless C<$ENV{RUN_TESTS}> is true. It
25 | can be activated expicitly by calling
26 | C.
27 |
28 | =head1 SEE ALSO
29 |
30 | L, L
31 |
32 | =head1 NOTE
33 |
34 | This is alpha software! Read the status section in the package README
35 | or on the L.
36 |
37 | =cut
38 |
39 | package FP::Repl::AutoTrap;
40 |
41 | use strict;
42 | use warnings;
43 | use warnings FATAL => 'uninitialized';
44 |
45 | # Interesting, FP::Repl::Repl::maybe_tty works differently; well makes
46 | # sense. So this is the "non-forcing" way to check:
47 | use POSIX qw(isatty);
48 | use FP::Carp;
49 |
50 | sub possibly_activate {
51 | if (isatty(0) and isatty(1)) {
52 | require FP::Repl::WithRepl;
53 | import FP::Repl::WithRepl;
54 | push_withrepl(0);
55 | 1
56 | } else {
57 | require Chj::Backtrace;
58 | import Chj::Backtrace;
59 | 0
60 | }
61 | }
62 |
63 | if (($ENV{RUN_TESTS} // '') eq '1') {
64 | warn "not activating since running in test mode";
65 | } else {
66 | possibly_activate
67 | }
68 |
69 | 1
70 |
--------------------------------------------------------------------------------
/lib/FP/Repl/Dependencies.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2015 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | FP::Repl::Dependencies - hack to load Repl dependencies
13 |
14 | =head1 SYNOPSIS
15 |
16 | use Chj::TEST use => 'FP::Repl::Dependencies';
17 |
18 | =head1 DESCRIPTION
19 |
20 | Term::ReadLine::Gnu does not allow to check for its presence
21 | directly. When require'ing Term::ReadLine::Gnu, it gives an error
22 | saying "It is invalid to load Term::ReadLine::Gnu directly.". That
23 | makes it appear as unloadable when in fact it is present. And
24 | depending on just Term::ReadLine is not enough, the repl will then
25 | fail at runtime. Stupid.
26 |
27 | So, this.
28 |
29 | =head1 NOTE
30 |
31 | This is alpha software! Read the status section in the package README
32 | or on the L.
33 |
34 | =cut
35 |
36 | package FP::Repl::Dependencies;
37 |
38 | use strict;
39 | use warnings;
40 | use warnings FATAL => 'uninitialized';
41 |
42 | use Term::ReadLine;
43 |
44 | $Term::ReadLine::Gnu::VERSION
45 | or die "dependency Term::ReadLine::Gnu not present";
46 |
47 | # now also depend on PadWalker etc.
48 | require FP::Repl::Repl;
49 |
50 | 1
51 |
--------------------------------------------------------------------------------
/lib/FP/Repl/Trap.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2015 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | FP::Repl::Trap
13 |
14 | =head1 SYNOPSIS
15 |
16 | use FP::Repl::Trap;
17 | die "fun"; # opens a repl from FP::Repl
18 |
19 | =head1 DESCRIPTION
20 |
21 | Dead-simple wrapper around FP::Repl::WithRepl that simply enables trapping
22 | globally.
23 |
24 | NOTE: the name is not set in stone yet, also, perhaps it should be
25 | *merged* with FP::Repl::WithRepl.
26 |
27 | =head1 SEE ALSO
28 |
29 | L, L
30 |
31 | =head1 NOTE
32 |
33 | This is alpha software! Read the status section in the package README
34 | or on the L.
35 |
36 | =cut
37 |
38 | package FP::Repl::Trap;
39 |
40 | use strict;
41 | use warnings;
42 | use warnings FATAL => 'uninitialized';
43 |
44 | use FP::Repl::WithRepl;
45 |
46 | if (($ENV{RUN_TESTS} // '') eq '1') {
47 | warn "not activating since running in test mode";
48 | } else {
49 | push_withrepl(0);
50 | }
51 |
52 | 1
53 |
--------------------------------------------------------------------------------
/lib/FP/Show/t.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2015-2021 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | FP::Show::t
13 |
14 | =head1 SYNOPSIS
15 |
16 | =head1 DESCRIPTION
17 |
18 | Run by test suite
19 |
20 | =head1 NOTE
21 |
22 | This is alpha software! Read the status section in the package README
23 | or on the L.
24 |
25 | =cut
26 |
27 | package FP::Show::t;
28 |
29 | use strict;
30 | use warnings;
31 | use warnings FATAL => 'uninitialized';
32 |
33 | use Chj::TEST;
34 |
35 | use FP::Show; # exports 'show'
36 | use FP::List ":all";
37 | use FP::Stream ":all";
38 | use FP::Ops qw(regex_substitute);
39 |
40 | TEST_EXCEPTION {
41 | my $l = list 100 - 1, "bottles";
42 | die "not what we wanted: " . show($l)
43 | }
44 | "not what we wanted: list(99, 'bottles')";
45 |
46 | TEST { show cons 1, cons 2, 3 }
47 | 'improper_list(1, 2, 3)';
48 |
49 | TEST { show improper_list(list(1, 3), 2) }
50 | 'improper_list(list(1, 3), 2)';
51 |
52 | TEST {
53 | regex_substitute sub {s/line \d+/line .../g},
54 | show improper_list([1, 3], { foo => list("bar", sub {"f"}) })
55 | }
56 | "improper_list([1, 3], +{foo => list('bar', sub { 'DUMMY' })})";
57 |
58 | my $s;
59 | TEST {
60 | $s = stream_iota->take(10);
61 | show $s
62 | }
63 | "lazyT { 'DUMMY' } 'FP::List::List'";
64 |
65 | TEST {
66 | $s->rest->rest;
67 | show $s
68 | }
69 | "improper_list(0, 1, lazyT { 'DUMMY' } 'FP::List::List')";
70 |
71 | TEST {
72 | show * STDERR {IO}
73 | }
74 | "bless(IO(2), 'IO::File')";
75 |
76 | 1
77 |
--------------------------------------------------------------------------------
/lib/FP/Struct/Equal.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2019 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | FP::Struct::Equal -- automatic Equal protocol implementation
13 |
14 | =head1 SYNOPSIS
15 |
16 | package FP_Struct_Equal_Example::Foo {
17 |
18 | use FP::Struct ["a","b"],
19 | 'FP::Struct::Equal';
20 |
21 | _END_
22 | }
23 |
24 | FP_Struct_Equal_Example::Foo::constructors->import;
25 | use FP::Equal;
26 | ok equal(Foo(1, [1+1, 3]), Foo(1, [2, 3]));
27 | ok not equal(Foo(1, 1+1), Foo(1, [3]));
28 |
29 | =head1 DESCRIPTION
30 |
31 | This class, when listed as a superclass of an L,
32 | automatically implements the L protocol
33 | (i.e. generates an `FP_Equal_equal` method that uses inspection
34 | specific to FP::Struct classes to get to know the public field values
35 | of the object it is being called on, and reconstructs a constructor
36 | call based on this information.) This will be the right thing for the
37 | typical `FP::Struct` based class that don't have or mutate hidden
38 | fields or want to exclude some fields from equality tests.
39 |
40 | =head1 SEE ALSO
41 |
42 | Creates implementations for: L
43 |
44 | L
45 |
46 | =head1 NOTE
47 |
48 | This is alpha software! Read the status section in the package README
49 | or on the L.
50 |
51 | =cut
52 |
53 | package FP::Struct::Equal;
54 |
55 | use strict;
56 | use warnings;
57 | use warnings FATAL => 'uninitialized';
58 |
59 | use base 'FP::Abstract::Equal';
60 | use FP::Equal ();
61 |
62 | sub FP_Equal_equal {
63 | my ($self, $b) = @_;
64 | my $class = ref($self);
65 | length $class or die "FP_Show_show called on non-object: $self";
66 | my $fieldnames = do {
67 | no strict 'refs';
68 | \@{"${class}::__Struct__fields"}
69 | };
70 |
71 | # XX is all_fields slow, probably? Optim?
72 | for (FP::Struct::all_fields([$class])) {
73 | my $fieldname = FP::Struct::field_name($_);
74 | FP::Equal::equal($self->$fieldname, $b->$fieldname) or return 0
75 | }
76 | 1
77 | }
78 |
79 | 1
80 |
--------------------------------------------------------------------------------
/lib/FP/Values.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2013-2015 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | FP::Values - utilities to work with Perl's multiple values ("lists")
13 |
14 | =head1 SYNOPSIS
15 |
16 | =head1 DESCRIPTION
17 |
18 |
19 | =head1 NOTE
20 |
21 | This is alpha software! Read the status section in the package README
22 | or on the L.
23 |
24 | =cut
25 |
26 | package FP::Values;
27 | use strict;
28 | use warnings;
29 | use warnings FATAL => 'uninitialized';
30 | use Exporter "import";
31 |
32 | our @EXPORT = qw();
33 | our @EXPORT_OK = qw(fst snd);
34 | our %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]);
35 |
36 | sub fst {
37 | $_[0]
38 | }
39 |
40 | sub snd {
41 | $_[1]
42 | }
43 |
44 | 1
45 |
--------------------------------------------------------------------------------
/lib/FP/Weak/t.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2015 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | FP::Weak::t - tests for FP::Weak
13 |
14 | =head1 SYNOPSIS
15 |
16 | # just let it sit there and be tested by `t/require_and_run_tests`
17 |
18 | =head1 DESCRIPTION
19 |
20 |
21 | =head1 NOTE
22 |
23 | This is alpha software! Read the status section in the package README
24 | or on the L.
25 |
26 | =cut
27 |
28 | package FP::Weak::t;
29 |
30 | use strict;
31 | use warnings;
32 | use warnings FATAL => 'uninitialized';
33 |
34 | use FP::Weak ":all";
35 | use Chj::TEST;
36 |
37 | sub t {
38 | my $foo = [];
39 | weaken $foo;
40 | $foo
41 | }
42 |
43 | TEST { my $foo = []; noweaken $foo; $foo }
44 | [];
45 | TEST {t}
46 | undef;
47 | TEST {
48 | with_noweaken {t}
49 | }
50 | [];
51 | TEST { &with_noweaken(\&t) }
52 | [];
53 | TEST {t}
54 | undef;
55 | TEST {
56 | my @w;
57 | local $SIG{__WARN__} = sub {
58 | my ($msg) = @_;
59 | $msg =~ s/0x[0-9a-f]*/0x.../s;
60 | $msg =~ s/ at .*/ .../s;
61 | push @w, $msg
62 | };
63 | [&with_warnweaken(\&t), @w]
64 | }
65 | [undef, "weaken (ARRAY(0x...)) ..."];
66 |
67 | 1
68 |
--------------------------------------------------------------------------------
/lib/FP/autobox.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2019-2021 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | FP::autobox
13 |
14 | =head1 SYNOPSIS
15 |
16 | use FP::autobox;
17 |
18 | is [12,4,2]->product, 96;
19 |
20 | my $arr = [55,10];
21 | is ref($arr), "ARRAY";
22 |
23 | use FP::Div ":all"; use FP::Ops ":all";
24 | is $arr->map(\&inc)->reduce(\&add), 67;
25 |
26 | # Calling methods on arrays 'consumes' the arrays, i.e. makes them
27 | # blessed and immutable (TODO: offer a FP::autobox::mutable or
28 | # so?):
29 |
30 | is ref($arr), 'FP::_::PureArray';
31 |
32 | is( (eval { $arr->[0]++; 1 } || regex_substitute(sub{s/\s*at .*//s}, $@)),
33 | 'Modification of a read-only value attempted' );
34 |
35 | is $$arr[0], 55;
36 |
37 | =head1 DESCRIPTION
38 |
39 | Wouldn't it be cool if an ordinary Perl array could be used as an
40 | L? Autobox delivers on that.
41 |
42 | This uses L underneath. This module does bless the arrays, so
43 | that method calls other than in the lexical scope of the autobox
44 | import work, too.
45 |
46 | =head1 TODO
47 |
48 | This could be extended to support methods on strings, once a
49 | L module has been implemented, and on hashes for a to-be
50 | implemented L protocol.
51 |
52 | =head1 SEE ALSO
53 |
54 | L, which is transparently loaded to provide the
55 | methods.
56 |
57 | =head1 NOTE
58 |
59 | This is alpha software! Read the status section in the package README
60 | or on the L.
61 |
62 | =cut
63 |
64 | package FP::autobox;
65 |
66 | use strict;
67 | use warnings;
68 | use warnings FATAL => 'uninitialized';
69 |
70 | require FP::PureArray;
71 | require autobox;
72 |
73 | our @ISA = "autobox";
74 |
75 | sub import {
76 | my ($pack, @args) = @_;
77 | my %args = @args;
78 | if (not $args{ARRAY}) {
79 | push @_, ARRAY => 'FP::PureArray::autobox';
80 | }
81 | if (not $args{HASH}) {
82 | push @_, HASH => 'FP::PureHash::autobox';
83 | }
84 | my $m = $pack->can("SUPER::import") or die "bug";
85 | goto $m
86 | }
87 |
88 | 1
89 |
--------------------------------------------------------------------------------
/lib/FP/url_.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2014-2021 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | FP::url_
13 |
14 | =head1 SYNOPSIS
15 |
16 | use FP::url_;
17 | my $u = url_ path => "index.html", fragment => "foo#bar";
18 | # $u is an URI object
19 | is "$u", 'index.html#foo%23bar';
20 |
21 | =head1 DESCRIPTION
22 |
23 |
24 | =head1 NOTE
25 |
26 | This is alpha software! Read the status section in the package README
27 | or on the L.
28 |
29 | =cut
30 |
31 | package FP::url_;
32 | use strict;
33 | use warnings;
34 | use warnings FATAL => 'uninitialized';
35 | use Exporter "import";
36 |
37 | our @EXPORT = qw(url_);
38 | our @EXPORT_OK = qw();
39 | our %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]);
40 |
41 | use URI;
42 |
43 | our @keys = qw(scheme path fragment);
44 |
45 | our %keys = map { $_ => $_ } @keys;
46 |
47 | sub url_ {
48 | my $u = URI->new;
49 | while (@_) {
50 | my $k = shift;
51 | @_ or die "url_: uneven number of arguments";
52 | my $v = shift;
53 | my $m = $keys{$k} // die "url_: unknown key '$k'";
54 | $u->$m($v);
55 | }
56 | $u
57 | }
58 |
59 | 1
60 |
--------------------------------------------------------------------------------
/lib/PXML.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2015-2020 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | PXML - functional XML handling, general functions
13 |
14 | =head1 SYNOPSIS
15 |
16 | use PXML qw(is_pxml_element);
17 | use PXML::XHTML qw(P);
18 |
19 | ok is_pxml_element P();
20 | is P("Hi ")->string, 'Hi <there>
';
21 |
22 | use PXML ":all";
23 |
24 | is(pxmlbody("foo")->string, "foo");
25 |
26 |
27 | =head1 DESCRIPTION
28 |
29 | General Functions for the PXML libraries.
30 |
31 | =head1 SEE ALSO
32 |
33 | L, L, L, L,
34 | L, L, L, L,
35 | L
36 |
37 | =head1 NOTE
38 |
39 | This is alpha software! Read the status section in the package README
40 | or on the L.
41 |
42 | =cut
43 |
44 | package PXML;
45 | use strict;
46 | use warnings;
47 | use warnings FATAL => 'uninitialized';
48 | use Exporter "import";
49 |
50 | our @EXPORT = qw(is_pxml_element);
51 | our @EXPORT_OK = qw(pxmlbody pxmlflush is_pxmlflush);
52 | our %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]);
53 |
54 | use PXML::Element;
55 |
56 | use FP::Predicates 'instance_of';
57 | use Scalar::Util qw(blessed);
58 |
59 | sub is_pxml_element;
60 | *is_pxml_element = instance_of("PXML::Element");
61 |
62 | {
63 |
64 | package PXML::Body;
65 |
66 | # hacky?.
67 | *string = \&PXML::Element::string;
68 | }
69 |
70 | sub pxmlbody {
71 | bless [@_], "PXML::Body"
72 | }
73 |
74 | my $flush = bless [], "PXML::Flush";
75 |
76 | sub pxmlflush {
77 | $flush
78 | }
79 |
80 | sub is_pxmlflush {
81 | my ($v) = @_;
82 | blessed($v) // return;
83 | $v->isa("PXML::Flush")
84 | }
85 |
86 | # XX make this cleaner:
87 | # - make PXML::Body and PXML::Element inherit both from a base class
88 | # - move `string` there (and perhaps all of serialization)
89 | # - automatically use PXML::Body for bodies? (now that I moved away
90 | # from requiring bodies to be arrays, though?)
91 |
92 | 1
93 |
--------------------------------------------------------------------------------
/lib/PXML/HTML5.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2013-2015 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | PXML::HTML5
13 |
14 | =head1 SYNOPSIS
15 |
16 | =head1 DESCRIPTION
17 |
18 | currently just provides $html5_void_elements and $html5_void_element_h
19 |
20 | =head1 NOTE
21 |
22 | This is alpha software! Read the status section in the package README
23 | or on the L.
24 |
25 | =cut
26 |
27 | package PXML::HTML5;
28 | use strict;
29 | use warnings;
30 | use warnings FATAL => 'uninitialized';
31 | use Exporter "import";
32 |
33 | our @EXPORT = qw();
34 | our @EXPORT_OK = qw( $html5_void_elements $html5_void_element_h);
35 | our %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]);
36 |
37 | # the set of tags that are allowed to be self-closing () without
38 | # semantical changes
39 | our $html5_void_elements = [
40 |
41 | # http://dev.w3.org/html5/markup/syntax.html
42 | # "The following is a complete list of the void elements in HTML:"
43 | qw(
44 | area
45 | base
46 | br
47 | col
48 | command
49 | embed
50 | hr
51 | img
52 | input
53 | keygen
54 | link
55 | meta
56 | param
57 | source
58 | track
59 | wbr
60 | )
61 | ];
62 |
63 | our $html5_void_element_h = +{ map { $_ => 1 } @$html5_void_elements };
64 |
65 | 1
66 |
--------------------------------------------------------------------------------
/lib/PXML/Preserialize/t.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2015 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | PXML::Preserialize::t -- tests for PXML::Preserialize
13 |
14 | =head1 SYNOPSIS
15 |
16 | =head1 DESCRIPTION
17 |
18 | # is tested by `t/require_and_run_tests`
19 |
20 | =head1 NOTE
21 |
22 | This is alpha software! Read the status section in the package README
23 | or on the L.
24 |
25 | =cut
26 |
27 | package PXML::Preserialize::t;
28 |
29 | use strict;
30 | use warnings;
31 | use warnings FATAL => 'uninitialized';
32 |
33 | use Chj::TEST;
34 | use PXML qw(pxmlbody);
35 |
36 | use PXML::Preserialize qw(pxmlfunc pxmlpre);
37 | use PXML::XHTML qw(A B);
38 |
39 | my $link_normal = sub {
40 | my ($href, $body) = @_;
41 | A { href => $href }, $body
42 | };
43 |
44 | my $link_fast = pxmlfunc {
45 | my ($href, $body) = @_; # can take up to 10[?] arguments.
46 | A { href => $href }, $body
47 | };
48 |
49 | # the `2` is the number of arguments
50 | my $link_fast2 = pxmlpre 2, $link_normal;
51 |
52 | # these expressions are all returing the same result, but the first
53 | # is slower then the others:
54 | my $res = 'FooBar';
55 | TEST { &$link_normal("http://foo", [B("Foo"), "Bar"])->string } $res;
56 |
57 | TEST { &$link_fast("http://foo", [B("Foo"), "Bar"])->string } $res;
58 | TEST { &$link_fast2("http://foo", [B("Foo"), "Bar"])->string } $res;
59 |
60 | TEST {
61 | pxmlfunc {1}->()->string
62 | }
63 | '1';
64 | TEST {
65 | pxmlfunc { [1, 2] }->()->string
66 | }
67 | '12';
68 | TEST {
69 | pxmlfunc { pxmlbody 3, 2 }->()->string
70 | }
71 | '32';
72 |
73 | TEST_EXCEPTION {
74 | pxmlfunc {
75 | my ($loc, $body) = @_;
76 | A { href => "http://$loc" }, $body
77 |
78 | # yes, already *that* is forbidden.
79 | }
80 | }
81 | "tried to access a PXML::Preserialize::Argument object";
82 |
83 | TEST_EXCEPTION {
84 | pxmlfunc {
85 | my ($loc, $body) = @_;
86 | A { href => $loc }, 0 - $body
87 | }
88 | }
89 | "tried to access a PXML::Preserialize::Argument object";
90 |
91 | TEST_EXCEPTION {
92 | pxmlfunc {
93 | my ($loc, $body) = @_;
94 | A { href => $loc }, $loc ? $body : 1
95 | }
96 | }
97 | "tried to access a PXML::Preserialize::Argument object";
98 |
99 | 1
100 |
--------------------------------------------------------------------------------
/lib/PXML/SVG.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2014-2015 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | PXML::SVG
13 |
14 | =head1 SYNOPSIS
15 |
16 | =head1 DESCRIPTION
17 |
18 |
19 | =head1 NOTE
20 |
21 | This is alpha software! Read the status section in the package README
22 | or on the L.
23 |
24 | =cut
25 |
26 | package PXML::SVG;
27 | use strict;
28 | use warnings;
29 | use warnings FATAL => 'uninitialized';
30 | use Exporter "import";
31 |
32 | use PXML::Element;
33 |
34 | our $tags = [
35 | 'svg', 'path', 'a',
36 |
37 | # XXX unfinished! Many more of course.
38 | ];
39 |
40 | sub svg {
41 | my $attrs = ref $_[0] eq "HASH" ? shift : {};
42 | my $attrs2 = +{%$attrs};
43 | $$attrs2{xmlns} = "http://www.w3.org/2000/svg";
44 | $$attrs2{"xmlns:xlink"} = "http://www.w3.org/1999/xlink";
45 | PXML::SVG::SVG($attrs2, @_)
46 | }
47 |
48 | # XX mostly copy paste from PXHTML. Abstract away, please.
49 |
50 | our $nbsp = "\xa0";
51 |
52 | our $funcs = [
53 | map {
54 | my $tag = $_;
55 | [
56 | uc $tag,
57 | sub {
58 | my $atts = ref($_[0]) eq "HASH" ? shift : undef;
59 | PXML::PSVG->new($tag, $atts, [@_]);
60 | }
61 | ]
62 | } @$tags
63 | ];
64 |
65 | for (@$funcs) {
66 | my ($name, $fn) = @$_;
67 | no strict 'refs';
68 | *{"PXML::SVG::$name"} = $fn
69 | }
70 |
71 | our @EXPORT_OK = ('svg', '$nbsp', map { $$_[0] } @$funcs);
72 | our %EXPORT_TAGS = (all => \@EXPORT_OK);
73 |
74 | {
75 |
76 | package PXML::PSVG;
77 | our @ISA = "PXML::Element";
78 |
79 | # serialize to HTML5 compatible representation: -- nope, not
80 | # necessary for SVG, ok? Assuming XHTML always? And different tags
81 | # anyway, ok?
82 | }
83 |
84 | 1
85 |
--------------------------------------------------------------------------------
/lib/PXML/Serialize/t.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2013-2015 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | PXML::Serialize::t -- tests for PXML::Serialize
13 |
14 | =head1 SYNOPSIS
15 |
16 | =head1 DESCRIPTION
17 |
18 | # is tested by `t/require_and_run_tests`
19 |
20 | =head1 NOTE
21 |
22 | This is alpha software! Read the status section in the package README
23 | or on the L.
24 |
25 | =cut
26 |
27 | package PXML::Serialize::t;
28 |
29 | use strict;
30 | use warnings;
31 | use warnings FATAL => 'uninitialized';
32 |
33 | use Chj::TEST;
34 | use PXML::Serialize qw(pxml_print_fragment_fast);
35 | use PXML::XHTML ":all";
36 | use PXML qw(pxmlflush);
37 |
38 | TEST_STDOUT { pxml_print_fragment_fast ["abc", P(2)], *STDOUT }
39 | 'abc2
';
40 | TEST_STDOUT { pxml_print_fragment_fast ["abc"], *STDOUT }
41 | 'abc';
42 |
43 | TEST_STDOUT {
44 | pxml_print_fragment_fast P({ foo => ["a", ["b", pxmlflush, "c"], "d"] },
45 | "abc"), *STDOUT{IO}
46 | }
47 | 'abc
';
48 |
49 | 1
50 |
--------------------------------------------------------------------------------
/lib/PXML/Tags.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2013-2019 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | PXML::Tags
13 |
14 | =head1 SYNOPSIS
15 |
16 | use PXML::Tags qw(records
17 | protocol-version
18 | record);
19 | my $xml = RECORDS(PROTOCOL_VERSION("1.0"),
20 | RECORD("Hi"), RECORD("there!"));
21 | is ref($xml), "PXML::Element";
22 | is $xml->string, '1.0Hithere!';
23 |
24 | =head1 DESCRIPTION
25 |
26 | Creates tag wrappers that return PXML elements. The names of the
27 | wrappers are all uppercase, and "-" is replaced with "_".
28 |
29 | =head1 NOTE
30 |
31 | This is alpha software! Read the status section in the package README
32 | or on the L.
33 |
34 | =cut
35 |
36 | package PXML::Tags;
37 |
38 | use strict;
39 | use warnings;
40 | use warnings FATAL => 'uninitialized';
41 |
42 | use PXML::Element;
43 |
44 | sub import {
45 | my $caller = caller;
46 | for my $name (@_) {
47 | my $fname = uc $name;
48 | $fname =~ s/-/_/sg;
49 | my $fqname = "${caller}::$fname";
50 | no strict 'refs';
51 | *$fqname = sub {
52 | my $atts = ref($_[0]) eq "HASH" ? shift : undef;
53 | PXML::Element->new($name, $atts, [@_]);
54 | };
55 | }
56 | 1
57 | }
58 |
59 | 1
60 |
--------------------------------------------------------------------------------
/meta/FunctionalPerl/ModuleList.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2015-2021 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | FunctionalPerl::ModuleList
13 |
14 | =head1 SYNOPSIS
15 |
16 | =head1 DESCRIPTION
17 |
18 |
19 | =head1 NOTE
20 |
21 | This is alpha software! Read the status section in the package README
22 | or on the L.
23 |
24 | =cut
25 |
26 | package FunctionalPerl::ModuleList;
27 | use strict;
28 | use warnings;
29 | use warnings FATAL => 'uninitialized';
30 | use Exporter "import";
31 |
32 | our @EXPORT = qw(modulenamelist modulepathlist);
33 | our @EXPORT_OK = qw();
34 | our %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]);
35 |
36 | use Chj::xopen 'xopen_read';
37 |
38 | our $modulenameandpathlist; # [[ name, path ] ...]
39 |
40 | sub modulenameandpathlist {
41 | $modulenameandpathlist //= do {
42 | my $f = xopen_read "MANIFEST";
43 | my @m;
44 | local $_;
45 | while (<$f>) { ## no critic, $_ is localized
46 | chomp;
47 | my $path = $_;
48 | next unless s/\.pm$//;
49 | s|^(lib\|meta\|htmlgen)/|| or die "no match: $_";
50 | s|/|::|sg;
51 | push @m, [$_, $path]
52 | }
53 | $f->xclose;
54 | \@m
55 | }
56 | }
57 |
58 | sub modulenamelist {
59 | [map { $$_[0] } @{ modulenameandpathlist() }]
60 | }
61 |
62 | sub modulepathlist {
63 | [map { $$_[1] } @{ modulenameandpathlist() }]
64 | }
65 |
66 | 1
67 |
--------------------------------------------------------------------------------
/meta/FunctionalPerl/TailExpand.pm:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2019 Christian Jaeger, copying@christianjaeger.ch
3 | #
4 | # This is free software, offered under either the same terms as perl 5
5 | # or the terms of the Artistic License version 2 or the terms of the
6 | # MIT License (Expat version). See the file COPYING.md that came
7 | # bundled with this file.
8 | #
9 |
10 | =head1 NAME
11 |
12 | FunctionalPerl::TailExpand
13 |
14 | =head1 SYNOPSIS
15 |
16 | =for test ignore
17 |
18 | use lib "./meta";
19 | use FunctionalPerl::TailExpand;
20 | use FunctionalPerl::Htmlgen::Nav; # or whatever other modules use tail
21 |
22 | =head1 DESCRIPTION
23 |
24 | Avoid dependency on L by running C
25 | on all modules (that can contain C calls) first.
26 |
27 | Automatically runs C