├── .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 | '' 43 | . '
1234
43.3foobar
'; 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 | 'abc

2

'; 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 etc. to have subsequent 28 | module loads happen from the expanded files. 29 | 30 | Can only be run with the current working directory being the root of 31 | the source repository, i.e. during testing (or build). 32 | 33 | =cut 34 | 35 | package FunctionalPerl::TailExpand; 36 | 37 | use strict; 38 | use warnings; 39 | use warnings FATAL => 'uninitialized'; 40 | 41 | use lib "./lib"; 42 | use Chj::xperlfunc ":all"; 43 | 44 | xxsystem_safe $^X, "meta/tail-expand"; 45 | 46 | use lib "./.htmlgen"; 47 | 48 | # no need; skipped in meta/tail-expand 49 | # use lib "./.lib"; 50 | # use lib "./.meta"; 51 | 52 | # normal load paths, to be transparent re what should be loaded 53 | # use lib "./meta"; no need as that had to be done already to reach us 54 | 55 | 1 56 | -------------------------------------------------------------------------------- /meta/bin/gpg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | my $copyright = <<'COPYRIGHT'; 4 | # Copyright 2021 by Christian Jaeger 5 | # Published under the same terms as perl itself 6 | COPYRIGHT 7 | 8 | use strict; 9 | use utf8; 10 | use warnings; 11 | use warnings FATAL => 'uninitialized'; 12 | use experimental 'signatures'; 13 | 14 | my ($email_full) = $copyright =~ / by ([^\n]*)/s; 15 | 16 | my ($mydir, $myname); 17 | 18 | BEGIN { 19 | $0 =~ /(.*?)([^\/]+)\z/s or die "?"; 20 | ($mydir, $myname) = ($1, $2); 21 | } 22 | 23 | use lib "$mydir/../../lib"; 24 | use FP::Equal; 25 | use Chj::xperlfunc qw(xexec xprint xprintln); # xgetfile_utf8 26 | use Chj::xIOUtil qw(xputfile_bytes); # XX move to ^ ? 27 | use FP::Repl; 28 | use Chj::singlequote qw(singlequote_many); 29 | use POSIX 'getcwd'; 30 | 31 | my $orig_gpg = "/usr/bin/gpg"; 32 | 33 | my $siginput_path = "$mydir/../../SIGNATURE-input"; 34 | my $sigoutput_path = getcwd . "/SIGNATURE.tmp"; 35 | 36 | if ( equal(\@ARGV, ["--version"]) 37 | or equal(\@ARGV, ['--batch', '--verify', 'SIGNATURE'])) 38 | { 39 | xexec $orig_gpg, @ARGV; 40 | } elsif (@ARGV and $ARGV[0] eq "--clearsign") { 41 | my $in = do { 42 | local $/; 43 | 44 | }; 45 | close STDIN or die "stdin: $!"; 46 | xputfile_bytes $siginput_path, $in; 47 | open STDOUT, ">", "/dev/tty" or die $!; 48 | xprintln "our call was:" . singlequote_many(@ARGV); 49 | xprintln "please run:"; 50 | xprintln " gpg @ARGV < $siginput_path > $sigoutput_path"; 51 | xprintln "then ctl-d here."; 52 | repl; 53 | unlink $siginput_path; 54 | 55 | # xprint xgetfile_utf8 $sigoutput_path; 56 | # unlink $sigoutput_path; 57 | # ^ not working somehow, huh. Thus use the output path that 58 | # Module::Signature uses and write to it directly above. 59 | } else { 60 | open STDERR, ">", "/dev/tty" or die $!; 61 | open STDOUT, ">", "/dev/tty" or die $!; 62 | open STDIN, "<", "/dev/tty" or die $!; 63 | 64 | xprintln "unknown call to $0, please examine..."; 65 | repl; 66 | } 67 | 68 | -------------------------------------------------------------------------------- /meta/bisect-modules: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | IFS= 5 | 6 | usage() { 7 | echo "$0 bad good modulename..." 8 | echo " Run git bisect, with bad as the bad commit (e.g. HEAD)," 9 | echo " good as the good commit, and running meta/test-modules" 10 | echo " with the given modulenames as the test." 11 | echo " Run \`git bisect reset\` as usual when you're done." 12 | exit 1 13 | } 14 | 15 | if [ $# -lt 3 ]; then 16 | usage 17 | fi 18 | 19 | bad="$1" 20 | good="$2" 21 | shift 22 | shift 23 | 24 | set -x 25 | 26 | git bisect start 27 | git bisect bad "$bad" 28 | git bisect good "$good" 29 | git bisect run meta/test-modules "$@" 30 | 31 | -------------------------------------------------------------------------------- /meta/check-hardcoded-perl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings FATAL => 'uninitialized'; 5 | 6 | if ( 7 | my @files = grep { 8 | chomp; 9 | $_ ne "meta/check-hardcoded-perl" 10 | } `git grep --cached -l /usr/bin/perl` 11 | ) 12 | { 13 | die "files containing hard-coded perl path: @files"; 14 | } 15 | 16 | -------------------------------------------------------------------------------- /meta/copyrightyearcheck: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | my $copyright = <<'COPYRIGHT'; 4 | # Copyright 2021 by Christian Jaeger 5 | # Published under the same terms as perl itself 6 | COPYRIGHT 7 | 8 | use strict; 9 | use warnings; 10 | use warnings FATAL => 'uninitialized'; 11 | use experimental 'signatures'; 12 | 13 | my ($email_full) = $copyright =~ / by ([^\n]*)/s; 14 | 15 | my ($mydir, $myname); 16 | 17 | BEGIN { 18 | $0 =~ /(.*?)([^\/]+)\z/s or die "?"; 19 | ($mydir, $myname) = ($1, $2); 20 | } 21 | 22 | sub usage { 23 | print STDERR map {"$_\n"} @_ if @_; 24 | print "$myname 25 | 26 | Print list of paths from checked-in files that have a Copyright 27 | statement with the last year number older than the year of the 28 | author time of the last commit with that file. 29 | 30 | ($email_full) 31 | "; 32 | exit(@_ ? 1 : 0); 33 | } 34 | 35 | use Getopt::Long; 36 | our $verbose = 0; 37 | 38 | #our $opt_dry; 39 | GetOptions( 40 | "verbose" => \$verbose, 41 | "help" => sub {usage}, 42 | 43 | #"dry-run"=> \$opt_dry, 44 | ) or exit 1; 45 | usage if @ARGV; 46 | 47 | use lib "/opt/functional-perl/lib"; # 48 | use Chj::xperlfunc qw(xgetfile_utf8 xprintln); 49 | use Chj::IO::Command; 50 | 51 | sub git_last_mod_year ($path) { 52 | local $/ = "\n"; 53 | my $in = Chj::IO::Command->new_sender( 54 | qw(git log --no-merges --pretty=format:%ai --), $path); 55 | my $dateline = <$in>; 56 | $in->xfinish; 57 | $dateline =~ /^(\d{4})-/ or die "no match: '$dateline'"; 58 | $1 59 | } 60 | 61 | my $in = Chj::IO::Command->new_sender(qw(git grep -l -z Copyright)); 62 | 63 | local $/ = "\0"; 64 | 65 | for my $path (<$in>) { 66 | chomp $path; 67 | if (xgetfile_utf8($path) =~ m/Copyright.*\b(\d{4})\b/m) { 68 | my $year_stated = $1; 69 | my $year_should = git_last_mod_year($path); 70 | if ($year_stated < $year_should) { 71 | xprintln $path; 72 | } 73 | } 74 | } 75 | 76 | #use FP::Repl; repl; 77 | #use Chj::ruse; 78 | #use Chj::Backtrace; 79 | 80 | -------------------------------------------------------------------------------- /meta/find-perl.pl: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use warnings FATAL => 'uninitialized'; 4 | 5 | require "./meta/test.pl"; 6 | 7 | my $path = $^X; 8 | 9 | if ($path =~ s{[\\/]perl[^\\/]*\z}{}s) { 10 | $ENV{PATH} = "$path:$ENV{PATH}"; 11 | } else { 12 | warn "no match for perl in '$path'"; 13 | } 14 | 15 | -------------------------------------------------------------------------------- /meta/install-development-dependencies-on-debian: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | IFS= 5 | 6 | set -x 7 | 8 | apt-get install libtest-requires-perl libterm-readline-gnu-perl libpadwalker-perl libcapture-tiny-perl libbsd-resource-perl libmethod-signatures-perl libtext-csv-perl libdbd-csv-perl libtext-csv-perl liburi-perl libtext-markdown-perl libclone-perl 9 | 10 | did=0 11 | function needbuild { 12 | if [ $did = 0 ]; then 13 | apt-get install libmodule-build-perl libmoosex-semiaffordanceaccessor-perl 14 | did=1 15 | fi 16 | } 17 | 18 | 19 | # These don't exist: 20 | # libeval-withlexicals-perl 21 | # libsub-util-perl 22 | # libtest-pod-snippets-perl 23 | # libsub-call-tail-perl 24 | 25 | for module in Eval::WithLexicals Sub::Util Test::Pod::Snippets; do 26 | if ! perl -w -M"$module" -e ''; then 27 | needbuild 28 | perl -w -MCPAN -e "install $module" 29 | fi 30 | done 31 | 32 | -------------------------------------------------------------------------------- /meta/perlfiles: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Uses `printfield`, `gls`, `filter`, `is-perl`, and `skiplines` from 4 | # chj-bin. 5 | 6 | # The resulting file contains all Perl files in the Git repo, even 7 | # files which are not in MANIFEST! 8 | 9 | set -euo pipefail 10 | IFS= 11 | 12 | perlfilespath=.perlfiles 13 | 14 | newsum=$(md5sum MANIFEST | printfield 1) 15 | 16 | oldsum=$(head -1 -- "$perlfilespath" || echo '') 17 | 18 | if [ "$newsum" = "$oldsum" ]; then 19 | # echo "unchanged $perlfilespath" >&2 20 | true 21 | else 22 | tmp=$(mktemp -p "$(dirname -- "$perlfilespath")") 23 | { 24 | echo $newsum 25 | gls | filter is-perl 26 | } > "$tmp" 27 | mv -- "$tmp" "$perlfilespath" 28 | #echo "updated $perlfilespath" >&2 29 | fi 30 | 31 | skiplines 1 < "$perlfilespath" 32 | -------------------------------------------------------------------------------- /meta/readin.pl: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use warnings FATAL => 'uninitialized'; 4 | 5 | use Test::More ( 6 | $^O =~ /win32/i ? (skip_all => "on Windows (no shell support)") : ()); 7 | 8 | require "./meta/test.pl"; 9 | 10 | our $len; 11 | 12 | sub readin { 13 | my ($what, $maybe_on_error) = @_; 14 | my $default_on_error = sub { 15 | warn "$what: $! exit value: $?"; 16 | undef 17 | }; 18 | my $on_error = $maybe_on_error // $default_on_error; 19 | open my $in, $what or die "$what: $!"; 20 | my $rv = read $in, my ($buf), $len // 999999; 21 | defined $rv or die $!; 22 | if (defined $len) { 23 | $rv == $len or die "only got $rv bytes instead of $len"; 24 | } 25 | close $in ? $buf : &$on_error($buf, $default_on_error, $!, $?) 26 | } 27 | 28 | 1 29 | -------------------------------------------------------------------------------- /meta/t-check: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | my $copyright = <<'COPYRIGHT'; 4 | # Copyright 2021 by Christian Jaeger 5 | # Published under the same terms as perl itself 6 | COPYRIGHT 7 | 8 | use strict; 9 | use utf8; 10 | use warnings; 11 | use warnings FATAL => 'uninitialized'; 12 | use experimental 'signatures'; 13 | 14 | use Getopt::Long; 15 | use Chj::xperlfunc qw(xslurp); 16 | 17 | my ($email_full) = $copyright =~ / by ([^\n]*)/s; 18 | 19 | my ($mydir, $myname); 20 | 21 | BEGIN { 22 | $0 =~ /(.*?)([^\/]+)\z/s or die "?"; 23 | ($mydir, $myname) = ($1, $2); 24 | } 25 | 26 | my $pathpattern = "t/*.t"; 27 | 28 | sub usage { 29 | print STDERR map {"$_\n"} @_ if @_; 30 | print "$myname [ files ] 31 | 32 | Check the files or $pathpattern for some issues, like not calling the right 33 | Perl. 34 | 35 | ($email_full) 36 | "; 37 | exit(@_ ? 1 : 0); 38 | } 39 | 40 | our $verbose = 0; 41 | 42 | #our $opt_dry; 43 | GetOptions( 44 | "verbose" => \$verbose, 45 | "help" => sub {usage}, 46 | 47 | #"dry-run"=> \$opt_dry, 48 | ) or exit 1; 49 | 50 | my $issues = 0; 51 | 52 | sub issue ($path, $line, $msg) { 53 | $issues++; 54 | warn "$myname: $msg in $path line $line\n"; 55 | } 56 | 57 | sub match_line { scalar split /\n/, substr $_[0], 0, pos($_[0]) } 58 | 59 | # use FP::Repl::Trap; 60 | 61 | sub tcheck ($path) { 62 | my $str = xslurp $path; 63 | while ($str =~ /xsystem\b/g) { 64 | issue $path, match_line($str), 65 | "uses x?xsystem instead of _safe variant"; 66 | } 67 | while ($str =~ /system_safe\b/g) { 68 | my $rest = substr $str, pos($str); 69 | my $before = substr $str, 0, pos($str) - length("system_safe"); 70 | my @lbefore = split /\n/, $before; 71 | my $l = $lbefore[-1]; 72 | next if $l =~ /use Chj::xperlfunc/; 73 | 74 | $rest =~ s/\s+//; 75 | $rest =~ s/\(//; 76 | if (my ($cmd) = $rest =~ /^qw\(([^()]+)\)/s) { 77 | next if $cmd =~ /^(git|diff)/; 78 | } 79 | $rest =~ s/\s+//; 80 | next if $rest =~ /^\$\^X\b/; 81 | next if $rest =~ /^\@cmd/; # not Perl, right? 82 | issue $path, scalar @lbefore, 'must use $^X to call Perl scripts'; 83 | 84 | # use FP::Repl; repl; 85 | } 86 | } 87 | 88 | if (@ARGV) { 89 | tcheck $_ for @ARGV; 90 | } else { 91 | tcheck $_ for glob $pathpattern; 92 | } 93 | 94 | exit($issues ? 1 : 0); 95 | -------------------------------------------------------------------------------- /meta/tail-expand: -------------------------------------------------------------------------------- 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 | # Expand some .pm files to avoid dependency on Sub::Call::Tail: 8 | 9 | # (XX could also include intro/more_tailcalls here and call this 10 | # instead from t/trampoline-fix, but so what) 11 | 12 | use strict; 13 | use warnings; 14 | use warnings FATAL => 'uninitialized'; 15 | 16 | use lib "./lib"; 17 | use Chj::xperlfunc qw(dirname xxsystem_safe xLmtime XLmtime xmkdir_p); 18 | 19 | my $windows = 1; #($^O =~ /win32/i); 20 | 21 | sub executable { 22 | my ($path) = @_; 23 | if ($windows) { 24 | 25 | # -x apparently doesn't work; thus: 26 | open my $in, "<", $path or die "open '$path': $!"; 27 | my $line = <$in>; 28 | close $in; 29 | $line =~ /^#!/ 30 | } else { 31 | -x $path 32 | } 33 | } 34 | 35 | open my $manif, "<", "MANIFEST" or die "$!"; 36 | 37 | local $_; 38 | while (<$manif>) { 39 | chomp; 40 | next unless m{^htmlgen/}; # if m{^(?:lib|meta)/}; 41 | next unless m|\.pm$| or executable $_; 42 | my $from = $_; 43 | my $to = "." . $from; 44 | 45 | my $t_from = xLmtime $from; 46 | my $expand = sub { 47 | xxsystem_safe($^X, "bin/expand-tail", $from, $to); 48 | }; 49 | if (my $t_to = XLmtime $to) { 50 | if ($t_from >= $t_to) { 51 | &$expand; 52 | } 53 | } else { 54 | xmkdir_p dirname $to; 55 | &$expand; 56 | } 57 | } 58 | 59 | close $manif or die $!; 60 | 61 | -------------------------------------------------------------------------------- /meta/test-modules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings FATAL => 'uninitialized'; 5 | 6 | use Cwd 'abs_path'; 7 | 8 | my ($mydir, $myname); 9 | 10 | BEGIN { 11 | my $location = (-l $0) ? abs_path($0) : $0; 12 | $location =~ /(.*?)([^\/]+)\z/s or die "?"; 13 | ($mydir, $myname) = ($1, $2); 14 | } 15 | 16 | use lib "$mydir/../lib"; 17 | 18 | use Getopt::Long; 19 | use Chj::singlequote 'singlequote'; 20 | 21 | sub usage { 22 | print STDERR map {"$_\n"} @_ if @_; 23 | print "$myname modulename... 24 | 25 | Loads the given modules, then runs the test suite on those. Exits 26 | with 0 for success, 1 for any kind of failure. 27 | 28 | Options: 29 | -x turns on tracing using -d:Trace 30 | -d ... or -d=... passed to Perl as -d:... 31 | 32 | "; 33 | exit(@_ ? 1 : 0); 34 | } 35 | 36 | our $verbose = 0; 37 | our $opt_x; 38 | our $opt_d; 39 | GetOptions( 40 | "verbose" => \$verbose, 41 | "help" => sub {usage}, 42 | "x" => \$opt_x, 43 | "d=s" => \$opt_d 44 | ) or exit 1; 45 | usage unless @ARGV; 46 | 47 | $ENV{RUN_TESTS} ||= 1; 48 | 49 | my @cmd = ( 50 | qw(perl -w -Mlib=lib -Mlib=htmlgen), 51 | ($opt_x ? "-d:Trace" : ()), 52 | (defined $opt_d ? "-d:$opt_d" : ()), 53 | (map {"-M$_"} @ARGV), 54 | "-MChj::TEST=:all", 55 | "-e", 56 | '$r= run_tests(' 57 | . join(", ", map { singlequote $_ } @ARGV) 58 | . '); exit( $$r{failures} // $$r{fail})' 59 | ); 60 | 61 | # Note: accessing field {fail} as a fall back above to cater for 62 | # older versions of Chj::TEST. 63 | 64 | system @cmd; 65 | 66 | exit($? == 0 ? 0 : 1); 67 | -------------------------------------------------------------------------------- /meta/test.pl: -------------------------------------------------------------------------------- 1 | 2 | # general setup for testing. 3 | 4 | $ENV{TEST} = 1; # make sure that Chj::TEST TEST { } snippets are not 5 | # dropped because of an accidental setting of the TEST 6 | # env var by the user. 7 | 8 | 1 9 | -------------------------------------------------------------------------------- /meta/update-pod: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # Copyright (c) 2019-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 | # 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 STDERR map {"$_\n"} @_ if @_; 24 | print "$myname 25 | 26 | Update the autogenerated POD parts in some of the modules. 27 | 28 | "; 29 | exit(@_ ? 1 : 0); 30 | } 31 | 32 | use Getopt::Long; 33 | our $verbose = 0; 34 | GetOptions( 35 | "verbose" => \$verbose, 36 | "help" => sub {usage}, 37 | 38 | #"dry-run" => \$opt_dry, 39 | ) or exit 1; 40 | usage if @ARGV; 41 | 42 | use FunctionalPerl ":all"; 43 | 44 | my $newpart = FunctionalPerl::export_desc2pod; 45 | 46 | sub is_see_also { 47 | $_[0] =~ /^=head1 SEE ALSO/ 48 | } 49 | 50 | sub is_head1 { 51 | $_[0] =~ /^=head1 / 52 | } 53 | 54 | my $file = "lib/FunctionalPerl.pm"; 55 | 56 | xfile_replace_lines $file, sub { 57 | my ($lines) = @_; 58 | my ($top, $rest) 59 | = Keep($lines)->take_while_and_rest(complement \&is_see_also); 60 | $rest->ref(2) eq 61 | "This is the list of supported import tags and the modules and other tags that they import:\n" 62 | or die "no match"; 63 | my ($top2, $rest2) = $rest->split_at(4); 64 | $top2->last eq "\n" or die "no match"; 65 | my ($_drop, $remainder) 66 | = $rest2->take_while_and_rest(complement \&is_head1); 67 | my $res = stream_append($top, $top2, cons $newpart, $remainder); 68 | 69 | #repl; 70 | $res 71 | }; 72 | 73 | -------------------------------------------------------------------------------- /meta/with-profiling: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | IFS= 5 | 6 | export PERL5OPT=-d:NYTProf 7 | 8 | exec "$@" 9 | -------------------------------------------------------------------------------- /t-extra/evil-env.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # Copyright (c) 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 | 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 | 20 | #use lib "$mydir/../lib"; 21 | 22 | $ENV{TEST} = "0"; 23 | 24 | exec "make", "test" or exit 127; 25 | 26 | -------------------------------------------------------------------------------- /t-extra/out-of-order.t: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | IFS= 5 | 6 | # check that tests work even when called in any order; XX for now just 7 | # deletes tail-expanded files, should actually freshly unpack the 8 | # distribution tarball? 9 | 10 | # uses scripts from chj-bin, really only meant to be run by the 11 | # maintainer; look at the output whether it contains FAILED or other 12 | # issues 13 | ele bash -c ' 14 | for f in t/*.t; do 15 | rm -rf .htmlgen # XX and all other tail-expanded files 16 | echo "&&&& $f:" 17 | if $f; then 18 | echo OK 19 | else 20 | echo FAILED 21 | fi 22 | done 23 | ' 24 | 25 | -------------------------------------------------------------------------------- /t-slow/csvstreams.t: -------------------------------------------------------------------------------- 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 | 11 | use Test::Requires qw(5.020 Text::CSV); 12 | use Test::More; 13 | 14 | use lib "./lib"; 15 | use Chj::xperlfunc ":all"; 16 | 17 | require "./meta/find-perl.pl"; 18 | 19 | require "./testmem.pl"; 20 | setlimit_mem_MB($^V->{version}[1] < 15 ? 30 : 80) 21 | ; # 14 is enough on 32bit Debian, 64bit will need more 22 | 23 | SKIP: { 24 | if (0) { 25 | warn "todo: fix perl issue or functional-perl bug"; 26 | skip "Perl issue or functional-perl bug?", 3; # XXX 27 | } 28 | 29 | is xsystem_safe($^X, qw"examples/gen-csv t/test-a.csv 40000"), 0; 30 | 31 | # 20000 pass on 32bit Debian even with bug 32 | 33 | is xsystem_safe( 34 | $^X, qw"examples/csv_to_xml_short t/test-a.csv t/test-a.xml" 35 | ), 36 | 0; 37 | 38 | is xsystem_safe($^X, qw"examples/csv_to_xml t/test-a.csv -o t/test-a.xml"), 39 | 0; 40 | } 41 | 42 | done_testing; 43 | -------------------------------------------------------------------------------- /t/csv_to_xml.expected: -------------------------------------------------------------------------------- 1 | 2 | 0.123123443.3foobar -------------------------------------------------------------------------------- /t/csv_to_xml.t: -------------------------------------------------------------------------------- 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 | our $len = 210; 12 | require "./meta/readin.pl"; 13 | require "./meta/find-perl.pl"; 14 | 15 | use Test::Requires qw(Text::CSV); 16 | use Test::More; 17 | 18 | is readin("perl examples/csv_to_xml examples/csv_to_xml-example.csv|"), 19 | readin("< t/csv_to_xml.expected"); 20 | 21 | done_testing; 22 | -------------------------------------------------------------------------------- /t/dbi.t: -------------------------------------------------------------------------------- 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 | use lib "./lib"; 12 | use Chj::xperlfunc ":all"; 13 | 14 | require "./meta/find-perl.pl"; 15 | 16 | $ENV{RUN_TESTS} = 1; 17 | xexec_safe $^X, "examples/dbi"; 18 | 19 | -------------------------------------------------------------------------------- /t/examples-csv-to-json.data/a.csv: -------------------------------------------------------------------------------- 1 | Omega,Puritas,Triangle,Amplitude 2 | 41.00,1,Humble Bumble,"Miami, Florida" 3 | 40.09,3,Pretense Pusher,"New York, New York" 4 | -------------------------------------------------------------------------------- /t/examples-csv-to-json.data/a.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Amplitude": "Miami, Florida", 4 | "Omega": "41.00", 5 | "Puritas": "1", 6 | "Triangle": "Humble Bumble" 7 | }, 8 | { 9 | "Amplitude": "New York, New York", 10 | "Omega": "40.09", 11 | "Puritas": "3", 12 | "Triangle": "Pretense Pusher" 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /t/examples-csv-to-json.data/a.mint: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | amplitude = "Miami, Florida", 4 | omega = "41.00", 5 | puritas = "1", 6 | triangle = "Humble Bumble" 7 | }, 8 | { 9 | amplitude = "New York, New York", 10 | omega = "40.09", 11 | puritas = "3", 12 | triangle = "Pretense Pusher" 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /t/examples-csv-to-json.data/a_auto-integers.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Amplitude": "Miami, Florida", 4 | "Omega": 41, 5 | "Puritas": 1, 6 | "Triangle": "Humble Bumble" 7 | }, 8 | { 9 | "Amplitude": "New York, New York", 10 | "Omega": 40.09, 11 | "Puritas": 3, 12 | "Triangle": "Pretense Pusher" 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /t/examples-csv-to-json.data/a_auto-numbers.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Amplitude": "Miami, Florida", 4 | "Omega": 41.00, 5 | "Puritas": 1, 6 | "Triangle": "Humble Bumble" 7 | }, 8 | { 9 | "Amplitude": "New York, New York", 10 | "Omega": 40.09, 11 | "Puritas": 3, 12 | "Triangle": "Pretense Pusher" 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /t/examples-csv-to-json.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # Copyright (c) 2021 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 Test::Requires { experimental => '"signatures"' }; # usually "5.020" 11 | use experimental 'signatures'; 12 | 13 | use lib "./lib"; 14 | use Test::Requires qw(JSON Text::CSV); 15 | use Test::More; 16 | use Chj::xperlfunc qw(xxsystem_safe xsystem_safe xslurp); 17 | 18 | # XX move to a lib? 19 | 20 | sub cmp ($a, $b) { 21 | xslurp($a) eq xslurp($b) 22 | } 23 | 24 | sub stripCR($str) { 25 | $str =~ s/\r//; 26 | } 27 | 28 | sub cmp_stripCR ($a, $b) { 29 | stripCR(xslurp($a)) eq stripCR(xslurp($b)) 30 | } 31 | 32 | # /move 33 | 34 | sub t ($direct_mode, $result_file, @options) { 35 | local $ENV{GIT_PAGER} = ""; # disable git calling a pager 36 | my $inpath = "t/examples-csv-to-json.data/a.csv"; 37 | my $result_path = "t/examples-csv-to-json.data/$result_file"; 38 | my $outpath = $direct_mode ? $result_path : "$result_path-out"; 39 | xxsystem_safe $^X, "examples/csv-to-json", $inpath, @options, $outpath; 40 | if ($direct_mode) { 41 | 0 == xsystem_safe(qw(git diff --exit-code), $result_path) 42 | } else { 43 | 44 | # 0 == xxsystem_safe(qw(diff --strip-trailing-cr -u), $result_path, $outpath); 45 | # ^ does not work on Windows, so: 46 | cmp_stripCR $result_path, $outpath 47 | } 48 | } 49 | 50 | sub tests_in_gitmode ($direct_mode) { 51 | ok t($direct_mode, "a.json"); 52 | ok t($direct_mode, "a.mint", "--mint"); 53 | ok t($direct_mode, "a_auto-integers.json", "--auto-integers"); 54 | ok t($direct_mode, "a_auto-numbers.json", "--auto-numbers"); 55 | } 56 | 57 | tests_in_gitmode(-e ".git"); 58 | 59 | done_testing; 60 | -------------------------------------------------------------------------------- /t/examples-fibs.t: -------------------------------------------------------------------------------- 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 | use lib "./lib"; 12 | use Chj::xperlfunc ":all"; 13 | 14 | require "./meta/find-perl.pl"; 15 | 16 | $ENV{RUN_TESTS} = 1; 17 | xexec_safe $^X, "examples/fibs"; 18 | 19 | -------------------------------------------------------------------------------- /t/examples-functional-classes.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # Copyright (c) 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 | use lib "./lib"; 12 | use Chj::xperlfunc ":all"; 13 | 14 | require "./meta/find-perl.pl"; 15 | 16 | $ENV{RUN_TESTS} = 1; 17 | xexec_safe $^X, "examples/functional-classes"; 18 | 19 | -------------------------------------------------------------------------------- /t/examples-hiring-without-whiteboards.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # Copyright (c) 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 lib "./lib"; 12 | use Test::Requires qw(5.020 FP::Repl::Dependencies); 13 | use Chj::xperlfunc ":all"; 14 | 15 | require "./meta/find-perl.pl"; 16 | 17 | $ENV{RUN_TESTS} = 1; 18 | xexec_safe $^X, "examples/hiring-without-whiteboards"; 19 | 20 | -------------------------------------------------------------------------------- /t/examples-logic.t: -------------------------------------------------------------------------------- 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 | 11 | use Test::Requires qw(5.020); 12 | 13 | use lib "./lib"; 14 | use Chj::xperlfunc ":all"; 15 | 16 | require "./meta/find-perl.pl"; 17 | 18 | $ENV{RUN_TESTS} = 1; 19 | xexec_safe $^X, "examples/logic"; 20 | -------------------------------------------------------------------------------- /t/examples-perlweekly-111-1.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # Copyright (c) 2021 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 Test::Requires { experimental => '"signatures"' }; 11 | use experimental 'signatures'; 12 | 13 | use lib "./lib"; 14 | use Test::Requires qw(FP::autobox v5.32.1 FP::SortedPureArray); 15 | use Test::More; 16 | use Chj::xperlfunc qw(xexec_safe); 17 | 18 | $ENV{RUN_TESTS} = 1; 19 | xexec_safe $^X, "examples/perl-weekly-challenges/111-1-search_matrix", "--test"; 20 | -------------------------------------------------------------------------------- /t/examples-perlweekly-113-1.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # Copyright (c) 2021 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 Test::Requires { experimental => '"signatures"' }; 11 | use experimental 'signatures'; 12 | 13 | use lib "./lib"; 14 | 15 | use Test::Requires { experimental => '"signatures"', 16 | feature => '"current_sub"' }; 17 | use Test::More; 18 | use Chj::xperlfunc qw(xexec_safe); 19 | 20 | $ENV{RUN_TESTS} = 1; 21 | xexec_safe $^X, "examples/perl-weekly-challenges/113-1-represent_integer", 22 | "--test"; 23 | -------------------------------------------------------------------------------- /t/examples-perlweekly-113-2.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # Copyright (c) 2021 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 Test::Requires { experimental => '"signatures"' }; 11 | use experimental 'signatures'; 12 | 13 | use lib "./lib"; 14 | 15 | use Test::Requires { experimental => '"signatures"' }; 16 | use Test::More; 17 | use Chj::xperlfunc qw(xexec_safe); 18 | 19 | $ENV{RUN_TESTS} = 1; 20 | xexec_safe $^X, "examples/perl-weekly-challenges/113-2-recreate_binary_tree", 21 | "--test"; 22 | -------------------------------------------------------------------------------- /t/examples-primes.t: -------------------------------------------------------------------------------- 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 | use lib "./lib"; 12 | use Chj::xperlfunc ":all"; 13 | 14 | require "./meta/find-perl.pl"; 15 | 16 | $ENV{RUN_TESTS} = 1; 17 | xexec_safe $^X, "examples/primes"; 18 | -------------------------------------------------------------------------------- /t/examples-sendprepare: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # Copyright (c) 2019-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 Test::Requires qw(5.020); 12 | 13 | use lib "./lib"; 14 | use Chj::xperlfunc ":all"; 15 | 16 | require "./meta/find-perl.pl"; 17 | 18 | $ENV{RUN_TESTS} = 1; 19 | xexec_safe $^X, "examples/sendprepare"; 20 | -------------------------------------------------------------------------------- /t/functional_XML-t-div.t: -------------------------------------------------------------------------------- 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 | use lib "./lib"; 12 | use Chj::xperlfunc ":all"; 13 | 14 | require "./meta/find-perl.pl"; 15 | 16 | $ENV{RUN_TESTS} = 1; 17 | xexec_safe $^X, "functional_XML/t/div"; 18 | 19 | # XX run functional_XML/t/stream as well? That one is slow, though. 20 | -------------------------------------------------------------------------------- /t/functional_XML-test.expected: -------------------------------------------------------------------------------- 1 | 2 | 3 | example page - Hello World

Hello World

Garçon méchanique, "1 < 2" is true. this will be 404

1one
2two
3three

By Christian Jaeger, last modified at Sun Apr 26 18:03:20 1970 (or something).

-------------------------------------------------------------------------------- /t/functional_XML-test.t: -------------------------------------------------------------------------------- 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 | use lib "./lib"; 12 | use Chj::xperlfunc ":all"; 13 | use Test::More; 14 | 15 | require "./meta/find-perl.pl"; 16 | 17 | our $len = 672; 18 | 19 | xxsystem_safe($^X, "functional_XML/test", 10001000); 20 | 21 | is xslurp("out.xhtml"), xslurp("t/functional_XML-test.expected"); 22 | 23 | done_testing; 24 | -------------------------------------------------------------------------------- /t/htmlgen.t: -------------------------------------------------------------------------------- 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 | use Test::Requires '5.020'; 12 | 13 | use Test::Requires + { 14 | 15 | #'Sub::Call::Tail' => 0, 16 | 'Text::Markdown' => 0, 17 | 'HTML::TreeBuilder' => 0, 18 | 'Perl::Tidy' => 0, 19 | }; 20 | 21 | use lib "./lib"; 22 | use Chj::xperlfunc ":all"; 23 | 24 | require "./meta/find-perl.pl"; 25 | 26 | $ENV{RUN_TESTS} = 1; 27 | xexec_safe $^X, "website/gen"; 28 | -------------------------------------------------------------------------------- /t/intro-basics.t: -------------------------------------------------------------------------------- 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 | use lib "./lib"; 12 | use Chj::xperlfunc ":all"; 13 | 14 | require "./meta/find-perl.pl"; 15 | 16 | $ENV{RUN_TESTS} = 1; 17 | xexec_safe $^X, "intro/basics"; 18 | -------------------------------------------------------------------------------- /t/maintainer/perhaps: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | use warnings FATAL => 'uninitialized'; 6 | 7 | # "What happens here?: " 8 | 9 | use Test::More; 10 | 11 | { 12 | my ($a, $b) = (0, undef) or die "no"; 13 | 14 | is_deeply [$a, $b], [0, undef], "t1"; 15 | } 16 | 17 | like( 18 | ( 19 | eval { 20 | my ($a, $b) = do { 21 | (0, undef) or die "no" 22 | }; 23 | 1 24 | } 25 | || $@ 26 | ), 27 | qr/^no at /, 28 | "t2" 29 | ); 30 | 31 | { 32 | is_deeply [my ($a, $b) = (0, undef)], [0, undef], "t3"; 33 | } 34 | 35 | { 36 | is_deeply [ 37 | do { my ($a, $b) = (0, undef) } 38 | ], 39 | [0, undef], "t3b"; 40 | } 41 | 42 | { 43 | is_deeply [ 44 | scalar do { my ($a, $b) = (0, undef) } 45 | ], 46 | [2], "t3c"; 47 | } 48 | 49 | { 50 | is_deeply [ 51 | scalar do { my ($a, $b) = () } 52 | ], 53 | [0], "t3d"; 54 | } 55 | 56 | { 57 | is_deeply [my ($a, $b) = (0, undef) or die "no"], [2], "t4"; 58 | } 59 | 60 | done_testing; 61 | 62 | -------------------------------------------------------------------------------- /t/perl-goto-leak.t: -------------------------------------------------------------------------------- 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 | require "./meta/readin.pl"; 12 | 13 | use Test::More; 14 | 15 | require "./meta/find-perl.pl"; 16 | 17 | require "./testmem.pl"; 18 | setlimit_mem_MB(30); 19 | 20 | SKIP: { 21 | skip "Perl issue", 1 unless $ENV{TEST_PERL}; 22 | 23 | is readin("perl t/perl/goto-leak 100000 1 |"), "5000050000\n"; 24 | } 25 | 26 | done_testing; 27 | -------------------------------------------------------------------------------- /t/perl-weaken-coderef-correctness.t: -------------------------------------------------------------------------------- 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 | use lib "./lib"; 12 | use Chj::xperlfunc ":all"; 13 | 14 | require "./meta/find-perl.pl"; 15 | 16 | # test t/perl-weaken-coderef without memory pressure, to check code 17 | # correctness aside memory behaviour 18 | 19 | $ENV{TEST_PERL} = 1; 20 | $ENV{N} = 800; 21 | $ENV{RES} = 320400; 22 | xexec_safe $^X, "t/perl-weaken-coderef.t"; 23 | -------------------------------------------------------------------------------- /t/perl-weaken-coderef.t: -------------------------------------------------------------------------------- 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 | require "./meta/readin.pl"; 12 | 13 | require "./meta/find-perl.pl"; 14 | 15 | use Test::More; 16 | 17 | require "./testmem.pl"; 18 | setlimit_mem_MB($^V->{version}[1] < 15 ? 30 : 80); 19 | 20 | is readin("perl t/perl/weaken-coderef 2 50000 |"), "3\n"; 21 | 22 | my $n = $ENV{N} // 80000; 23 | my $res = ($ENV{RES} // 3200040000) . "\n"; 24 | 25 | is readin("perl t/perl/weaken-coderef $n 1 |"), $res; 26 | 27 | SKIP: { 28 | skip "Perl issue", 3 unless $ENV{TEST_PERL}; 29 | 30 | # XXX is this really a perl issue? 31 | 32 | is readin("perl t/perl/weaken-coderef-alternative-fix Y $n 1 |"), $res; 33 | 34 | is readin("perl t/perl/weaken-coderef-alternative-fix rec $n 1 |"), $res; 35 | 36 | is readin( 37 | "perl t/perl/weaken-coderef-alternative-fix haskell_uncurried $n 1 |"), 38 | $res; 39 | 40 | is readin("perl t/perl/weaken-coderef-alternative-fix '' $n 1 |"), $res; 41 | 42 | } 43 | 44 | done_testing; 45 | -------------------------------------------------------------------------------- /t/perl/__SUB__-gc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | my $copyright = <<'COPYRIGHT'; 4 | # Copyright 2021 by Christian Jaeger 5 | # Published under the same terms as perl itself 6 | COPYRIGHT 7 | 8 | use strict; 9 | use utf8; 10 | use warnings; 11 | use warnings FATAL => 'uninitialized'; 12 | use experimental 'signatures'; 13 | use feature 'current_sub'; # __SUB__ 14 | 15 | my ($mydir, $myname); 16 | 17 | BEGIN { 18 | $0 =~ /(.*?)([^\/]+)\z/s or die "?"; 19 | ($mydir, $myname) = ($1, $2); 20 | } 21 | 22 | # Just some random stupid curried and partially self-recursive 23 | # function: 24 | sub foo($x) { 25 | 26 | sub ($y) { 27 | 28 | # warn "f called"; 29 | my $f = __SUB__; 30 | 31 | sub ($z) { 32 | $z < 1000 ? $f->($x * $y)->($z + $y) : $z 33 | } 34 | } 35 | } 36 | 37 | # Now show that this doesn't leak: 38 | sub t ($n) { 39 | my $res; 40 | for (1 .. $n) { 41 | $res = foo(50)->(30)->(30); 42 | } 43 | $res 44 | } 45 | 46 | warn 47 | "Please verify manually from outside (e.g. `top`) that this process, pid $$, doesn't increase memory usage over time"; 48 | 49 | my $res = t 10000000; 50 | 51 | # use FP::Repl; repl; 52 | -------------------------------------------------------------------------------- /t/perl/weaken-coderef: -------------------------------------------------------------------------------- 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 | 11 | use Test::Requires qw(BSD::Resource); 12 | import BSD::Resource; 13 | use Scalar::Util "weaken"; 14 | use FP::Carp; 15 | 16 | sub rss { 17 | @_ == 0 or fp_croak_arity 0; 18 | (BSD::Resource::getrusage(BSD::Resource::RUSAGE_SELF()))[2] 19 | } 20 | 21 | sub naturals { 22 | my $f; 23 | $f = sub { 24 | my ($n) = @_; 25 | my $f = $f; 26 | sub { 27 | if ($n > 0) { [$n, &$f($n - 1)] } 28 | else { 29 | undef 30 | } 31 | } 32 | }; 33 | my $f_ = $f; 34 | weaken $f; 35 | goto &$f_; 36 | } 37 | 38 | sub stream_sum { 39 | my ($s) = @_; 40 | 41 | #weaken $_[0]; 42 | # ^ not necessary here, since, unlike with FP::Lazy::Promise, 43 | # resulting value is not saved in its 'generating container' 44 | my $tot = 0; 45 | LP: { 46 | if (my $fs = &$s) { 47 | ($tot, $s) = ($$fs[0] + $tot, $$fs[1]); 48 | goto LP; 49 | } else { 50 | $tot 51 | } 52 | } 53 | } 54 | 55 | @ARGV == 2 or die "usage: $0 n N"; 56 | my ($n, $N) = @ARGV; 57 | 58 | my $start = rss; 59 | 60 | my $res; 61 | for (1 .. $N) { 62 | my $ns = naturals $n; 63 | $res = stream_sum $ns; 64 | } 65 | 66 | print $res, "\n"; 67 | 68 | my $end = rss; 69 | 70 | if (($end - $start) / $start > 1.5) { 71 | die "leaked: $start .. $end"; 72 | } 73 | 74 | -------------------------------------------------------------------------------- /t/perl/weaken-coderef-alternative: -------------------------------------------------------------------------------- 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 Scalar::Util "weaken"; 12 | 13 | sub foo { 14 | my $f = sub { 15 | my ($f, $n) = @_; 16 | sub { 17 | if ($n > 0) { 18 | $n + &{ &$f($f, $n - 1) } 19 | } else { 20 | 0 21 | } 22 | } 23 | }; 24 | &$f($f, @_); 25 | } 26 | 27 | my $res = &{ foo 2 }; 28 | 29 | print $res, "\n"; 30 | 31 | -------------------------------------------------------------------------------- /t/perl/weaken-coderef-alternative-FP: -------------------------------------------------------------------------------- 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 | use Scalar::Util 'weaken'; 23 | 24 | @ARGV == 3 or die "usage: $0 impl n m"; 25 | our ($impl, $n, $m) = @ARGV; 26 | 27 | use FP::List ":all"; 28 | use FP::Lazy ":all"; 29 | 30 | sub naturals { 31 | my $f; 32 | $f = sub { 33 | my ($n) = @_; 34 | my $f = $f; 35 | lazy { 36 | if ($n > 0) { 37 | cons $n, &$f($n - 1) 38 | } else { 39 | null 40 | } 41 | } 42 | }; 43 | my $f_ = $f; 44 | weaken $f; 45 | goto &$f_; 46 | } 47 | 48 | sub stream_sum { 49 | my ($s) = @_; 50 | weaken $_[0]; 51 | 52 | # ^ not necessary here, since, unlike with FP::Lazy::Promise, 53 | # resulting value is not saved in its 'generating container' 54 | my $lp; 55 | $lp = sub { 56 | my ($tot, $s) = @_; 57 | weaken $_[1]; 58 | FORCE $s; 59 | if (is_null $s) { 60 | $tot 61 | } else { 62 | @_ = (car($s) + $tot, cdr $s); 63 | goto &$lp; 64 | } 65 | }; 66 | @_ = (0, $s); 67 | my $lp_ = $lp; 68 | weaken $lp; 69 | goto &$lp_; 70 | } 71 | 72 | my $res; 73 | for (1 .. $m) { 74 | my $ns = naturals $n; 75 | $res = stream_sum $ns; 76 | } 77 | 78 | print $res, "\n"; 79 | 80 | -------------------------------------------------------------------------------- /t/perl/weaken-coderef-alternative-__SUB__: -------------------------------------------------------------------------------- 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 | # "This token is only available under use v5.16 or the "current_sub" feature" 12 | use v5.16; 13 | 14 | sub foo { 15 | my $f = sub { 16 | my ($n) = @_; 17 | my $f = __SUB__; 18 | sub { 19 | if ($n > 0) { 20 | $n + &{ &$f($n - 1) } 21 | } else { 22 | 0 23 | } 24 | } 25 | }; 26 | goto &$f 27 | } 28 | 29 | my $res = &{ foo 2 }; 30 | 31 | print $res, "\n"; 32 | 33 | -------------------------------------------------------------------------------- /t/perl/weaken-coderef-alternative-fix: -------------------------------------------------------------------------------- 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 | use Scalar::Util 'weaken'; 23 | 24 | @ARGV == 3 or die "usage: $0 impl n m"; 25 | our ($impl, $n, $m) = @ARGV; 26 | 27 | use FP::fix; 28 | 29 | if ($impl) { 30 | $impl =~ /^\w+\z/ or die "invalid arg"; 31 | undef *fix; 32 | *fix = eval '\&FP::fix::' . $impl; 33 | } 34 | 35 | sub naturals { 36 | my $f = fix sub { 37 | my ($f, $n) = @_; 38 | sub { 39 | if ($n > 0) { [$n, &$f($n - 1)] } 40 | else { 41 | undef 42 | } 43 | } 44 | }; 45 | goto &$f; 46 | } 47 | 48 | sub stream_sum { 49 | my ($s) = @_; 50 | weaken $_[0]; 51 | 52 | # ^ not necessary here, since, unlike with FP::Lazy::Promise, 53 | # resulting value is not saved in its 'generating container' 54 | my $lp = fix sub { 55 | my ($lp, $tot, $s) = @_; 56 | weaken $_[2]; 57 | if (my $fs = &$s) { 58 | @_ = ($$fs[0] + $tot, $$fs[1]); 59 | goto &$lp; 60 | } else { 61 | $tot 62 | } 63 | }; 64 | @_ = (0, $s); 65 | my $lp_ = $lp; 66 | weaken $lp; 67 | goto &$lp_; 68 | } 69 | 70 | my $res; 71 | for (1 .. $m) { 72 | my $ns = naturals $n; 73 | $res = stream_sum $ns; 74 | } 75 | 76 | print $res, "\n"; 77 | 78 | -------------------------------------------------------------------------------- /t/perl/weaken-coderef-alternative-local: -------------------------------------------------------------------------------- 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 | sub foo { 12 | local *f = sub { 13 | my ($n) = @_; 14 | 15 | #f($n); # works, endless recursion. 16 | #my $f = \&f; # does not work on either v5.14.2 or bleadperl 17 | my $f = *f{CODE}; # neither does this (both give undef) 18 | sub { 19 | if ($n > 0) { 20 | $n + &{ &$f($n - 1) } 21 | } else { 22 | 0 23 | } 24 | } 25 | }; 26 | f(@_); 27 | } 28 | 29 | my $res = &{ foo 2 }; 30 | 31 | print $res, "\n"; 32 | 33 | -------------------------------------------------------------------------------- /t/perl/weaken-coderef-simplified: -------------------------------------------------------------------------------- 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 Scalar::Util "weaken"; 12 | 13 | sub foo { 14 | my $f; 15 | $f = sub { 16 | my ($n) = @_; 17 | my $f = $f; # create a new, strong binding for f to prevent it 18 | # from being freed (upon return from f) 19 | sub { 20 | if ($n > 0) { 21 | $n + &{ &$f($n - 1) } 22 | } else { 23 | 0 24 | } 25 | } 26 | }; 27 | my $f_ = $f; 28 | weaken $f; 29 | &$f_; 30 | } 31 | 32 | my $res = &{ foo 2 }; 33 | 34 | print $res, "\n"; 35 | 36 | -------------------------------------------------------------------------------- /t/predicates.t: -------------------------------------------------------------------------------- 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 | use lib "./lib"; 12 | use Chj::xperlfunc ":all"; 13 | require "./meta/find-perl.pl"; 14 | 15 | $ENV{RUN_TESTS} = 1; 16 | xexec_safe $^X, "examples/predicates"; 17 | -------------------------------------------------------------------------------- /t/require_and_run_tests.t: -------------------------------------------------------------------------------- 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 | 21 | BEGIN { 22 | # switch Chj::TEST to TAP style testing (unless requested 'old'), and 23 | # disable FP::Repl::Trap / FP::Repl::AutoTrap (unless requested 24 | # otherwise by another setting than "1"): 25 | $ENV{RUN_TESTS} ||= 1; 26 | } 27 | 28 | use Test::More; 29 | use lib "./meta"; 30 | use FunctionalPerl::TailExpand; 31 | use FunctionalPerl::ModuleList; 32 | use FunctionalPerl::Dependencies 'module_needs'; 33 | use Chj::Backtrace; 34 | 35 | require "./meta/find-perl.pl"; 36 | 37 | plan tests => 2; 38 | 39 | subtest "require" => sub { 40 | my $modules = do { 41 | if (my $mnl = $ENV{MODULENAMELIST}) { [split /\s+|,/, $mnl] } 42 | else { 43 | modulenamelist 44 | } 45 | }; 46 | 47 | for my $module (@$modules) { 48 | SKIP: { 49 | if (my @needs = module_needs $module) { 50 | skip "require $module - can't use @needs", 1; 51 | } 52 | require_ok $module; 53 | } 54 | } 55 | }; 56 | 57 | subtest "run_tests" => sub { 58 | 59 | # already loaded by require_ok above: 60 | Chj::TEST::run_tests(); 61 | }; 62 | 63 | #use Chj::ruse; 64 | #use Chj::Backtrace; 65 | #use FP::Repl; 66 | #repl; 67 | -------------------------------------------------------------------------------- /t/skip-internal.t: -------------------------------------------------------------------------------- 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 | use lib "./lib"; 12 | use Chj::xperlfunc ":all"; 13 | require "./meta/find-perl.pl"; 14 | 15 | $ENV{RUN_TESTS} = 1; 16 | xexec_safe $^X, "examples/skip"; 17 | -------------------------------------------------------------------------------- /t/skip-leak.t: -------------------------------------------------------------------------------- 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 | require "./meta/readin.pl"; 12 | require "./meta/find-perl.pl"; 13 | use Test::More; 14 | 15 | require "./testmem.pl"; 16 | setlimit_mem_MB(50); 17 | 18 | use lib "./lib"; 19 | use Chj::chompspace; 20 | 21 | is chompspace( 22 | readin( 23 | "( dd bs=10000 count=20000 2>/dev/null < /dev/zero || head -c 200000000 /dev/zero) | perl examples/skip --leaktest 10000000 1 | wc -c |" 24 | ) 25 | ), 26 | "189999999"; 27 | 28 | done_testing; 29 | -------------------------------------------------------------------------------- /t/skip.input: -------------------------------------------------------------------------------- 1 | Hello World. -------------------------------------------------------------------------------- /t/skip.t: -------------------------------------------------------------------------------- 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 | require "./meta/readin.pl"; 12 | require "./meta/find-perl.pl"; 13 | use Test::More; 14 | 15 | # test non-seekable input 16 | is readin("echo hallo | perl examples/skip 1 2 |"), "all"; 17 | 18 | sub t { 19 | my ($bufsiz) = @_; 20 | is readin("perl examples/skip --bufsiz $bufsiz 1 0 < t/skip.input|"), 21 | "ello World."; 22 | is readin("perl examples/skip --bufsiz $bufsiz 0 1 < t/skip.input|"), 23 | "Hello World"; 24 | is readin("perl examples/skip --bufsiz $bufsiz 4 5 < t/skip.input|"), 25 | "o W"; 26 | is readin("perl examples/skip --bufsiz $bufsiz 10 1 < t/skip.input|"), "d"; 27 | is readin("perl examples/skip --bufsiz $bufsiz 11 1 < t/skip.input|"), ""; 28 | is readin("perl examples/skip --bufsiz $bufsiz 11 2 < t/skip.input 2>&1 |", 29 | sub { $_[0] }), 30 | "skip: only 1 byte(s) left after skipping 11 byte(s)\n"; 31 | is readin("perl examples/skip --bufsiz $bufsiz 12 1 < t/skip.input 2>&1 |", 32 | sub { $_[0] }), 33 | "skip: no remainder left after skipping 12 byte(s)\n"; 34 | } 35 | 36 | t 1024; 37 | t 1; 38 | t 3; 39 | 40 | done_testing; 41 | -------------------------------------------------------------------------------- /t/testlazy.expected: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pflanze/functional-perl/f2fcc20eabea03a2fffb4af57d33110bd4f2a359/t/testlazy.expected -------------------------------------------------------------------------------- /t/testlazy.t: -------------------------------------------------------------------------------- 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 | our $len = 1000; 12 | require "./meta/readin.pl"; 13 | require "./meta/find-perl.pl"; 14 | use Test::More; 15 | 16 | use POSIX 'SIGPIPE'; 17 | our $sigpipe_is_fine = sub { 18 | my ($buf, $default_on_error) = @_; 19 | $? == SIGPIPE ? $buf : &$default_on_error(); 20 | }; 21 | 22 | is readin("perl functional_XML/testlazy |", $sigpipe_is_fine), 23 | readin("< t/testlazy.expected"); 24 | 25 | done_testing; 26 | -------------------------------------------------------------------------------- /t/testlazy10.expected: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pflanze/functional-perl/f2fcc20eabea03a2fffb4af57d33110bd4f2a359/t/testlazy10.expected -------------------------------------------------------------------------------- /t/testlazy10.t: -------------------------------------------------------------------------------- 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 | our $len = 759; 12 | require "./meta/readin.pl"; 13 | require "./meta/find-perl.pl"; 14 | use Test::More; 15 | 16 | $ENV{N} = 10; 17 | $ENV{T} = 0; 18 | $ENV{TZ} = "MET"; 19 | is readin("LANG=C TZ=UTC perl functional_XML/testlazy |"), 20 | readin("< t/testlazy10.expected"); 21 | 22 | done_testing; 23 | -------------------------------------------------------------------------------- /t/trampoline-fix.t: -------------------------------------------------------------------------------- 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 | use lib "./lib"; 12 | use Chj::xperlfunc ":all"; 13 | require "./meta/find-perl.pl"; # I'm still doing this just to be sure.. 14 | require "./testmem.pl"; 15 | 16 | # bleadperl on 64bit system needs enormeously more memory than v5.14.2 17 | # on 32bit. FIXME for the right combinations (or/and increase 18 | # iteration count to trigger it even with too high limits) 19 | my $m = ($^V->{version}[1] < 15 ? 30 : 100); 20 | warn "m=$m" if $ENV{RUN_TESTS_VERBOSE}; 21 | setlimit_mem_MB($m); 22 | 23 | use Test::Requires qw(Method::Signatures); 24 | 25 | # Also need Sub::Call::Tail: 26 | 27 | if (eval { require Sub::Call::Tail; 1 }) { 28 | 29 | $ENV{RUN_TESTS} = 1; 30 | xexec_safe $^X, "intro/more_tailcalls"; 31 | 32 | } else { 33 | 34 | # hack to run it without Sub::Call::Tail, e.g. on bleadperl where 35 | # this can't be installed currently. 36 | xxsystem_safe($^X, "bin/expand-tail", "intro/more_tailcalls", 37 | "intro/.expansion-more_tailcalls"); 38 | 39 | $ENV{RUN_TESTS} = 1; 40 | xexec_safe $^X, "intro/.expansion-more_tailcalls"; 41 | } 42 | -------------------------------------------------------------------------------- /testmem.pl: -------------------------------------------------------------------------------- 1 | use Test::Requires qw(BSD::Resource); 2 | use FP::Carp; 3 | import BSD::Resource; 4 | 5 | sub MB { 6 | @_ == 1 or fp_croak_arity 1; 7 | $_[0] * 1048576 8 | } 9 | 10 | my $RLIMIT_KIND = 11 | 12 | # At least OpenBSD does not have RLIMIT_VMEM; RLIMIT_DATA is the 13 | # memory limit it supports. Linux does, too, so just use that one 14 | RLIMIT_DATA; 15 | 16 | sub setlimit_mem_MB { 17 | @_ == 1 or fp_croak_arity 1; 18 | my ($limit_MB) = @_; 19 | my $limit = MB $limit_MB; 20 | setrlimit $RLIMIT_KIND, $limit, $limit or die "setrlimit: $!"; 21 | } 22 | 23 | 1 24 | -------------------------------------------------------------------------------- /website/FP-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pflanze/functional-perl/f2fcc20eabea03a2fffb4af57d33110bd4f2a359/website/FP-logo.png -------------------------------------------------------------------------------- /website/FP.css: -------------------------------------------------------------------------------- 1 | .logo2 { 2 | font-weight: bold; 3 | font-size:24px; 4 | } 5 | .footer_legalese { 6 | text-align: center; 7 | font-size: small; 8 | } 9 | .editandhist { 10 | text-align: right; 11 | font-size: small; 12 | margin-bottom: -1em; 13 | } -------------------------------------------------------------------------------- /website/gen: -------------------------------------------------------------------------------- 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 | 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 | 20 | unless ($ENV{SUB_CALL_TAIL}) { 21 | use lib "./meta"; 22 | require FunctionalPerl::TailExpand; 23 | } 24 | 25 | chdir $mydir or die "chdir $mydir: $!"; 26 | 27 | my $inbase = ".."; 28 | my $outbase = "www"; 29 | my $configpath = "./gen-config.pl"; 30 | 31 | # make it work by default without Sub::Call::Tail: 32 | my $prefix = $ENV{SUB_CALL_TAIL} ? "" : "."; 33 | 34 | my $gen = "../${prefix}htmlgen/gen"; 35 | 36 | exec $^X, $gen, $configpath, $inbase, $outbase, @ARGV 37 | or die 38 | "possibly missing '$gen', run 'meta/tail-expand' (or simply 'make test') first"; 39 | -------------------------------------------------------------------------------- /website/logo.pl: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use warnings FATAL => 'uninitialized'; 4 | use PXML::XHTML ":all"; 5 | 6 | my $homeurl = "http://functional-perl.org"; 7 | my $logo_from_base = "FP-logo.png"; 8 | 9 | sub { 10 | my ($path0) = @_; 11 | +{ 12 | homeurl => $homeurl, 13 | logo => DIV( 14 | { class => "header" }, 15 | A( 16 | { href => "$homeurl", class => "header" }, 17 | SPAN({ class => "logo2" }, "Functional "), 18 | IMG( 19 | { 20 | src => path_diff($path0, $logo_from_base), 21 | alt => "Logo", 22 | border => 0 23 | } 24 | ), 25 | SPAN({ class => "logo2" }, " Perl") 26 | ), 27 | SPAN( 28 | { class => "logo2" }, 29 | " $nbsp $nbsp $nbsp $nbsp $nbsp $nbsp $nbsp $nbsp $nbsp" 30 | ) 31 | ), 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /website/sync: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | # enable job control consistently, not just when there is a tty: 4 | set -m 5 | IFS= 6 | 7 | mydir=$(dirname "$0") 8 | 9 | set -x 10 | 11 | rsync -auvz --delete --exclude .git --exclude lpw-talk --exclude london.pm-talk "$mydir"/www/. servi:www/functional-perl.org/. 12 | 13 | --------------------------------------------------------------------------------