├── .gitignore ├── .proverc ├── .travis.yml ├── LICENSE ├── README.md ├── bin ├── crenv-install ├── crenv-uninstall └── crystal-build ├── cpanfile ├── lib ├── CrystalBuild.pm └── CrystalBuild │ ├── Builder │ └── Shards.pm │ ├── Downloader │ ├── Crystal.pm │ ├── Shards.pm │ └── Tarball.pm │ ├── GitHub.pm │ ├── Homebrew.pm │ ├── Installer │ ├── Crystal.pm │ └── Shards.pm │ ├── Resolver │ ├── Crystal.pm │ ├── Crystal │ │ ├── GitHub.pm │ │ └── RemoteCache.pm │ ├── Shards.pm │ └── Utils.pm │ ├── Sense.pm │ └── Utils.pm ├── script └── dev │ └── update_vendor_modules.bash ├── share └── crystal-build │ ├── .gitkeep │ ├── releases.json │ └── shards.json ├── t ├── 00_compile.t ├── Util.pm ├── bin │ ├── curl │ └── wget ├── crystal_build │ ├── builder │ │ └── shards │ │ │ ├── _run_script.t │ │ │ ├── build.t │ │ │ └── new.t │ ├── downloader │ │ ├── crystal │ │ │ ├── _detect_extracted_dirs.t │ │ │ ├── _detect_filename.t │ │ │ └── new.t │ │ ├── shards │ │ │ ├── _detect_extracted_dirs.t │ │ │ ├── _detect_filename.t │ │ │ └── new.t │ │ └── tarball │ │ │ ├── _detect_extracted_dirs.t │ │ │ ├── _detect_filename.t │ │ │ ├── download.t │ │ │ └── new.t │ ├── github │ │ ├── base_url.t │ │ ├── fetch.t │ │ ├── fetch_as_json.t │ │ ├── fetch_release.t │ │ ├── fetch_releases.t │ │ └── new.t │ ├── homebrew │ │ ├── alive.t │ │ ├── exists.t │ │ └── install.t │ ├── install.t │ ├── installer │ │ ├── crystal │ │ │ ├── _download.t │ │ │ ├── _move.t │ │ │ ├── _resolve.t │ │ │ ├── install.t │ │ │ ├── needs_shards.t │ │ │ ├── new.t │ │ │ └── versions.t │ │ └── shards │ │ │ ├── _build.t │ │ │ ├── _copy.t │ │ │ ├── _download.t │ │ │ ├── _resolve.t │ │ │ ├── install.t │ │ │ └── new.t │ ├── new.t │ ├── resolver │ │ ├── crystal │ │ │ ├── _create_enable_resolvers.t │ │ │ ├── _create_github_resolver.t │ │ │ ├── _create_remote_cache_resolver.t │ │ │ ├── github │ │ │ │ ├── _find_binary_download_urls.t │ │ │ │ ├── github.t │ │ │ │ ├── new.t │ │ │ │ ├── resolve.t │ │ │ │ └── versions.t │ │ │ ├── new.t │ │ │ ├── remote_cache │ │ │ │ ├── cache_url.t │ │ │ │ ├── fetcher.t │ │ │ │ ├── new.t │ │ │ │ ├── resolve.t │ │ │ │ └── versions.t │ │ │ ├── resolve.t │ │ │ ├── resolve_by_version.t │ │ │ └── versions.t │ │ ├── shards │ │ │ ├── _fetch.t │ │ │ ├── new.t │ │ │ └── resolve.t │ │ └── utils │ │ │ ├── normalize_version.t │ │ │ └── sort_version.t │ ├── sense │ │ └── import.t │ ├── show_definitions.t │ └── utils │ │ ├── extract_tar.t │ │ ├── parse_args.t │ │ └── system_info.t ├── data │ ├── archive.tar.gz │ ├── crystal-0.7.7-1.tar.gz │ └── test.txt └── t │ ├── create_crenv.t │ └── uri_for.t ├── test └── run ├── vendor └── lib │ ├── Class │ └── Accessor │ │ └── Lite.pm │ ├── File │ └── Which.pm │ ├── HTTP │ └── Command │ │ ├── Wrapper.pm │ │ └── Wrapper │ │ ├── Curl.pm │ │ └── Wget.pm │ ├── JSON │ ├── PP.pm │ └── PP │ │ └── Boolean.pm │ ├── Mac │ └── OSVersion │ │ └── Lite.pm │ ├── SemVer │ └── V2 │ │ └── Strict.pm │ ├── Text │ └── Caml.pm │ └── parent.pm └── xt ├── 00_lint.t └── perlcriticrc /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.swo 3 | *~ 4 | 5 | local 6 | cpanfile.snapshot 7 | 8 | t/tmp 9 | cover_db 10 | -------------------------------------------------------------------------------- /.proverc: -------------------------------------------------------------------------------- 1 | --lib 2 | --timer 3 | --color 4 | --failures 5 | --trap 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: perl 2 | sudo: false 3 | dist: trusty 4 | 5 | perl: 6 | - "5.22" 7 | - "5.20" 8 | - "5.18" 9 | - "5.16" 10 | # - "5.14" 11 | - "5.12" 12 | - "5.10" 13 | 14 | before_script: 15 | - git clone --depth 1 https://github.com/sstephenson/bats.git 16 | 17 | script: 18 | - PATH="./bats/bin:$PATH" test/run 19 | - prove -r xt 20 | - HARNESS_PERL_SWITCHES="-MDevel::Cover=+ignore,^local/|^t/|^vendor/" prove -r t 21 | 22 | after_success: 23 | - cover -report codecov 24 | 25 | notifications: 26 | slack: 27 | secure: "dOCP7BGx1DmOeKVbrp15+TXT1h6ne2eDLyrBeUkbVF7Rdbh2G2nV67P4vOSNF6GbbM2SJlvpdxLYgAkHey2fLyNeKPai4zHv21xqpfGQSDCYeqj8FBm0sfnqBPulDvl9ZbXtgfByQ7Vnt2C7g03WGgr76AJPDj5cWQvDVISqEqRpHEozdOQF635OtvatOgcQAAOEkXIxFk1V3k1lnhA23JNJMca/ZvLdPXyZvxQbaX747wTLwCu4BztFkLVfJ1JK1kkuXd6A2hdKzjIJEofwU+djfdGifrxZUZ1ILuVpnHXWtZBDwl7GDlDgLAzjbI8FV0tON/Lep2m0/mGqC6HQ10lpFK3erdTJa3UmCMDslPvjuGP37raJYCbWxOU64i/oWfAr6wkgfCBNQgyCuhotWytcFVH/o7fn6JtdoEE1nb5xYaPGxLbhL5k2NmpBP+u4sg9ogRxucGr5OxAVp73ZJJskxDgiJ6ECghR6Cvvc8SBUUpwgXLWTRfwcpfDOco68VnETGacTABpY9oBtUnEeBky/KGYMzw1jnDuaGbJKKl/sJCR5KIDvQMUIfr6Og8+tgQFnaqJX7+Fxd0PQ85tjWt0GL40Y1bt5u9eJPgpo3wJBqMa9XOV3spTiptS8Np6ZmaUZcqSfqirLBXnL29xbf/gulySEp4nW7FvvbCJ0TuE=" 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | | node-build 3 | | https://github.com/riywo/node-build 4 | 5 | (The MIT license) 6 | 7 | Copyright (c) 2013 Ryosuke Iwanaga 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 14 | 15 | ----------------------------------------------------------- 16 | 17 | | nodebrew 18 | | https://github.com/hokaccha/nodebrew 19 | 20 | (The MIT License) 21 | 22 | Copyright (c) 2011 Kazuhito Hokamura 23 | 24 | Permission is hereby granted, free of charge, to any person obtaining 25 | a copy of this software and associated documentation files (the 26 | 'Software'), to deal in the Software without restriction, including 27 | without limitation the rights to use, copy, modify, merge, publish, 28 | distribute, sublicense, and/or sell copies of the Software, and to 29 | permit persons to whom the Software is furnished to do so, subject to 30 | the following conditions: 31 | 32 | The above copyright notice and this permission notice shall be 33 | included in all copies or substantial portions of the Software. 34 | 35 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 36 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 37 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 38 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 39 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 40 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 41 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 42 | 43 | ----------------------------------------------------------- 44 | 45 | | JSON::PP 46 | | https://metacpan.org/pod/JSON::PP 47 | 48 | Copyright 2005-2013 by Makamaka Hannyaharamitu 49 | 50 | This library is free software; you can redistribute it and/or modify it 51 | under the same terms as Perl itself. 52 | 53 | ----------------------------------------------------------- 54 | 55 | | File::Which 56 | | https://metacpan.org/pod/File::Which 57 | 58 | This software is copyright (c) 2002 by Per Einar Ellefsen . 59 | 60 | This is free software; you can redistribute it and/or modify it under 61 | the same terms as the Perl 5 programming language system itself. 62 | 63 | ----------------------------------------------------------- 64 | 65 | | Text::Caml 66 | | https://metacpan.org/pod/Text::Caml 67 | 68 | Copyright (C) 2011-2015, Viacheslav Tykhanovskyi 69 | 70 | This program is free software, you can redistribute it and/or modify it under 71 | the terms of the Artistic License version 2.0. 72 | 73 | ----------------------------------------------------------- 74 | 75 | | crystal-build 76 | 77 | (The MIT license) 78 | 79 | Copyright (c) 2015-2016 Pine Mizune 80 | 81 | Permission is hereby granted, free of charge, to any person obtaining 82 | a copy of this software and associated documentation files (the 83 | 'Software'), to deal in the Software without restriction, including 84 | without limitation the rights to use, copy, modify, merge, publish, 85 | distribute, sublicense, and/or sell copies of the Software, and to 86 | permit persons to whom the Software is furnished to do so, subject to 87 | the following conditions: 88 | 89 | The above copyright notice and this permission notice shall be 90 | included in all copies or substantial portions of the Software. 91 | 92 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 93 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 94 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 95 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 96 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 97 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 98 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 99 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | crystal-build 2 | ------------- 3 | 4 | [![Build Status](https://travis-ci.org/pine/crystal-build.svg?branch=master)](https://travis-ci.org/pine/crystal-build) 5 | [![Build Status](https://www.bitrise.io/app/c772b4960037bae6.svg?token=cW8dSMlLIZn_a7takD622Q&branch=master)](https://www.bitrise.io/app/c772b4960037bae6) 6 | [![codecov.io](http://codecov.io/github/pine/crystal-build/coverage.svg?branch=master)](http://codecov.io/github/pine/crystal-build?branch=master) 7 | 8 | 9 | crystal-build is an [crenv](https://github.com/pine/crenv) plugin that provides an crenv install command. 10 | 11 | ## Warning: This project has been replaced and is no longer in active development 12 | 13 | It is being replaced by [crystal-build-cr](https://github.com/crenv/crystal-build-cr), which is a rewrite of this existing Perl codebase in Crystal. The Crystal replacement should satisfy the needs of anyone wanting to use `crystal-build`, so we suggest you use it instead of this repository. The rewrite is largely motivated by the previous maintainer no longer being able to maintain and find contributors for the Perl codebase. Pull requests are no longer being accepted for this repository. 14 | 15 | ## Install 16 | 17 | ``` 18 | $ git clone https://github.com/pine/crystal-build.git ~/.crenv/plugins/crystal-build 19 | ``` 20 | 21 | crystal-build currently supports only download a compiled tarball. 22 | 23 | ## Usage 24 | ### Using `crenv install` with crenv 25 | 26 | To install a Crystal version for use with crenv, run `crenv install` with the exact name of the version you want to install. For example, 27 | 28 | ``` 29 | crenv install 0.15.0 30 | ``` 31 | 32 | Crystal versions will be installed into a directory of the same name under `~/.crenv/versions`. 33 | 34 | To see a list of all available Crystal versions, run `crenv install --list`. 35 | 36 | ### Special environment variables 37 | 38 | - `CRYSTAL_BUILD_CACHE_PATH`, if set, specifies a directory to use for caching downloaded package files. 39 | 40 | ### Development 41 | 42 | Tests are executed using [Carton](https://github.com/perl-carton/carton): 43 | 44 | ``` 45 | $ carton install 46 | $ carton exec -- prove -r t # all 47 | $ carton exec -- prove t//.t 48 | ``` 49 | 50 | ### Acknowledgement 51 | 52 | - [riywo](https://github.com/riywo) 53 | - [hokaccha](https://github.com/hokaccha) 54 | 55 | ### Change log 56 | 57 | - 1.3.0 - Support FreeBSD 58 | - 1.2.0 - Support installing Crystal from Homebrew bottles 59 | - 1.1.0 - Support [shards](https://github.com/ysbaddaden/shards) auto-install 60 | - 1.0.0 - First release 61 | 62 | ## License 63 | MIT License 64 | -------------------------------------------------------------------------------- /bin/crenv-install: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Summary: Install a Crystal version using the crystal-build plugin 4 | # 5 | # Usage: crenv install 6 | # crenv install -l|--list|--without-release 7 | # 8 | # -l/--list List all available versions 9 | # --without-release Do not build the compiler in release mode 10 | # 11 | set -e 12 | [ -n "$CRENV_DEBUG" ] && set -x 13 | 14 | 15 | if [ -z "$CRENV_ROOT" ]; then 16 | CRENV_ROOT="${HOME}/.crenv" 17 | fi 18 | 19 | parse_options() { 20 | OPTIONS=() 21 | ARGUMENTS=() 22 | local arg option index 23 | 24 | for arg in "$@"; do 25 | if [ "${arg:0:1}" = "-" ]; then 26 | if [ "${arg:1:1}" = "-" ]; then 27 | OPTIONS[${#OPTIONS[*]}]="${arg:2}" 28 | else 29 | index=1 30 | while option="${arg:$index:1}"; do 31 | [ -n "$option" ] || break 32 | OPTIONS[${#OPTIONS[*]}]="$option" 33 | index=$(($index+1)) 34 | done 35 | fi 36 | else 37 | ARGUMENTS[${#ARGUMENTS[*]}]="$arg" 38 | fi 39 | done 40 | } 41 | 42 | definitions() { 43 | local query="$1" 44 | crystal-build --definitions | grep -F "$query" || true 45 | } 46 | 47 | indent() { 48 | sed 's/^/ /' 49 | } 50 | 51 | parse_options "$@" 52 | WITHOUT_RELEASE=0 53 | for option in "${OPTIONS[@]}"; do 54 | case "$option" in 55 | "l" | "list" ) 56 | echo "Available versions:" 57 | definitions | indent 58 | exit 59 | ;; 60 | "without-release" ) 61 | WITHOUT_RELEASE=1 62 | ;; 63 | esac 64 | done 65 | 66 | 67 | unset VERSION_NAME 68 | 69 | # The first argument contains the definition to install. If the 70 | # argument is missing, try to install whatever local app-specific 71 | # version is specified by rbenv. Show usage instructions if a local 72 | # version is not specified. 73 | DEFINITION="$1" 74 | 75 | [ -n "$DEFINITION" ] || DEFINITION="$(crenv local 2>/dev/null || true)" 76 | [ -n "$DEFINITION" ] || exit 1 77 | 78 | 79 | # Define `before_install` and `after_install` functions that allow 80 | # plugin hooks to register a string of code for execution before or 81 | # after the installation process. 82 | declare -a before_hooks after_hooks 83 | 84 | before_install() { 85 | local hook="$1" 86 | before_hooks["${#before_hooks[@]}"]="$hook" 87 | } 88 | 89 | after_install() { 90 | local hook="$1" 91 | after_hooks["${#after_hooks[@]}"]="$hook" 92 | } 93 | 94 | # Load plugin hooks. 95 | for script in $(crenv-hooks install); do 96 | source "$script" 97 | done 98 | 99 | 100 | # Set VERSION_NAME from $DEFINITION, if it is not already set. Then 101 | # compute the installation prefix. 102 | [ -n "$VERSION_NAME" ] || VERSION_NAME="${DEFINITION##*/}" 103 | PREFIX="${CRENV_ROOT}/versions/${VERSION_NAME}" 104 | 105 | [ -d "${PREFIX}" ] && PREFIX_EXISTS=1 106 | 107 | # If the installation prefix exists, prompt for confirmation unless 108 | # the --force option was specified. 109 | if [ -d "${PREFIX}/bin" ]; then 110 | echo "crenv: $PREFIX already exists" >&2 111 | read -p "continue with installation? (y/N) " 112 | 113 | case "$REPLY" in 114 | y* | Y* ) ;; 115 | * ) exit 1 ;; 116 | esac 117 | fi 118 | 119 | # If CRENV_BUILD_ROOT is set, always pass keep options to crystal-build. 120 | if [ -n "${CRENV_BUILD_ROOT}" ]; then 121 | export CRYSTAL_BUILD_BUILD_PATH="${CRENV_BUILD_ROOT}/${VERSION_NAME}" 122 | fi 123 | 124 | # Set CRYSTAL_BUILD_CACHE_PATH to $CRENV_ROOT, if the directory 125 | # exists and the variable is not already set. 126 | if [ -z "${CRYSTAL_BUILD_CACHE_PATH}" ] && [ -d "${CRENV_ROOT}" ]; then 127 | mkdir -p "${CRENV_ROOT}/cache" 128 | export CRYSTAL_BUILD_CACHE_PATH="${CRENV_ROOT}/cache" 129 | fi 130 | 131 | # Default CRENV_VERSION to the globally-specified Node version. 132 | export CRENV_VERSION="$(crenv global 2>/dev/null || true)" 133 | 134 | 135 | # Execute `before_install` hooks. 136 | for hook in "${before_hooks[@]}"; do eval "$hook"; done 137 | 138 | # Plan cleanup on unsuccessful installation. 139 | cleanup() { 140 | [ -z "${PREFIX_EXISTS}" ] && rm -rf "$PREFIX" 141 | } 142 | 143 | trap cleanup SIGINT 144 | 145 | # Invoke `crystal-build` and record the exit status in $STATUS. 146 | STATUS=0 147 | crystal-build "$DEFINITION" "$PREFIX" "$WITHOUT_RELEASE" || STATUS="$?" 148 | 149 | # Display a more helpful message if the definition wasn't found. 150 | if [ "$STATUS" == "2" ]; then 151 | { 152 | echo "If the version you're looking for is not present, first try upgrading" 153 | echo "crystal-build. If it's still missing, open a request on the crystal-build" 154 | echo "issue tracker: https://github.com/pine613/crystal-build/issues" 155 | } >&2 156 | fi 157 | 158 | # Execute `after_install` hooks. 159 | for hook in "${after_hooks[@]}"; do eval "$hook"; done 160 | 161 | # Run `crenv-rehash` after a successful installation. 162 | if [ "$STATUS" == "0" ]; then 163 | crenv rehash 164 | else 165 | cleanup 166 | fi 167 | 168 | exit "$STATUS" 169 | 170 | # vim: se et ts=2 sw=2 sts=2 ft=sh : 171 | -------------------------------------------------------------------------------- /bin/crenv-uninstall: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Summary: Uninstall a specific Crystal version 4 | # 5 | # Usage: crenv uninstall [-f|--force] 6 | # 7 | # -f Attempt to remove the specified version without prompting 8 | # for confirmation. If the version does not exist, do not 9 | # display an error message. 10 | # 11 | # See `crenv versions` for a complete list of installed versions. 12 | # 13 | set -e 14 | 15 | # Provide rbenv completions 16 | if [ "$1" = "--complete" ]; then 17 | exec crenv versions --bare 18 | fi 19 | 20 | if [ -z "$CRENV_ROOT" ]; then 21 | CRENV_ROOT="${HOME}/.crenv" 22 | fi 23 | 24 | unset FORCE 25 | if [ "$1" = "-f" ] || [ "$1" = "--force" ]; then 26 | FORCE=true 27 | shift 28 | fi 29 | 30 | DEFINITION="$1" 31 | case "$DEFINITION" in 32 | "" | -* ) 33 | # We can remove the sed fallback once rbenv 0.4.0 is widely available. 34 | { crenv-help uninstall 2>/dev/null || 35 | sed -ne '/^#/!q;s/.\{1,2\}//;1,4d;p' < "$0" 36 | } >&2 37 | exit 1 38 | ;; 39 | esac 40 | 41 | VERSION_NAME="${DEFINITION##*/}" 42 | PREFIX="${CRENV_ROOT}/versions/${VERSION_NAME}" 43 | 44 | if [ -z "$FORCE" ]; then 45 | if [ ! -d "$PREFIX" ]; then 46 | echo "crenv: version \`$VERSION_NAME' not installed" >&2 47 | exit 1 48 | fi 49 | 50 | read -p "crenv: remove $PREFIX? " 51 | case "$REPLY" in 52 | y* | Y* ) ;; 53 | * ) exit 1 ;; 54 | esac 55 | fi 56 | 57 | if [ -d "$PREFIX" ]; then 58 | rm -rf "$PREFIX" 59 | crenv rehash 60 | fi 61 | -------------------------------------------------------------------------------- /bin/crystal-build: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | use utf8; 6 | 7 | use File::Spec; 8 | use File::Basename qw/dirname/; 9 | use lib ( 10 | File::Spec->catfile(dirname(__FILE__), qw/.. vendor lib/), 11 | File::Spec->catfile(dirname(__FILE__), qw/.. lib/), 12 | ); 13 | 14 | use Cwd qw/abs_path/; 15 | use HTTP::Command::Wrapper; 16 | 17 | use CrystalBuild; 18 | use CrystalBuild::Utils; 19 | 20 | sub main { 21 | my $args = CrystalBuild::Utils::parse_args(@ARGV); 22 | my $github_repo = 'crystal-lang/crystal'; 23 | my $cache_dir = $ENV{CRYSTAL_BUILD_CACHE_PATH} // '.'; 24 | 25 | my $share_url = 'https://raw.githubusercontent.com/pine/crystal-build/master/share/crystal-build/'; 26 | my $cache_url = $share_url.'/releases.json'; 27 | my $shards_url = $share_url.'/shards.json'; 28 | 29 | my $crenv = CrystalBuild->new( 30 | prefix => $args->{prefix}, 31 | cache => $args->{cache}, 32 | cache_url => $cache_url, 33 | shards_url => $shards_url, 34 | cache_dir => abs_path($cache_dir), 35 | fetcher => HTTP::Command::Wrapper->create, 36 | github_repo => $github_repo, 37 | without_release => $args->{without_release}, 38 | ); 39 | 40 | if ($args->{definitions}) { 41 | $crenv->show_definitions; 42 | } 43 | 44 | else { 45 | $crenv->install($args->{version}); 46 | } 47 | } 48 | 49 | main unless caller; 50 | 51 | # vim: se et ts=4 sw=4 sts=4 ft=perl : 52 | -------------------------------------------------------------------------------- /cpanfile: -------------------------------------------------------------------------------- 1 | requires 'parent', '0.234'; 2 | 3 | requires 'Class::Accessor::Lite', '0.08'; 4 | requires 'HTTP::Command::Wrapper', '0.04'; 5 | requires 'JSON::PP', '2.27300'; 6 | requires 'Mac::OSVersion::Lite', '0.07'; 7 | requires 'SemVer::V2::Strict', '0.10'; 8 | requires 'Text::Caml', '0.14'; 9 | 10 | on test => sub { 11 | requires 'Data::Dumper', '2.154'; 12 | requires 'Data::Section::Simple', '0.07'; 13 | 14 | requires 'Capture::Tiny', '0.30'; 15 | requires 'File::Touch', '0.08'; 16 | requires 'File::Path', '2.09'; 17 | requires 'File::Temp', '0.2304'; 18 | requires 'File::Slurp', '9999.19'; 19 | requires 'Module::Find', '0.13'; 20 | requires 'Scope::Guard', '0.21'; 21 | 22 | requires 'Test::More', '1.001014'; 23 | requires 'Test::Deep', '0.117'; 24 | requires 'Test::Deep::Matcher', '0.01'; 25 | requires 'Test::Exception', '0.40'; 26 | 27 | requires 'Test::Mock::Cmd', '0.6'; 28 | requires 'Test::Mock::Guard', '0.10'; 29 | requires 'Test::MockObject', '1.20150527'; 30 | requires 'Test::MockTime', '0.15'; 31 | requires 'Test::TCP', '2.12'; 32 | requires 'Plack', '1.0037'; 33 | 34 | requires 'Devel::Cover', '1.20'; 35 | requires 'Devel::Cover::Report::Codecov'; 36 | requires 'Perl::Critic', '1.125'; 37 | requires 'Test::Perl::Critic', '1.03'; 38 | }; 39 | 40 | on develop => sub { 41 | requires 'Data::Printer', '0.38'; 42 | }; 43 | -------------------------------------------------------------------------------- /lib/CrystalBuild.pm: -------------------------------------------------------------------------------- 1 | package CrystalBuild; 2 | use CrystalBuild::Sense; 3 | 4 | our $VERSION = '1.3.2'; 5 | 6 | use File::Path qw/rmtree mkpath/; 7 | use JSON::PP; 8 | use SemVer::V2::Strict; 9 | 10 | use CrystalBuild::Utils; 11 | use CrystalBuild::GitHub; 12 | use CrystalBuild::Installer::Crystal; 13 | use CrystalBuild::Installer::Shards; 14 | use CrystalBuild::Resolver::Utils; 15 | 16 | sub new { 17 | my ($class, %opt) = @_; 18 | 19 | my $self = +{ %opt }; 20 | return bless $self => $class; 21 | } 22 | 23 | sub install { 24 | my ($self, $v) = @_; 25 | my $version = CrystalBuild::Resolver::Utils->normalize_version($v); 26 | 27 | $self->crystal_installer->install($version, $self->{prefix}); 28 | if ($self->crystal_installer->needs_shards($version)) { 29 | $self->shards_installer->install($version, $self->{prefix}); 30 | } 31 | 32 | say 'Install successful'; 33 | } 34 | 35 | sub crystal_installer { 36 | my $self = shift; 37 | state $installer = CrystalBuild::Installer::Crystal->new( 38 | fetcher => $self->{fetcher}, 39 | github_repository => $self->{github_repo}, 40 | remote_cache_url => $self->{cache_url}, 41 | cache_dir => $self->{cache_dir}, 42 | use_remote_cache => $self->use_remote_cache, 43 | use_github => 1, 44 | ); 45 | return $installer; 46 | } 47 | 48 | sub shards_installer { 49 | my $self = shift; 50 | state $installer = CrystalBuild::Installer::Shards->new( 51 | fetcher => $self->{fetcher}, 52 | remote_cache_url => $self->{shards_url}, 53 | cache_dir => $self->{cache_dir}, 54 | without_release => $self->{without_release}, 55 | ); 56 | return $installer; 57 | } 58 | 59 | sub show_definitions { 60 | my $self = shift; 61 | say $_ for @{ $self->crystal_installer->versions }; 62 | } 63 | 64 | sub get_install_dir { 65 | my $self = shift; 66 | 67 | my $dir = $self->{prefix}; 68 | rmtree $dir if -d $dir; 69 | mkpath $dir unless -e $dir; 70 | 71 | return $dir; 72 | } 73 | 74 | sub use_remote_cache { shift->{cache} } 75 | 76 | 1; 77 | -------------------------------------------------------------------------------- /lib/CrystalBuild/Builder/Shards.pm: -------------------------------------------------------------------------------- 1 | package CrystalBuild::Builder::Shards; 2 | use CrystalBuild::Sense; 3 | 4 | use Cwd qw/abs_path/; # >= perl 5 5 | use File::Temp qw/tempfile/; 6 | use Text::Caml; 7 | 8 | use CrystalBuild::Utils; 9 | 10 | sub new { 11 | my ($class, %opt) = @_; 12 | bless { %opt } => $class; 13 | } 14 | 15 | sub without_release { shift->{without_release} } 16 | 17 | sub build { 18 | my ($self, $target_dir, $crystal_dir) = @_; 19 | 20 | my $shards_bin = "$target_dir/bin/shards"; 21 | 22 | { 23 | my $script = $self->_create_build_script($target_dir, $crystal_dir); 24 | return $shards_bin if $self->_run_script($script); 25 | } 26 | 27 | print "\n"; 28 | print 'Retry building Shards ... '; 29 | 30 | { 31 | my $script = $self->_create_build_script($target_dir, $crystal_dir, 1); 32 | return $shards_bin if $self->_run_script($script); 33 | } 34 | 35 | die "shards build faild: $target_dir"; 36 | } 37 | 38 | sub _create_build_script { 39 | my ($self, $target_dir, $crystal_dir, $no_pie_fg) = @_; 40 | 41 | my ($platform) = CrystalBuild::Utils::system_info(); 42 | my $template = $self->_get_data_section; 43 | 44 | # Determine crystal version from the new crystal path, split the folder to 45 | # determine the version 46 | my $crystal_version = (split "/", $crystal_dir)[-1]; 47 | my @vers = split(/\./, $crystal_version); 48 | my ($major, $minor, $patch) = @vers[0..2]; 49 | 50 | # Crystal versions before 0.20.5 did not have the --no-debug flag, so we 51 | # must exclude it then 52 | my $release_flags = ''; 53 | if (($major == "0") && ($minor <= "20") && (($patch < "5") || ($minor < "20"))) { 54 | my $release_flags = "--release"; 55 | } else { 56 | my $release_flags = "--release --no-debug"; 57 | } 58 | 59 | my $params = { 60 | crystal_dir => abs_path($crystal_dir), 61 | target_dir => $target_dir, 62 | platform => $platform, 63 | link_flags => $no_pie_fg ? '-no-pie' : '', 64 | without_release_flags => $self->without_release ? '' : $release_flags, 65 | }; 66 | 67 | return Text::Caml->new->render($template, $params); 68 | } 69 | 70 | sub _run_script { 71 | my ($self, $script) = @_; 72 | 73 | my ($fh, $filename) = tempfile(); 74 | print $fh $script; 75 | close $fh; 76 | 77 | chmod 0755, $filename; 78 | return system($filename) == 0; 79 | } 80 | 81 | sub _get_data_section { 82 | my $pos = tell DATA; 83 | my $data = do { local $/; }; 84 | 85 | seek DATA, $pos, 0; 86 | 87 | return $data; 88 | } 89 | 90 | 1; 91 | __DATA__ 92 | #!/usr/bin/env bash 93 | 94 | set -e 95 | 96 | export CRYSTAL_PATH={{crystal_dir}}/libs:{{crystal_dir}}/src:{{crystal_dir}}/lib:. 97 | export LIBRARY_PATH={{target_dir}}:/usr/local/lib:$LIBRARY_PATH 98 | export LD_LIBRARY_PATH={{target_dir}}:/usr/local/lib:$LD_LIBRARY_PATH 99 | 100 | if [ "{{platform}}" = "darwin" ]; then 101 | if which brew > /dev/null 2>&1; then 102 | prefix=`brew --prefix libyaml 2>/dev/null` 103 | 104 | if [ ! -f "$prefix/lib/libyaml.a" ]; then 105 | echo "" 106 | brew install libyaml || true 107 | prefix=`brew --prefix libyaml 2>/dev/null` 108 | fi 109 | 110 | if [ -f "$prefix/lib/libyaml.a" ]; then 111 | cp -f "$prefix/lib/libyaml.a" "{{target_dir}}/libyaml.a" 112 | fi 113 | fi 114 | fi 115 | 116 | cd "{{target_dir}}" 117 | 118 | if [ -z "{{link_flags}}" ]; then 119 | "{{crystal_dir}}/bin/crystal" build src/shards.cr -o bin/shards {{without_release_flags}} 120 | else 121 | "{{crystal_dir}}/bin/crystal" build src/shards.cr -o bin/shards --link-flags "{{link_flags}}" {{without_release_flags}} 122 | fi 123 | -------------------------------------------------------------------------------- /lib/CrystalBuild/Downloader/Crystal.pm: -------------------------------------------------------------------------------- 1 | package CrystalBuild::Downloader::Crystal; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use parent qw/CrystalBuild::Downloader::Tarball/; 6 | 7 | use File::Spec; 8 | 9 | sub _detect_filename { 10 | my ($self, $url) = @_; 11 | 12 | return $1 if $url && $url =~ /\/([\w\.-]+)$/; 13 | die 'Invalid download URL'; 14 | } 15 | 16 | sub _detect_extracted_dirs { 17 | my ($self, $cache_dir) = @_; 18 | my $matches = [ 19 | glob(File::Spec->join($cache_dir, 'crystal')), # for Homebrew bottles 20 | glob(File::Spec->join($cache_dir, 'crystal-*')), # for GitHub releases 21 | ]; 22 | return grep { -d $_ } @$matches; 23 | } 24 | 25 | 1; 26 | -------------------------------------------------------------------------------- /lib/CrystalBuild/Downloader/Shards.pm: -------------------------------------------------------------------------------- 1 | package CrystalBuild::Downloader::Shards; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use parent qw/CrystalBuild::Downloader::Tarball/; 6 | 7 | use File::Spec; 8 | 9 | sub _detect_filename { 10 | my ($self, $url) = @_; 11 | 12 | return "shards-$1" if $url && $url =~ /\/([\w\.-]+)$/; 13 | die 'Invalid download URL'; 14 | } 15 | 16 | sub _detect_extracted_dirs { 17 | my ($self, $cache_dir) = @_; 18 | return grep { -d $_ } glob File::Spec->join($cache_dir, 'shards-*'); 19 | } 20 | 21 | 1; 22 | -------------------------------------------------------------------------------- /lib/CrystalBuild/Downloader/Tarball.pm: -------------------------------------------------------------------------------- 1 | package CrystalBuild::Downloader::Tarball; 2 | 3 | use CrystalBuild::Utils; 4 | 5 | use File::Spec; 6 | use File::Path qw/rmtree mkpath/; # => 5.001 7 | 8 | sub new { 9 | my ($class, %opt) = @_; 10 | my $self = bless {} => $class; 11 | 12 | die 'A fetcher is required' unless $opt{fetcher}; 13 | $self->{fetcher} = $opt{fetcher}; 14 | 15 | return $self; 16 | } 17 | 18 | sub download { 19 | my ($self, $tarball_url, $cache_dir) = @_; 20 | 21 | my $filename = $self->_detect_filename($tarball_url); 22 | my $tarball_path = File::Spec->join($cache_dir, $filename); 23 | 24 | mkpath $cache_dir unless -e $cache_dir; 25 | $self->{fetcher}->download($tarball_url, $tarball_path) 26 | or die "download faild: $tarball_url"; 27 | 28 | rmtree $_ for $self->_detect_extracted_dirs($cache_dir); 29 | 30 | CrystalBuild::Utils::extract_tar($tarball_path, $cache_dir); 31 | 32 | my ($target_dir) = $self->_detect_extracted_dirs($cache_dir); 33 | return $target_dir; 34 | } 35 | 36 | sub _detect_filename { die "abstract method\n" } 37 | sub _detect_extracted_dirs { die "abstract method\n" } 38 | 39 | 1; 40 | -------------------------------------------------------------------------------- /lib/CrystalBuild/GitHub.pm: -------------------------------------------------------------------------------- 1 | package CrystalBuild::GitHub; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | use JSON::PP; 7 | 8 | sub new { 9 | my ($class, %opt) = @_; 10 | 11 | my $self = +{ %opt }; 12 | bless $self => $class; 13 | } 14 | 15 | sub fetch { 16 | my ($self, $url) = @_; 17 | $self->{fetcher}->fetch($url, ['Accept: application/vnd.github.v3+json' ]); 18 | } 19 | 20 | sub base_url { 21 | my $self = shift; 22 | 'https://api.github.com/repos/' . $self->{github_repo} . '/'; 23 | } 24 | 25 | sub fetch_as_json { 26 | my ($self, $path) = @_; 27 | 28 | my $url = $self->base_url . $path; 29 | my $content = $self->fetch($url); 30 | decode_json($content); 31 | } 32 | 33 | sub fetch_release { 34 | my ($self, $version) = @_; 35 | $self->fetch_as_json('releases/tags/' . $version); 36 | } 37 | 38 | sub fetch_releases { 39 | my ($self) = @_; 40 | $self->fetch_as_json('releases?per_page=100'); 41 | } 42 | 43 | 1; 44 | -------------------------------------------------------------------------------- /lib/CrystalBuild/Homebrew.pm: -------------------------------------------------------------------------------- 1 | package CrystalBuild::Homebrew; 2 | use CrystalBuild::Sense; 3 | 4 | use File::Which; 5 | 6 | use Class::Accessor::Lite ( 7 | new => 1, 8 | ); 9 | 10 | sub alive { 11 | my $self = shift; 12 | return !! which('brew'); 13 | } 14 | 15 | sub exists { 16 | my ($self, $formula) = @_; 17 | my $list = `brew list`; 18 | return 0 if $? != 0; 19 | 20 | my @formulas = grep { $_ } split(/\s+/, $list); 21 | return !! grep { $_ eq $formula } @formulas; 22 | } 23 | 24 | sub install { 25 | my ($self, $formula) = @_; 26 | 27 | unless ($self->exists($formula)) { 28 | say "Installing $formula by Homebrew"; 29 | return system("brew install $formula") == 0; 30 | } 31 | 32 | return 1; # already installed 33 | } 34 | 35 | 1; 36 | __END__ 37 | -------------------------------------------------------------------------------- /lib/CrystalBuild/Installer/Crystal.pm: -------------------------------------------------------------------------------- 1 | package CrystalBuild::Installer::Crystal; 2 | use CrystalBuild::Sense; 3 | 4 | use File::Path qw/mkpath rmtree/; # => 5.001 5 | 6 | use CrystalBuild::Utils; 7 | use CrystalBuild::Homebrew; 8 | use CrystalBuild::Downloader::Crystal; 9 | use CrystalBuild::Resolver::Utils; 10 | use CrystalBuild::Resolver::Crystal; 11 | 12 | sub new { 13 | my ($class, %opt) = @_; 14 | my $self = bless {} => $class; 15 | 16 | $self->{fetcher} = $opt{fetcher}; 17 | $self->{cache_dir} = $opt{cache_dir}; 18 | $self->{resolver} = $self->_create_resolver(%opt); 19 | 20 | return $self; 21 | } 22 | 23 | sub install { 24 | my ($self, $crystal_version, $install_dir) = @_; 25 | 26 | eval { 27 | my $tarball_url = $self->_resolve($crystal_version); 28 | 29 | say "Downloading Crystal binary tarball ..."; 30 | say $tarball_url; 31 | my $extracted_dir = $self->_download($tarball_url, $crystal_version); 32 | say "ok"; 33 | 34 | print "Moving the Crystal directory ..."; 35 | 36 | # Homebrew's tarballs are structured differently 37 | if ($tarball_url =~ /homebrew/) { 38 | $extracted_dir = $extracted_dir . "/" . $crystal_version; 39 | } 40 | 41 | $self->_move($extracted_dir, $install_dir); 42 | print "ok\n"; 43 | 44 | if ($self->_is_installed_from_homebrew($tarball_url)) { 45 | print 'Checking if depended Homebrew formulas installed ... '; 46 | 47 | my $brew = CrystalBuild::Homebrew->new; 48 | 49 | die "Error: Homebrew not found\n" unless $brew->alive; 50 | 51 | $brew->install('bdw-gc'); 52 | $brew->install('libevent'); 53 | $brew->install('libyaml'); 54 | 55 | say 'ok'; 56 | } 57 | }; 58 | 59 | CrystalBuild::Utils::error_and_exit($@) if $@; 60 | } 61 | 62 | sub versions { 63 | my $self = shift; 64 | my $versions = eval { $self->{resolver}->versions }; 65 | 66 | CrystalBuild::Utils::error_and_exit('avaiable versions not found') if $@; 67 | 68 | my @normalized_versions = map { CrystalBuild::Resolver::Utils->normalize_version($_) } @$versions; 69 | my $sorted_versions = CrystalBuild::Resolver::Utils->sort_version(\@normalized_versions); 70 | 71 | return $sorted_versions; 72 | } 73 | 74 | sub needs_shards { 75 | my ($self, $version) = @_; 76 | my $v077 = SemVer::V2::Strict->new('0.7.7'); 77 | return SemVer::V2::Strict->new($version) >= $v077; # >= v0.7.7 78 | } 79 | 80 | sub _resolve { 81 | my ($self, $crystal_version) = @_; 82 | return $self->{resolver}->resolve_by_version($crystal_version); 83 | } 84 | 85 | sub _download { 86 | my ($self, $tarball_url, $crystal_version) = @_; 87 | my $cache_dir = "$self->{cache_dir}/$crystal_version"; 88 | return CrystalBuild::Downloader::Crystal->new( 89 | fetcher => $self->{fetcher}, 90 | )->download($tarball_url, $cache_dir); 91 | } 92 | 93 | sub _move { 94 | my ($self, $extracted_dir, $install_dir) = @_; 95 | mkpath $install_dir unless -e $install_dir; 96 | rmtree $install_dir if -e $install_dir; 97 | rename $extracted_dir, $install_dir 98 | or die "faild to move the Crystal directory $!\n"; 99 | } 100 | 101 | sub _create_resolver { 102 | my ($self, %opt) = @_; 103 | return CrystalBuild::Resolver::Crystal->new( 104 | fetcher => $opt{fetcher}, 105 | github_repository => $opt{github_repository}, 106 | remote_cache_url => $opt{remote_cache_url}, 107 | use_remote_cache => $opt{use_remote_cache}, 108 | use_github => $opt{use_github}, 109 | ); 110 | } 111 | 112 | sub _is_installed_from_homebrew { 113 | my ($self, $tarball_url) = @_; 114 | return index($tarball_url, 'homebrew') > -1; 115 | } 116 | 117 | 1; 118 | -------------------------------------------------------------------------------- /lib/CrystalBuild/Installer/Shards.pm: -------------------------------------------------------------------------------- 1 | package CrystalBuild::Installer::Shards; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | use File::Copy qw/copy/; # >= 5.002 7 | use File::Path qw/mkpath/; # >= 5.001 8 | use File::Spec; # >= 5.00405 9 | 10 | use CrystalBuild::Utils; 11 | use CrystalBuild::Resolver::Shards; 12 | use CrystalBuild::Downloader::Shards; 13 | use CrystalBuild::Builder::Shards; 14 | 15 | sub new { 16 | my ($class, %opt) = @_; 17 | bless { %opt } => $class; 18 | } 19 | 20 | sub fetcher { shift->{fetcher} } 21 | sub remote_cache_url { shift->{remote_cache_url} } 22 | sub cache_dir { shift->{cache_dir} } 23 | sub without_release { shift->{without_release} } 24 | 25 | sub install { 26 | my ($self, $crystal_version, $crystal_dir) = @_; 27 | my $shards_install_path = $self->_shards_install_path($crystal_dir); 28 | 29 | eval { 30 | print "Checking if Shards already exists ... "; 31 | return print "ok\n" if -f $shards_install_path; 32 | print "ng\n"; 33 | 34 | print "Resolving Shards download URL ... "; 35 | my $tarball_url = $self->_resolve($crystal_version); 36 | print "ok\n"; 37 | 38 | print "Downloading Shards tarball ...\n"; 39 | print "$tarball_url\n"; 40 | my $target_dir = $self->_download($tarball_url, $crystal_version); 41 | print "ok\n"; 42 | 43 | print "Building Shards ... "; 44 | my $shards_bin = $self->_build($target_dir, $crystal_dir); 45 | print "ok\n"; 46 | 47 | print "Copying Shards binary ... "; 48 | $self->_copy($shards_bin, $shards_install_path); 49 | print "ok\n"; 50 | }; 51 | 52 | CrystalBuild::Utils::error_and_exit($@) if $@; 53 | } 54 | 55 | sub _resolve { 56 | my ($self, $crystal_version) = @_; 57 | return CrystalBuild::Resolver::Shards->new( 58 | fetcher => $self->fetcher, 59 | shards_releases_url => $self->remote_cache_url, 60 | )->resolve($crystal_version); 61 | } 62 | 63 | sub _download { 64 | my ($self, $tarball_url, $crystal_version) = @_; 65 | my $cache_dir = $self->cache_dir.'/'.$crystal_version; 66 | return CrystalBuild::Downloader::Shards->new( 67 | fetcher => $self->fetcher, 68 | )->download($tarball_url, $cache_dir); 69 | } 70 | 71 | sub _build { 72 | my ($self, $target_dir, $crystal_dir) = @_; 73 | return CrystalBuild::Builder::Shards->new( 74 | without_release => $self->without_release, 75 | )->build($target_dir, $crystal_dir); 76 | } 77 | 78 | sub _copy { 79 | my ($self, $shards_bin, $shards_install_path) = @_; 80 | 81 | copy($shards_bin, $shards_install_path) 82 | or die 'shards binary copy failed: '.$shards_install_path; 83 | 84 | chmod 0755, $shards_install_path; 85 | } 86 | 87 | sub _shards_install_path { 88 | my ($self, $crystal_dir) = @_; 89 | return File::Spec->catfile($crystal_dir, 'bin', 'shards'); 90 | } 91 | 92 | 1; 93 | -------------------------------------------------------------------------------- /lib/CrystalBuild/Resolver/Crystal.pm: -------------------------------------------------------------------------------- 1 | package CrystalBuild::Resolver::Crystal; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use feature qw/say/; 6 | 7 | use CrystalBuild::Utils; 8 | use CrystalBuild::GitHub; 9 | use CrystalBuild::Resolver::Utils; 10 | use CrystalBuild::Resolver::Crystal::GitHub; 11 | use CrystalBuild::Resolver::Crystal::RemoteCache; 12 | 13 | sub new { 14 | my ($class, %opt) = @_; 15 | my $self = bless { } => $class; 16 | 17 | $self->{fetcher} = $opt{fetcher}; 18 | $self->{github_repository} = $opt{github_repository}; 19 | $self->{remote_cache_url} = $opt{remote_cache_url}; 20 | $self->{use_remote_cache} = $opt{use_remote_cache}; 21 | $self->{use_github} = $opt{use_github}; 22 | $self->{resolvers} = $self->_create_enable_resolvers; 23 | 24 | return $self; 25 | } 26 | 27 | sub resolve { 28 | my ($self, $version, @system_info) = @_; 29 | 30 | for my $resolver (@{ $self->{resolvers} }) { 31 | print 'Resolving Crystal download URL by '.$resolver->name.' ... '; 32 | my $download_url = $resolver->resolve($version, @system_info); 33 | 34 | if (defined $download_url) { 35 | say 'ok'; 36 | return $download_url; 37 | } 38 | 39 | say 'ng'; 40 | } 41 | 42 | die "Error: Version not found\n"; 43 | } 44 | 45 | sub resolve_by_version { 46 | my ($self, $version) = @_; 47 | my @system_info = CrystalBuild::Utils::system_info(); 48 | return $self->resolve($version, @system_info); 49 | } 50 | 51 | sub versions { 52 | my $self = shift; 53 | 54 | for my $resolver (@{ $self->{resolvers} }) { 55 | my $versions = eval { $resolver->versions }; 56 | return $versions if !$@ && @$versions > 0; 57 | 58 | say $@ if $@; 59 | } 60 | 61 | die "faild to fetch Crystal versions list\n"; 62 | } 63 | 64 | sub _create_enable_resolvers { 65 | my $self = shift; 66 | return [ 67 | $self->{use_remote_cache} ? $self->_create_remote_cache_resolver : (), 68 | $self->{use_github} ? $self->_create_github_resolver : (), 69 | ]; 70 | } 71 | 72 | sub _create_remote_cache_resolver { 73 | my $self = shift; 74 | return CrystalBuild::Resolver::Crystal::RemoteCache->new( 75 | fetcher => $self->{fetcher}, 76 | cache_url => $self->{remote_cache_url}, 77 | ); 78 | } 79 | 80 | sub _create_github_resolver { 81 | my $self = shift; 82 | return CrystalBuild::Resolver::Crystal::GitHub->new( 83 | github => CrystalBuild::GitHub->new( 84 | fetcher => $self->{fetcher}, 85 | github_repo => $self->{github_repository}, 86 | ), 87 | ); 88 | } 89 | 90 | 1; 91 | -------------------------------------------------------------------------------- /lib/CrystalBuild/Resolver/Crystal/GitHub.pm: -------------------------------------------------------------------------------- 1 | package CrystalBuild::Resolver::Crystal::GitHub; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | sub new { 7 | my ($class, %opt) = @_; 8 | return bless { %opt } => $class; 9 | } 10 | 11 | sub name { 'GitHub' } 12 | 13 | sub resolve { 14 | my ($self, $version, $platform, $arch) = @_; 15 | 16 | my $release = $self->github->fetch_release($version); 17 | my $download_urls = $self->_find_binary_download_urls($release->{assets}); 18 | 19 | return $download_urls->{"$platform-$arch"}; 20 | } 21 | 22 | sub versions { 23 | my $self = shift; 24 | 25 | my $releases = $self->github->fetch_releases; 26 | my @tags = map { $_->{tag_name} } @$releases; 27 | 28 | return \@tags; 29 | } 30 | 31 | sub github { shift->{github} } 32 | 33 | sub _find_binary_download_urls { 34 | my ($self, $assets) = @_; 35 | 36 | my ($linux_x64) = grep { $_->{name} =~ /linux.*64/ } @$assets; 37 | my ($linux_x86) = grep { $_->{name} =~ /linux.*i686/ } @$assets; 38 | my ($darwin_64) = grep { $_->{name} =~ /darwin/ } @$assets; 39 | my ($freebsd_x64) = grep { $_->{name} =~ /freebsd/ } @$assets; 40 | 41 | my $download_urls = {}; 42 | $download_urls->{'linux-x64'} = $linux_x64->{browser_download_url} if defined $linux_x64; 43 | $download_urls->{'linux-x86'} = $linux_x86->{browser_download_url} if defined $linux_x86; 44 | $download_urls->{'darwin-x64'} = $darwin_64->{browser_download_url} if defined $darwin_64; 45 | $download_urls->{'freebsd-x64'} = $freebsd_x64->{browser_download_url} if defined $freebsd_x64; 46 | 47 | return $download_urls; 48 | } 49 | 50 | 1; 51 | -------------------------------------------------------------------------------- /lib/CrystalBuild/Resolver/Crystal/RemoteCache.pm: -------------------------------------------------------------------------------- 1 | package CrystalBuild::Resolver::Crystal::RemoteCache; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | use JSON::PP; 7 | 8 | sub new { 9 | my ($class, %opt) = @_; 10 | return bless { %opt } => $class; 11 | } 12 | 13 | sub name { 'Remote Cache' } 14 | 15 | sub resolve { 16 | my ($self, $version, $platform, $arch, $os_version) = @_; 17 | 18 | my ($release) = grep { $_->{tag_name} eq $version } @{ $self->_fetch }; 19 | return unless defined $release; 20 | 21 | if (defined $os_version) { 22 | my $key = "$platform-$arch-$os_version"; 23 | return $release->{assets}->{$key} if defined $release->{assets}->{$key}; 24 | } 25 | 26 | return $release->{assets}->{"$platform-$arch"}; 27 | } 28 | 29 | sub versions { 30 | my $self = shift; 31 | 32 | my @versions = map { $_->{tag_name} } @{ $self->_fetch }; 33 | return \@versions; 34 | } 35 | 36 | sub _fetch { 37 | my $self = shift; 38 | my $response = $self->fetcher->fetch($self->cache_url . '?' . time); 39 | return decode_json($response); 40 | } 41 | 42 | sub fetcher { shift->{fetcher} } 43 | sub cache_url { shift->{cache_url} } 44 | 45 | 1; 46 | -------------------------------------------------------------------------------- /lib/CrystalBuild/Resolver/Shards.pm: -------------------------------------------------------------------------------- 1 | package CrystalBuild::Resolver::Shards; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | use JSON::PP qw/decode_json/; 7 | 8 | sub new { 9 | my ($class, %opt) = @_; 10 | return bless { %opt } => $class; 11 | } 12 | 13 | sub fetcher { shift->{fetcher} } 14 | sub shards_releases_url { shift->{shards_releases_url} } 15 | 16 | sub resolve { 17 | my ($self, $crystal_version) = @_; 18 | 19 | my $shards_releases = $self->_fetch; 20 | return unless ref($shards_releases) eq 'HASH'; 21 | 22 | if (defined $shards_releases->{$crystal_version}) { 23 | my $tarball_url = $shards_releases->{$crystal_version}; 24 | return $tarball_url unless ref $tarball_url; 25 | } 26 | 27 | return $shards_releases->{default} if defined $shards_releases->{default}; 28 | return undef; 29 | } 30 | 31 | sub _fetch { 32 | my $self = shift; 33 | my $response = $self->fetcher->fetch($self->shards_releases_url); 34 | return decode_json($response); 35 | } 36 | 37 | 1; 38 | -------------------------------------------------------------------------------- /lib/CrystalBuild/Resolver/Utils.pm: -------------------------------------------------------------------------------- 1 | package CrystalBuild::Resolver::Utils; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | use SemVer::V2::Strict; 7 | 8 | sub sort_version { 9 | my ($self, $versions) = @_; 10 | return [ sort { 11 | SemVer::V2::Strict->new($a) <=> SemVer::V2::Strict->new($b) 12 | } @$versions ]; 13 | } 14 | 15 | sub normalize_version { 16 | my ($self, $v) = @_; 17 | 18 | die 'version is required' unless $v; 19 | 20 | return $v if $v =~ m/^\d+\.?(\d+|x)?\.?(\d+|x)?$/; 21 | return do { $v =~ s/v//; $v } if $v =~ m/^v\d+\.?(\d+|x)?\.?(\d+|x)?$/; 22 | return $v; 23 | } 24 | 25 | 1; 26 | -------------------------------------------------------------------------------- /lib/CrystalBuild/Sense.pm: -------------------------------------------------------------------------------- 1 | package CrystalBuild::Sense; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | use feature (); 6 | 7 | sub import { 8 | import strict; 9 | import warnings; 10 | import utf8; 11 | import feature qw/say state/; 12 | } 13 | 14 | 1; 15 | -------------------------------------------------------------------------------- /lib/CrystalBuild/Utils.pm: -------------------------------------------------------------------------------- 1 | package CrystalBuild::Utils; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | use POSIX; 7 | use File::Copy; 8 | use Getopt::Long qw/:config posix_default no_ignore_case gnu_compat/; 9 | 10 | use SemVer::V2::Strict; 11 | use Mac::OSVersion::Lite; 12 | 13 | sub system_info { 14 | my $arch; 15 | my $version; 16 | my ($sysname, $machine) = (POSIX::uname)[0, 4]; 17 | 18 | # Linux/Darwin --- x86_64 19 | # FreeBSD --- amd64 20 | if ($machine =~ m/x86_64|amd64/) { 21 | $arch = 'x64'; 22 | } elsif ($machine =~ m/i\d86/) { 23 | $arch = 'x86'; 24 | } else { 25 | die "Error: $sysname $machine is not supported." 26 | } 27 | 28 | if ($sysname =~ m/\ADarwin/i) { 29 | eval { $version = Mac::OSVersion::Lite->new->name }; 30 | } 31 | 32 | return (lc $sysname, $arch, $version); 33 | } 34 | 35 | sub extract_tar { 36 | my ($filepath, $outdir) = @_; 37 | 38 | my $cwd = getcwd; 39 | chdir($outdir); 40 | 41 | eval { 42 | require Archive::Tar; 43 | my $tar = Archive::Tar->new; 44 | $tar->read($filepath); 45 | $tar->extract; 46 | }; 47 | 48 | if ($@) { 49 | `tar xfz $filepath`; 50 | } 51 | 52 | chdir($cwd); 53 | } 54 | 55 | sub parse_args { 56 | my ($version, $prefix, $without_release) = @_[-3,-2,-1]; 57 | my $definitions = 0; 58 | my $cache = 1; 59 | 60 | GetOptions( 61 | definitions => \$definitions, 62 | 'cache!' => \$cache, 63 | ); 64 | 65 | return { 66 | version => $version, 67 | prefix => $prefix, 68 | definitions => $definitions, 69 | cache => $cache, 70 | without_release => $without_release, 71 | }; 72 | } 73 | 74 | sub error_and_exit { 75 | my $msg = shift; 76 | 77 | print "$msg\n"; 78 | exit 1; 79 | } 80 | 81 | sub copy_force { 82 | my ($src_path, $dest_path) = @_; 83 | 84 | unlink $dest_path if -f $dest_path; 85 | copy $src_path, $dest_path; 86 | } 87 | 88 | 1; 89 | -------------------------------------------------------------------------------- /script/dev/update_vendor_modules.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -xe 4 | 5 | cd `git rev-parse --show-toplevel` 6 | 7 | LOCAL_LIB=local/lib/perl5 8 | VENDOR_LIB=vendor/lib 9 | 10 | rm -rf $VENDOR_LIB 11 | mkdir -p $VENDOR_LIB 12 | 13 | # parent 14 | cp -rf $LOCAL_LIB/parent.pm $VENDOR_LIB/parent.pm 15 | 16 | # Class::Accessor::Lite 17 | mkdir -p $VENDOR_LIB/Class/Accessor 18 | cp -rf $LOCAL_LIB/Class/Accessor/Lite.pm $VENDOR_LIB/Class/Accessor/Lite.pm 19 | 20 | # File::Which 21 | mkdir -p $VENDOR_LIB/File 22 | cp -rf $LOCAL_LIB/File/Which.pm $VENDOR_LIB/File/Which.pm 23 | 24 | # HTTP::Command::Wrapper 25 | mkdir -p $VENDOR_LIB/HTTP/Command 26 | cp -rf $LOCAL_LIB/HTTP/Command/Wrapper $VENDOR_LIB/HTTP/Command/Wrapper 27 | cp -rf $LOCAL_LIB/HTTP/Command/Wrapper.pm $VENDOR_LIB/HTTP/Command/Wrapper.pm 28 | 29 | # JSON::PP 30 | mkdir -p $VENDOR_LIB/JSON 31 | cp -rf $LOCAL_LIB/JSON/PP $VENDOR_LIB/JSON/PP 32 | cp -rf $LOCAL_LIB/JSON/PP.pm $VENDOR_LIB/JSON/PP.pm 33 | 34 | # Mac::OSVersion::Lite 35 | mkdir -p $VENDOR_LIB/Mac/OSVersion 36 | cp -rf $LOCAL_LIB/Mac/OSVersion/Lite.pm $VENDOR_LIB/Mac/OSVersion/Lite.pm 37 | 38 | # SemVer::V2::Strict 39 | mkdir -p $VENDOR_LIB/SemVer/V2 40 | cp -rf $LOCAL_LIB/SemVer/V2/Strict.pm $VENDOR_LIB/SemVer/V2/Strict.pm 41 | 42 | # Text::Camel 43 | mkdir -p $VENDOR_LIB/Text 44 | cp -rf $LOCAL_LIB/Text/Caml.pm $VENDOR_LIB/Text/Caml.pm 45 | -------------------------------------------------------------------------------- /share/crystal-build/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crenv/crystal-build/abe65ce45e47ca1e6d255b6252304802e2f4f8fc/share/crystal-build/.gitkeep -------------------------------------------------------------------------------- /share/crystal-build/shards.json: -------------------------------------------------------------------------------- 1 | { 2 | "default": "https://github.com/crystal-lang/shards/archive/master.tar.gz", 3 | "0.35.1": "https://github.com/crystal-lang/shards/archive/v0.11.1.tar.gz", 4 | "0.35.0": "https://github.com/crystal-lang/shards/archive/v0.11.1.tar.gz", 5 | "0.34.0": "https://github.com/crystal-lang/shards/archive/v0.10.0.tar.gz", 6 | "0.33.0": "https://github.com/crystal-lang/shards/archive/v0.9.0.tar.gz", 7 | "0.32.1": "https://github.com/crystal-lang/shards/archive/v0.9.0.tar.gz", 8 | "0.32.0": "https://github.com/crystal-lang/shards/archive/v0.9.0.tar.gz", 9 | "0.31.1": "https://github.com/crystal-lang/shards/archive/v0.9.0.tar.gz", 10 | "0.31.0": "https://github.com/crystal-lang/shards/archive/v0.9.0.tar.gz", 11 | "0.30.1": "https://github.com/crystal-lang/shards/archive/v0.9.0.tar.gz", 12 | "0.30.0": "https://github.com/crystal-lang/shards/archive/v0.9.0.tar.gz", 13 | "0.29.0": "https://github.com/crystal-lang/shards/archive/v0.9.0.tar.gz", 14 | "0.28.0": "https://github.com/crystal-lang/shards/archive/v0.8.1.tar.gz", 15 | "0.27.2": "https://github.com/crystal-lang/shards/archive/v0.8.1.tar.gz", 16 | "0.27.1": "https://github.com/crystal-lang/shards/archive/v0.8.1.tar.gz", 17 | "0.27.0": "https://github.com/crystal-lang/shards/archive/v0.8.1.tar.gz", 18 | "0.26.1": "https://github.com/crystal-lang/shards/archive/v0.8.1.tar.gz", 19 | "0.26.0": "https://github.com/crystal-lang/shards/archive/v0.8.1.tar.gz", 20 | "0.25.1": "https://github.com/crystal-lang/shards/archive/v0.8.1.tar.gz", 21 | "0.23.1": "https://github.com/crystal-lang/shards/archive/v0.7.1.tar.gz", 22 | "0.23.0": "https://github.com/crystal-lang/shards/archive/v0.7.1.tar.gz", 23 | "0.22.0": "https://github.com/crystal-lang/shards/archive/v0.7.1.tar.gz", 24 | "0.21.1": "https://github.com/crystal-lang/shards/archive/v0.7.1.tar.gz", 25 | "0.21.0": "https://github.com/crystal-lang/shards/archive/v0.7.1.tar.gz", 26 | "0.20.5": "https://github.com/crystal-lang/shards/archive/v0.7.1.tar.gz", 27 | "0.20.4": "https://github.com/crystal-lang/shards/archive/v0.7.1.tar.gz", 28 | "0.20.3": "https://github.com/crystal-lang/shards/archive/v0.7.1.tar.gz", 29 | "0.20.1": "https://github.com/crystal-lang/shards/archive/v0.7.1.tar.gz", 30 | "0.20.0": "https://github.com/crystal-lang/shards/archive/v0.7.0.tar.gz", 31 | "0.19.4": "https://github.com/crystal-lang/shards/archive/v0.6.4.tar.gz", 32 | "0.19.3": "https://github.com/crystal-lang/shards/archive/v0.6.4.tar.gz", 33 | "0.19.2": "https://github.com/crystal-lang/shards/archive/v0.6.4.tar.gz", 34 | "0.19.1": "https://github.com/crystal-lang/shards/archive/v0.6.4.tar.gz", 35 | "0.19.0": "https://github.com/crystal-lang/shards/archive/v0.6.4.tar.gz", 36 | "0.18.7": "https://github.com/crystal-lang/shards/archive/v0.6.3.tar.gz", 37 | "0.18.6": "https://github.com/crystal-lang/shards/archive/v0.6.3.tar.gz", 38 | "0.18.4": "https://github.com/crystal-lang/shards/archive/v0.6.3.tar.gz", 39 | "0.18.3": "https://github.com/crystal-lang/shards/archive/v0.6.3.tar.gz", 40 | "0.18.2": "https://github.com/crystal-lang/shards/archive/v0.6.3.tar.gz", 41 | "0.18.1": "https://github.com/crystal-lang/shards/archive/v0.6.3.tar.gz", 42 | "0.18.0": "https://github.com/crystal-lang/shards/archive/v0.6.3.tar.gz", 43 | "0.17.4": "https://github.com/crystal-lang/shards/archive/v0.6.3.tar.gz", 44 | "0.17.3": "https://github.com/crystal-lang/shards/archive/v0.6.3.tar.gz", 45 | "0.17.2": "https://github.com/crystal-lang/shards/archive/v0.6.3.tar.gz", 46 | "0.17.1": "https://github.com/crystal-lang/shards/archive/v0.6.3.tar.gz", 47 | "0.17.0": "https://github.com/crystal-lang/shards/archive/v0.6.3.tar.gz", 48 | "0.16.0": "https://github.com/crystal-lang/shards/archive/v0.6.3.tar.gz", 49 | "0.15.0": "https://github.com/crystal-lang/shards/archive/v0.6.2.tar.gz", 50 | "0.14.2": "https://github.com/crystal-lang/shards/archive/v0.6.2.tar.gz", 51 | "0.14.1": "https://github.com/crystal-lang/shards/archive/v0.6.2.tar.gz", 52 | "0.14.0": "https://github.com/crystal-lang/shards/archive/v0.6.2.tar.gz", 53 | "0.13.0": "https://github.com/crystal-lang/shards/archive/v0.6.2.tar.gz", 54 | "0.12.0": "https://github.com/crystal-lang/shards/archive/v0.6.1.tar.gz", 55 | "0.11.1": "https://github.com/crystal-lang/shards/archive/v0.6.0.tar.gz", 56 | "0.11.0": "https://github.com/crystal-lang/shards/archive/v0.6.0.tar.gz", 57 | "0.10.2": "https://github.com/crystal-lang/shards/archive/v0.5.4.tar.gz", 58 | "0.10.1": "https://github.com/crystal-lang/shards/archive/v0.5.4.tar.gz", 59 | "0.10.0": "https://github.com/crystal-lang/shards/archive/v0.5.4.tar.gz", 60 | "0.9.1": "https://github.com/crystal-lang/shards/archive/v0.5.3.tar.gz", 61 | "0.9.0": "https://github.com/crystal-lang/shards/archive/v0.5.3.tar.gz", 62 | "0.8.0": "https://github.com/crystal-lang/shards/archive/v0.5.1.tar.gz", 63 | "0.7.7": "https://github.com/crystal-lang/shards/archive/v0.4.0.tar.gz" 64 | } 65 | -------------------------------------------------------------------------------- /t/00_compile.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use t::Util; 6 | use Module::Find; 7 | 8 | subtest basic => sub { 9 | lives_ok { useall 'CrystalBuild' }; 10 | }; 11 | 12 | done_testing; 13 | -------------------------------------------------------------------------------- /t/Util.pm: -------------------------------------------------------------------------------- 1 | package t::Util; 2 | 3 | use strict; 4 | use warnings; 5 | use utf8; 6 | use feature qw/state/; 7 | 8 | use File::Basename qw/dirname/; 9 | use lib ( 10 | dirname(__FILE__)."/../vendor/lib", 11 | dirname(__FILE__)."/../lib" 12 | ); 13 | 14 | use Exporter 'import'; 15 | use Data::Dumper; 16 | use Cwd qw/abs_path/; 17 | 18 | use Test::More; 19 | use Test::Deep; 20 | use Test::Deep::Matcher; 21 | use Test::Exception; 22 | use Test::Mock::Guard; 23 | 24 | sub create_crenv { 25 | my (%opt) = @_; 26 | 27 | require CrystalBuild; 28 | require HTTP::Command::Wrapper; 29 | 30 | $opt{fetcher} ||= HTTP::Command::Wrapper->create('wget'); 31 | $opt{github_repo} ||= 'author/repo'; 32 | $opt{prefix} ||= 't/tmp/.crenv/versions/0.7.7'; 33 | $opt{cache} ||= 1; 34 | $opt{cache_dir} ||= abs_path('t/tmp/.crenv/cache'); 35 | $opt{cache_url} ||= 'http://example.com/releases'; 36 | 37 | setup_dirs(); 38 | CrystalBuild->new(%opt); 39 | } 40 | 41 | sub setup_dirs { 42 | require File::Path; 43 | import File::Path qw/rmtree mkpath/; 44 | 45 | rmtree('t/tmp') if -d 't/tmp'; 46 | mkpath('t/tmp/.crenv'); 47 | } 48 | 49 | sub create_server { 50 | state $server; 51 | return $server if defined $server; 52 | 53 | require Test::TCP; 54 | require Plack::Loader; 55 | require Plack::Middleware::Static; 56 | 57 | my $app = sub { 58 | my $env = shift; 59 | 60 | Plack::Middleware::Static->new({ 61 | path => sub { 1 }, 62 | root => 't/data/', 63 | })->call($env); 64 | }; 65 | 66 | $server = Test::TCP->new( 67 | code => sub { 68 | my $port = shift; 69 | my $server = Plack::Loader->auto( 70 | port => $port, 71 | host => '127.0.0.1', 72 | ); 73 | $server->run($app); 74 | }, 75 | ); 76 | 77 | return $server; 78 | } 79 | 80 | sub uri_for { 81 | my $path = shift; 82 | my $port = create_server()->port; 83 | 84 | return "http://127.0.0.1:$port/$path"; 85 | } 86 | 87 | our @EXPORT = ( 88 | qw/create_crenv create_server setup_dirs uri_for/, 89 | 90 | @Data::Dumper::EXPORT, 91 | 92 | @Test::More::EXPORT, 93 | @Test::Deep::EXPORT, 94 | @Test::Deep::Matcher::EXPORT, 95 | @Test::Exception::EXPORT, 96 | @Test::Mock::Guard::EXPORT, 97 | ); 98 | 99 | 1; 100 | -------------------------------------------------------------------------------- /t/bin/curl: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo $* 4 | -------------------------------------------------------------------------------- /t/bin/wget: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo $* 4 | -------------------------------------------------------------------------------- /t/crystal_build/builder/shards/_run_script.t: -------------------------------------------------------------------------------- 1 | use CrystalBuild::Sense; 2 | 3 | use t::Util; 4 | use CrystalBuild::Builder::Shards; 5 | 6 | subtest basic => sub { 7 | my $builder = CrystalBuild::Builder::Shards->new; 8 | 9 | ok $builder->_run_script("#/usr/bin/env bash\nexit 0"); 10 | ok !$builder->_run_script("#/usr/bin/env bash\nexit 1"); 11 | }; 12 | 13 | done_testing; 14 | -------------------------------------------------------------------------------- /t/crystal_build/builder/shards/build.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | my $dummy_system;; 6 | use Test::Mock::Cmd system => sub { &$dummy_system(@_) }; 7 | use Test::Mock::Guard qw/mock_guard/; 8 | 9 | use t::Util; 10 | use CrystalBuild::Builder::Shards; 11 | 12 | use constant TARGET_DIR => '__TARGET_DIR__'; 13 | use constant CRYSTAL_DIR => '__CRYSTAL_DIR__'; 14 | 15 | subtest basic => sub { 16 | my $builder = CrystalBuild::Builder::Shards->new; 17 | 18 | my $guard = mock_guard('CrystalBuild::Builder::Shards', { 19 | abs_path => sub { $_[0] }, 20 | tempfile => '/tmp/tempfile', 21 | _create_build_script => sub { 22 | is $_[1], TARGET_DIR; 23 | is $_[2], CRYSTAL_DIR; 24 | return '#!/usr/bin/env bash'; 25 | }, 26 | _run_script => 1, 27 | }); 28 | 29 | is $builder->build(TARGET_DIR, CRYSTAL_DIR), TARGET_DIR.'/bin/shards'; 30 | 31 | is $guard->call_count('CrystalBuild::Builder::Shards', '_create_build_script'), 1; 32 | is $guard->call_count('CrystalBuild::Builder::Shards', '_run_script'), 1; 33 | }; 34 | 35 | done_testing; 36 | -------------------------------------------------------------------------------- /t/crystal_build/builder/shards/new.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use Test::MockObject; 6 | 7 | use t::Util; 8 | 9 | use CrystalBuild::Builder::Shards; 10 | 11 | subtest basic => sub { 12 | my $without_release = Test::MockObject->new; 13 | 14 | my $self = CrystalBuild::Builder::Shards->new( 15 | without_release => $without_release, 16 | ); 17 | 18 | isa_ok $self, 'CrystalBuild::Builder::Shards'; 19 | 20 | is $self->without_release, $without_release; 21 | }; 22 | 23 | done_testing; 24 | -------------------------------------------------------------------------------- /t/crystal_build/downloader/crystal/_detect_extracted_dirs.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings FATAL => 'all'; 3 | use utf8; 4 | 5 | use File::Path qw/mkpath/; 6 | use File::Touch; 7 | 8 | use t::Util; 9 | use CrystalBuild::Downloader::Crystal; 10 | 11 | setup_dirs; 12 | 13 | subtest basic => sub { 14 | # ----- setup ----------------------------------------- 15 | mkpath 't/tmp/.crenv/cache'; 16 | 17 | touch 't/tmp/.crenv/cache/crystal-file'; 18 | mkpath 't/tmp/.crenv/cache/crystal-dir'; 19 | 20 | ok -f 't/tmp/.crenv/cache/crystal-file'; 21 | ok -d 't/tmp/.crenv/cache/crystal-dir'; 22 | 23 | 24 | # ----- assert ---------------------------------------- 25 | my $self = bless {} => 'CrystalBuild::Downloader::Crystal'; 26 | cmp_deeply 27 | [ $self->_detect_extracted_dirs('t/tmp/.crenv/cache') ], 28 | ['t/tmp/.crenv/cache/crystal-dir']; 29 | }; 30 | 31 | done_testing; 32 | -------------------------------------------------------------------------------- /t/crystal_build/downloader/crystal/_detect_filename.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings FATAL => 'all'; 3 | use utf8; 4 | 5 | use t::Util; 6 | use CrystalBuild::Downloader::Crystal; 7 | 8 | use constant VALID_URL => 9 | 'https://github.com/crystal-lang/crystal/releases/download/0.12.0/crystal-0.12.0-1-darwin-x86_64.tar.gz'; 10 | 11 | subtest succeeded => sub { 12 | my $self = bless {} => 'CrystalBuild::Downloader::Crystal'; 13 | is $self->_detect_filename(VALID_URL), 'crystal-0.12.0-1-darwin-x86_64.tar.gz'; 14 | }; 15 | 16 | subtest failed => sub { 17 | my $self = bless {} => 'CrystalBuild::Downloader::Crystal'; 18 | 19 | subtest '# undef' => sub { 20 | dies_ok { $self->_detect_filename }; 21 | }; 22 | 23 | subtest '# invalid URL' => sub { 24 | dies_ok { $self->_detect_filename('http://www.example.com/') }; 25 | }; 26 | }; 27 | 28 | done_testing; 29 | -------------------------------------------------------------------------------- /t/crystal_build/downloader/crystal/new.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings FATAL => 'all'; 3 | use utf8; 4 | 5 | use t::Util; 6 | use CrystalBuild::Downloader::Crystal; 7 | 8 | subtest basic => sub { 9 | my $self = CrystalBuild::Downloader::Crystal->new(fetcher => '__FETCHER__'); 10 | 11 | isa_ok $self, 'CrystalBuild::Downloader::Crystal'; 12 | is $self->{fetcher}, '__FETCHER__'; 13 | }; 14 | 15 | done_testing; 16 | -------------------------------------------------------------------------------- /t/crystal_build/downloader/shards/_detect_extracted_dirs.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings FATAL => 'all'; 3 | use utf8; 4 | 5 | use File::Path qw/mkpath/; 6 | use File::Touch; 7 | 8 | use t::Util; 9 | use CrystalBuild::Downloader::Shards; 10 | 11 | setup_dirs; 12 | 13 | subtest basic => sub { 14 | # ----- setup ----------------------------------------- 15 | mkpath 't/tmp/.crenv/cache'; 16 | 17 | touch 't/tmp/.crenv/cache/shards-file'; 18 | mkpath 't/tmp/.crenv/cache/shards-dir'; 19 | 20 | ok -f 't/tmp/.crenv/cache/shards-file'; 21 | ok -d 't/tmp/.crenv/cache/shards-dir'; 22 | 23 | 24 | # ----- assert ---------------------------------------- 25 | my $self = bless {} => 'CrystalBuild::Downloader::Shards'; 26 | cmp_deeply 27 | [ $self->_detect_extracted_dirs('t/tmp/.crenv/cache') ], 28 | ['t/tmp/.crenv/cache/shards-dir']; 29 | }; 30 | 31 | done_testing; 32 | -------------------------------------------------------------------------------- /t/crystal_build/downloader/shards/_detect_filename.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings FATAL => 'all'; 3 | use utf8; 4 | 5 | use t::Util; 6 | use CrystalBuild::Downloader::Shards; 7 | 8 | use constant VALID_URL => 9 | 'https://github.com/ysbaddaden/shards/archive/v0.5.3.tar.gz'; 10 | 11 | subtest succeeded => sub { 12 | my $self = bless {} => 'CrystalBuild::Downloader::Shards'; 13 | is $self->_detect_filename(VALID_URL), 'shards-v0.5.3.tar.gz'; 14 | }; 15 | 16 | subtest failed => sub { 17 | my $self = bless {} => 'CrystalBuild::Downloader::Shards'; 18 | 19 | subtest '# undef' => sub { 20 | dies_ok { $self->_detect_filename }; 21 | }; 22 | 23 | subtest '# invalid URL' => sub { 24 | dies_ok { $self->_detect_filename('http://www.example.com/') }; 25 | }; 26 | }; 27 | 28 | done_testing; 29 | -------------------------------------------------------------------------------- /t/crystal_build/downloader/shards/new.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use t::Util; 6 | use CrystalBuild::Downloader::Shards; 7 | 8 | subtest basic => sub { 9 | my $self = CrystalBuild::Downloader::Shards->new(fetcher => '__FETCHER__'); 10 | 11 | isa_ok $self, 'CrystalBuild::Downloader::Shards'; 12 | is $self->{fetcher}, '__FETCHER__'; 13 | }; 14 | 15 | done_testing; 16 | -------------------------------------------------------------------------------- /t/crystal_build/downloader/tarball/_detect_extracted_dirs.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings FATAL => 'all'; 3 | use utf8; 4 | 5 | use t::Util; 6 | use CrystalBuild::Downloader::Tarball; 7 | 8 | subtest basic => sub { 9 | my $self = bless {} => 'CrystalBuild::Downloader::Tarball'; 10 | throws_ok sub { $self->_detect_extracted_dirs }, qr/\Aabstract method\n\z/; 11 | }; 12 | 13 | done_testing; 14 | -------------------------------------------------------------------------------- /t/crystal_build/downloader/tarball/_detect_filename.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings FATAL => 'all'; 3 | use utf8; 4 | 5 | use t::Util; 6 | use CrystalBuild::Downloader::Tarball; 7 | 8 | subtest basic => sub { 9 | my $self = bless {} => 'CrystalBuild::Downloader::Tarball'; 10 | throws_ok sub { $self->_detect_filename }, qr/\Aabstract method\n\z/; 11 | }; 12 | 13 | done_testing; 14 | -------------------------------------------------------------------------------- /t/crystal_build/downloader/tarball/download.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use Test::MockObject; 6 | use Test::Mock::Guard qw/mock_guard/; 7 | use File::Path qw/mkpath rmtree/; 8 | 9 | use t::Util; 10 | use CrystalBuild::Downloader::Shards; 11 | 12 | subtest basic => sub { 13 | # mocking 14 | my $fetcher = Test::MockObject->new; 15 | $fetcher->mock(download => sub { 16 | my ($self, $tarball_url, $tarball_path) = @_; 17 | 18 | is $self, $fetcher; 19 | is $tarball_url, 'https://www.example.com/example.tar.gz'; 20 | is $tarball_path, 't/tmp/test.tar.gz'; 21 | }); 22 | 23 | my $guard_self = mock_guard('CrystalBuild::Downloader::Shards', { 24 | _detect_filename => sub { 25 | my ($self, $url) = @_; 26 | is $url, 'https://www.example.com/example.tar.gz'; 27 | return 'test.tar.gz'; 28 | }, 29 | }); 30 | 31 | my $guard_utils = mock_guard('CrystalBuild::Utils', { 32 | error_and_exit => sub { }, 33 | extract_tar => sub { 34 | my ($tarball_path, $cache_dir) = @_; 35 | 36 | is $tarball_path, 't/tmp/test.tar.gz'; 37 | is $cache_dir, 't/tmp/'; 38 | 39 | mkpath 't/tmp/shards-v0.1.0/'; 40 | }, 41 | }); 42 | 43 | # before 44 | ok !-d 't/tmp/shards-v0.1.0'; 45 | 46 | # test 47 | my $self = CrystalBuild::Downloader::Shards->new(fetcher => $fetcher); 48 | 49 | my $target_dir = $self->download('https://www.example.com/example.tar.gz', 't/tmp/'); 50 | 51 | # assert 52 | is $target_dir, 't/tmp/shards-v0.1.0'; 53 | ok -d 't/tmp/shards-v0.1.0'; 54 | 55 | ok $fetcher->called('download'); 56 | is $guard_self->call_count('CrystalBuild::Downloader::Shards', '_detect_filename'), 1; 57 | is $guard_utils->call_count('CrystalBuild::Utils', 'extract_tar'), 1; 58 | 59 | # after 60 | rmtree 't/tmp/shards-v0.1.0/'; 61 | ok !-d 't/tmp/shards-v0.1.0'; 62 | }; 63 | 64 | done_testing; 65 | -------------------------------------------------------------------------------- /t/crystal_build/downloader/tarball/new.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use t::Util; 6 | use CrystalBuild::Downloader::Tarball; 7 | 8 | subtest succeeded => sub { 9 | my $self = CrystalBuild::Downloader::Tarball->new(fetcher => '__FETCHER__'); 10 | 11 | isa_ok $self, 'CrystalBuild::Downloader::Tarball'; 12 | is $self->{fetcher}, '__FETCHER__'; 13 | }; 14 | 15 | subtest failed => sub { 16 | throws_ok sub { CrystalBuild::Downloader::Tarball->new }, qr/A fetcher is required/; 17 | }; 18 | 19 | done_testing; 20 | -------------------------------------------------------------------------------- /t/crystal_build/github/base_url.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use t::Util; 6 | 7 | use CrystalBuild::GitHub; 8 | 9 | subtest basic => sub { 10 | my $github = CrystalBuild::GitHub->new(github_repo => 'author/repo'); 11 | is $github->base_url, 'https://api.github.com/repos/author/repo/'; 12 | }; 13 | 14 | done_testing; 15 | -------------------------------------------------------------------------------- /t/crystal_build/github/fetch.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use HTTP::Command::Wrapper; 6 | 7 | use t::Util; 8 | use CrystalBuild::GitHub; 9 | 10 | subtest basic => sub { 11 | my $guard = mock_guard('HTTP::Command::Wrapper::Wget', { 12 | fetch => sub { 13 | my ($self, $url) = @_; 14 | die if $url ne 'http://dummy.url/'; 15 | } 16 | }); 17 | 18 | my $fetcher = HTTP::Command::Wrapper->create('wget'); 19 | my $github = CrystalBuild::GitHub->new(fetcher => $fetcher); 20 | 21 | $github->fetch('http://dummy.url/'); 22 | is $guard->call_count('HTTP::Command::Wrapper::Wget', 'fetch'), 1; 23 | }; 24 | 25 | done_testing; 26 | -------------------------------------------------------------------------------- /t/crystal_build/github/fetch_as_json.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use t::Util; 6 | 7 | use CrystalBuild::GitHub; 8 | 9 | subtest basic => sub { 10 | my $guard = mock_guard('CrystalBuild::GitHub', { 11 | fetch => sub { '{ "status": "ok" }' }, 12 | base_url => sub { 'http://127.0.0.1/' }, 13 | }); 14 | 15 | my $github = CrystalBuild::GitHub->new; 16 | cmp_deeply $github->fetch_as_json('test.json'), { status => "ok" }; 17 | }; 18 | 19 | done_testing; 20 | -------------------------------------------------------------------------------- /t/crystal_build/github/fetch_release.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use t::Util; 6 | use CrystalBuild::GitHub; 7 | 8 | subtest basic => sub { 9 | no warnings 'redefine'; 10 | 11 | local *CrystalBuild::GitHub::fetch = sub { 12 | is $_[1], 'https://api.github.com/repos/author/repo/releases/tags/0.7.4'; 13 | '{ "status": "ok" }'; 14 | }; 15 | 16 | my $github = CrystalBuild::GitHub->new(github_repo => 'author/repo'); 17 | cmp_deeply $github->fetch_release('0.7.4'), { status => 'ok' }; 18 | }; 19 | 20 | done_testing; 21 | -------------------------------------------------------------------------------- /t/crystal_build/github/fetch_releases.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use t::Util; 6 | use CrystalBuild::GitHub; 7 | 8 | subtest basic => sub { 9 | no warnings 'redefine'; 10 | 11 | local *CrystalBuild::GitHub::fetch = sub { 12 | is $_[1], 'https://api.github.com/repos/author/repo/releases?per_page=100'; 13 | '{ "status": "ok" }'; 14 | }; 15 | 16 | my $github = CrystalBuild::GitHub->new(github_repo => 'author/repo'); 17 | cmp_deeply $github->fetch_releases, { status => 'ok' }; 18 | }; 19 | 20 | done_testing; 21 | -------------------------------------------------------------------------------- /t/crystal_build/github/new.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use t::Util; 6 | 7 | use CrystalBuild::GitHub; 8 | 9 | subtest basic => sub { 10 | my $self = CrystalBuild::GitHub->new(test_key => 'test_value'); 11 | 12 | isa_ok $self, 'CrystalBuild::GitHub'; 13 | is $self->{test_key}, 'test_value'; 14 | }; 15 | 16 | done_testing; 17 | -------------------------------------------------------------------------------- /t/crystal_build/homebrew/alive.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings FATAL => 'all'; 3 | use utf8; 4 | 5 | use File::Slurp; 6 | use File::Temp qw/tempdir/; 7 | 8 | use t::Util; 9 | use CrystalBuild::Homebrew; 10 | 11 | subtest basic => sub { 12 | # setup 13 | my $bin_dir = tempdir(); 14 | my $bin_path = File::Spec->catfile($bin_dir, 'brew'); 15 | 16 | write_file($bin_path, ); 17 | chmod 0755, $bin_path; 18 | 19 | # ------------------------------------------------------------------------- 20 | 21 | subtest 'ok: detected' => sub { 22 | local $ENV{PATH} = $bin_dir; 23 | 24 | my $brew = CrystalBuild::Homebrew->new; 25 | ok $brew->alive(); 26 | }; 27 | 28 | subtest 'ok: not detected' => sub { 29 | local $ENV{PATH} = ''; 30 | 31 | my $brew = CrystalBuild::Homebrew->new; 32 | ok !$brew->alive(); 33 | }; 34 | }; 35 | 36 | done_testing; 37 | __DATA__ 38 | #!/bin/bash 39 | 40 | exit 0 41 | -------------------------------------------------------------------------------- /t/crystal_build/homebrew/exists.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings FATAL => 'all'; 3 | use utf8; 4 | 5 | use Capture::Tiny qw/capture/; 6 | use File::Slurp; 7 | use File::Temp qw/tempdir/; 8 | 9 | use t::Util; 10 | use CrystalBuild::Homebrew; 11 | 12 | subtest basic => sub { 13 | # setup 14 | my $bin_dir = tempdir(); 15 | my $bin_path = File::Spec->catfile($bin_dir, 'brew'); 16 | 17 | write_file($bin_path, ); 18 | chmod 0755, $bin_path; 19 | 20 | # ------------------------------------------------------------------------- 21 | 22 | subtest 'ok: exists' => sub { 23 | local $ENV{PATH} = $bin_dir; 24 | my $brew = CrystalBuild::Homebrew->new; 25 | ok $brew->exists('bdw-gc'); 26 | }; 27 | 28 | subtest 'ok: not exists' => sub { 29 | local $ENV{PATH} = $bin_dir; 30 | my $brew = CrystalBuild::Homebrew->new; 31 | ok !$brew->exists('llvm'); 32 | }; 33 | 34 | subtest 'ng: brew not found' => sub { 35 | local $ENV{PATH} = ''; 36 | capture { 37 | my $brew = CrystalBuild::Homebrew->new; 38 | ok !$brew->exists('llvm'); 39 | }; 40 | }; 41 | }; 42 | 43 | done_testing; 44 | __DATA__ 45 | #!/bin/bash 46 | 47 | echo bdw-gc 48 | echo libevent 49 | -------------------------------------------------------------------------------- /t/crystal_build/homebrew/install.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings FATAL => 'all'; 3 | use utf8; 4 | 5 | use Capture::Tiny qw/capture capture_stdout/; 6 | use Data::Section::Simple qw/get_data_section/; 7 | use File::Slurp; 8 | use File::Temp qw/tempdir/; 9 | 10 | use t::Util; 11 | use CrystalBuild::Homebrew; 12 | 13 | subtest basic => sub { 14 | # setup 15 | my $bin_dir = tempdir(); 16 | my $bin_path = File::Spec->catfile($bin_dir, 'brew'); 17 | 18 | # ------------------------------------------------------------------------- 19 | 20 | subtest 'ok: install succeeded' => sub { 21 | local $ENV{PATH} = $bin_dir; 22 | write_file($bin_path, get_data_section('exit_0.sh')); 23 | chmod 0755, $bin_path; 24 | 25 | my $stdout = capture_stdout { 26 | my $brew = CrystalBuild::Homebrew->new; 27 | ok $brew->install('libevent'); 28 | }; 29 | 30 | is $stdout, < sub { 37 | local $ENV{PATH} = $bin_dir; 38 | write_file($bin_path, get_data_section('exit_1.sh')); 39 | chmod 0755, $bin_path; 40 | 41 | my $stdout = capture_stdout { 42 | my $brew = CrystalBuild::Homebrew->new; 43 | ok !$brew->install('libevent'); 44 | }; 45 | 46 | is $stdout, < sub { 53 | local $ENV{PATH} = ''; 54 | capture { 55 | my $brew = CrystalBuild::Homebrew->new; 56 | ok! $brew->install('libevent'); 57 | }; 58 | }; 59 | }; 60 | 61 | done_testing; 62 | __DATA__ 63 | @@ exit_0.sh 64 | #!/bin/bash 65 | echo $* 66 | exit 0 67 | 68 | @@ exit_1.sh 69 | #!/bin/bash 70 | echo $* 71 | exit 1 72 | -------------------------------------------------------------------------------- /t/crystal_build/install.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use Capture::Tiny qw/capture_stdout/; 6 | use Test::MockObject; 7 | 8 | use t::Util; 9 | 10 | subtest basic => sub { 11 | my $crystal_installer = Test::MockObject->new; 12 | $crystal_installer->mock(install => sub { 13 | my ($self, $crystal_version, $install_dir) = @_; 14 | is $crystal_version, '0.7.7'; 15 | is $install_dir, 't/tmp/.crenv/versions/0.7.7'; 16 | }); 17 | $crystal_installer->mock(needs_shards => sub { 1 }); 18 | 19 | my $shards_installer = Test::MockObject->new; 20 | $shards_installer->mock(install => sub { 21 | my ($self, $crystal_version, $crystal_dir) = @_; 22 | is $crystal_version, '0.7.7'; 23 | is $crystal_dir, 't/tmp/.crenv/versions/0.7.7'; 24 | }); 25 | 26 | my $guard = mock_guard('CrystalBuild', { 27 | crystal_installer => sub { $crystal_installer }, 28 | shards_installer => sub { $shards_installer }, 29 | }); 30 | 31 | my $crenv = create_crenv; 32 | 33 | my ($stdout) = capture_stdout { 34 | $crenv->install('0.7.7'); 35 | }; 36 | 37 | my $expected = <<"EOF"; 38 | Install successful 39 | EOF 40 | 41 | is $stdout, $expected; 42 | 43 | ok $crystal_installer->called('install'); 44 | ok $crystal_installer->called('needs_shards'); 45 | ok $shards_installer->called('install'); 46 | ok !$shards_installer->called('needs_shards'); 47 | }; 48 | 49 | done_testing; 50 | -------------------------------------------------------------------------------- /t/crystal_build/installer/crystal/_download.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use Test::MockObject; 6 | use Test::Mock::Guard qw/mock_guard/; 7 | 8 | use t::Util; 9 | use CrystalBuild::Installer::Crystal; 10 | use CrystalBuild::Downloader::Crystal; 11 | 12 | subtest basic => sub { 13 | my $fetcher = Test::MockObject->new; 14 | my $downloader = Test::MockObject->new; 15 | 16 | $downloader->mock(download => sub { 17 | my ($self, $tarball_url, $cache_dir) = @_; 18 | 19 | is $tarball_url, 'http://dummy.url'; 20 | is $cache_dir, '__CACHE_DIR__/__CRYSTAL_VERSION__'; 21 | }); 22 | 23 | my $guard = mock_guard('CrystalBuild::Downloader::Crystal', { 24 | new => sub { 25 | my ($class, %opt) = @_; 26 | is $opt{fetcher}, '__FETCHER__'; 27 | return $downloader; 28 | }, 29 | }); 30 | 31 | my $installer = CrystalBuild::Installer::Crystal->new( 32 | fetcher => '__FETCHER__', 33 | cache_dir => '__CACHE_DIR__', 34 | ); 35 | $installer->_download('http://dummy.url', '__CRYSTAL_VERSION__'); 36 | 37 | is $guard->call_count('CrystalBuild::Downloader::Crystal', 'new'), 1; 38 | ok $downloader->called('download'); 39 | }; 40 | 41 | done_testing; 42 | -------------------------------------------------------------------------------- /t/crystal_build/installer/crystal/_move.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use File::Touch; 6 | use File::Path qw/rmtree mkpath/; 7 | use Test::MockObject; 8 | use Test::Mock::Guard qw/mock_guard/; 9 | 10 | use t::Util; 11 | use CrystalBuild::Installer::Crystal; 12 | use CrystalBuild::Downloader::Crystal; 13 | 14 | use constant CACHE_DIR => 't/tmp/.crenv/cache/0.13.0/crystal-0.13.0-1'; 15 | use constant INSTALL_DIR => 't/tmp/.crenv/versions/0.13.0'; 16 | 17 | sub run_tests { 18 | # ----- setup ----------------------------------------- 19 | mkpath CACHE_DIR; 20 | touch CACHE_DIR.'/file'; 21 | 22 | ok !-f INSTALL_DIR.'/file'; 23 | 24 | my $installer = bless {} => 'CrystalBuild::Installer::Crystal'; 25 | $installer->_move(CACHE_DIR, INSTALL_DIR); 26 | 27 | 28 | # ----- assert ---------------------------------------- 29 | ok -d INSTALL_DIR; 30 | ok -f INSTALL_DIR.'/file'; 31 | } 32 | 33 | subtest succeeded => sub { 34 | subtest '# clean' => sub { 35 | setup_dirs; 36 | run_tests; 37 | }; 38 | 39 | subtest '# exists' => sub { 40 | setup_dirs; 41 | mkpath INSTALL_DIR; 42 | run_tests; 43 | }; 44 | }; 45 | 46 | done_testing; 47 | -------------------------------------------------------------------------------- /t/crystal_build/installer/crystal/_resolve.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use Test::MockObject; 6 | 7 | use t::Util; 8 | use CrystalBuild::Installer::Crystal; 9 | 10 | subtest basic => sub { 11 | my $resolver = Test::MockObject->new; 12 | $resolver->mock(resolve_by_version => sub { 13 | my ($self, $version) = @_; 14 | is $version, '0.13.0'; 15 | }); 16 | 17 | my $installer = bless {} => 'CrystalBuild::Installer::Crystal'; 18 | $installer->{resolver} = $resolver; 19 | 20 | $installer->_resolve('0.13.0'); 21 | 22 | ok !$resolver->called('resolve'); 23 | ok $resolver->called('resolve_by_version'); 24 | }; 25 | 26 | done_testing; 27 | -------------------------------------------------------------------------------- /t/crystal_build/installer/crystal/install.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use Capture::Tiny qw/capture_stdout/; 6 | use Test::MockObject; 7 | use Test::Mock::Guard qw/mock_guard/; 8 | 9 | use t::Util; 10 | use CrystalBuild::Installer::Crystal; 11 | 12 | subtest basic => sub { 13 | my $guard = mock_guard('CrystalBuild::Installer::Crystal', { 14 | _resolve => sub { 15 | my ($self, $crystal_version) = @_; 16 | is $crystal_version, '__CRYSTAL_VERSION__'; 17 | return '__URL__'; 18 | }, 19 | _download => sub { 20 | my ($self, $tarball_url, $crystal_version) = @_; 21 | is $tarball_url, '__URL__'; 22 | is $crystal_version, '__CRYSTAL_VERSION__'; 23 | return '__DIR__'; 24 | }, 25 | _move => sub { 26 | my ($self, $extracted_dir, $install_dir) = @_; 27 | is $extracted_dir, '__DIR__'; 28 | is $install_dir, '__INSTALL_DIR__'; 29 | }, 30 | }); 31 | 32 | my $installer = bless {} => 'CrystalBuild::Installer::Crystal'; 33 | my ($actual) = capture_stdout { 34 | $installer->install('__CRYSTAL_VERSION__', '__INSTALL_DIR__'); 35 | }; 36 | 37 | is $guard->call_count('CrystalBuild::Installer::Crystal', '_resolve'), 1; 38 | is $guard->call_count('CrystalBuild::Installer::Crystal', '_download'), 1; 39 | is $guard->call_count('CrystalBuild::Installer::Crystal', '_move'), 1; 40 | }; 41 | 42 | done_testing; 43 | -------------------------------------------------------------------------------- /t/crystal_build/installer/crystal/needs_shards.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use t::Util; 6 | use CrystalBuild::Installer::Crystal; 7 | 8 | subtest basic => sub { 9 | my $installer = bless {} => 'CrystalBuild::Installer::Crystal'; 10 | 11 | ok !$installer->needs_shards('0.6.0'); 12 | ok !$installer->needs_shards('0.7.6'); 13 | ok $installer->needs_shards('0.7.7'); 14 | ok $installer->needs_shards('0.7.8'); 15 | ok $installer->needs_shards('0.8.0'); 16 | ok $installer->needs_shards('0.10.0'); 17 | }; 18 | 19 | done_testing; 20 | -------------------------------------------------------------------------------- /t/crystal_build/installer/crystal/new.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use Test::MockObject; 6 | use Test::Mock::Guard qw/mock_guard/; 7 | 8 | use t::Util; 9 | use CrystalBuild::Installer::Crystal; 10 | 11 | subtest basic => sub { 12 | my $guard = mock_guard('CrystalBuild::Installer::Crystal', { 13 | _create_resolver => sub { 14 | my ($self, %opt) = @_; 15 | is $opt{_resolver_param}, '__PARAM__'; 16 | return '__RESOLVER__'; 17 | }, 18 | }); 19 | 20 | my $installer = CrystalBuild::Installer::Crystal->new( 21 | fetcher => '__FETCHER__', 22 | cache_dir => '__CACHE_DIR__', 23 | _resolver_param => '__PARAM__', 24 | ); 25 | 26 | isa_ok $installer, 'CrystalBuild::Installer::Crystal'; 27 | 28 | is $installer->{fetcher}, '__FETCHER__'; 29 | is $installer->{cache_dir}, '__CACHE_DIR__'; 30 | is $installer->{resolver}, '__RESOLVER__'; 31 | }; 32 | 33 | done_testing; 34 | -------------------------------------------------------------------------------- /t/crystal_build/installer/crystal/versions.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use Test::MockObject; 6 | use Test::Mock::Guard qw/mock_guard/; 7 | 8 | use t::Util; 9 | use CrystalBuild::Resolver::Utils; 10 | use CrystalBuild::Installer::Crystal; 11 | 12 | subtest basic => sub { 13 | my $resolver = Test::MockObject->new; 14 | $resolver->mock(versions => sub { [ 'v0.13.0', '0.12.1', '0.12.0' ] }); 15 | 16 | my $installer = bless {} => 'CrystalBuild::Installer::Crystal'; 17 | $installer->{resolver} = $resolver; 18 | 19 | cmp_deeply $installer->versions, [qw/0.12.0 0.12.1 0.13.0/]; 20 | }; 21 | 22 | done_testing; 23 | -------------------------------------------------------------------------------- /t/crystal_build/installer/shards/_build.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use Test::MockObject; 6 | use Test::Mock::Guard qw/mock_guard/; 7 | 8 | use t::Util; 9 | use CrystalBuild::Installer::Shards; 10 | use CrystalBuild::Builder::Shards; 11 | 12 | subtest basic => sub { 13 | my $builder = Test::MockObject->new; 14 | 15 | $builder->mock(build => sub { 16 | my ($self, $target_dir, $crystal_dir) = @_; 17 | 18 | is $target_dir, 'target_dir'; 19 | is $crystal_dir, 'crystal_dir'; 20 | }); 21 | 22 | my $guard = mock_guard('CrystalBuild::Builder::Shards', { 23 | new => $builder, 24 | }); 25 | 26 | my $installer = CrystalBuild::Installer::Shards->new; 27 | $installer->_build('target_dir', 'crystal_dir'); 28 | 29 | is $guard->call_count('CrystalBuild::Builder::Shards', 'new'), 1; 30 | ok $builder->called('build'); 31 | }; 32 | 33 | done_testing; 34 | -------------------------------------------------------------------------------- /t/crystal_build/installer/shards/_copy.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use File::Basename qw/dirname/; 6 | use File::Slurp; 7 | use File::Spec; 8 | use File::Temp qw/tempfile tempdir/; 9 | use File::Touch; 10 | use Test::MockObject; 11 | use Test::Mock::Guard qw/mock_guard/; 12 | 13 | use t::Util; 14 | use CrystalBuild::Installer::Shards; 15 | 16 | subtest basic => sub { 17 | # ----- before ----- 18 | 19 | # create crystal dir 20 | my $crystal_dir = tempdir(); 21 | my $target_dir = File::Spec->catfile($crystal_dir, 'bin'); 22 | my $copied_shards_bin = File::Spec->catfile($target_dir, 'shards'); 23 | mkdir $target_dir; 24 | 25 | # create shards bin 26 | my ($fh, $shards_bin) = tempfile(); 27 | my $shars_dir = dirname($shards_bin); 28 | my $shards_bin_content = time(); 29 | 30 | print $fh $shards_bin_content; 31 | close $fh; 32 | 33 | ok !-e $copied_shards_bin, 'should not exists'; 34 | 35 | 36 | # ----- test ----- 37 | my $installer = CrystalBuild::Installer::Shards->new; 38 | $installer->_copy($shards_bin, $copied_shards_bin); 39 | 40 | 41 | # ----- assertion ----- 42 | ok -e $copied_shards_bin, 'should exists'; 43 | ok -r $copied_shards_bin, 'should be able to read'; 44 | ok -x $copied_shards_bin, 'should be able to execute'; 45 | is read_file($copied_shards_bin), $shards_bin_content, 'should have valid content'; 46 | }; 47 | 48 | done_testing; 49 | -------------------------------------------------------------------------------- /t/crystal_build/installer/shards/_download.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use Test::MockObject; 6 | use Test::Mock::Guard qw/mock_guard/; 7 | 8 | use t::Util; 9 | use CrystalBuild::Installer::Shards; 10 | use CrystalBuild::Downloader::Shards; 11 | 12 | subtest basic => sub { 13 | my $fetcher = Test::MockObject->new; 14 | my $downloader = Test::MockObject->new; 15 | 16 | $downloader->mock(download => sub { 17 | my ($self, $tarball_url, $cache_dir) = @_; 18 | 19 | is $tarball_url, 'http://dummy.url'; 20 | is $cache_dir, '__CACHE_DIR__/__CRYSTAL_VERSION__'; 21 | }); 22 | 23 | my $guard = mock_guard('CrystalBuild::Downloader::Shards', { 24 | new => sub { 25 | my ($class, %opt) = @_; 26 | is $opt{fetcher}, $fetcher; 27 | return $downloader; 28 | }, 29 | }); 30 | 31 | my $installer = CrystalBuild::Installer::Shards->new( 32 | fetcher => $fetcher, 33 | cache_dir => '__CACHE_DIR__', 34 | ); 35 | $installer->_download('http://dummy.url', '__CRYSTAL_VERSION__'); 36 | 37 | is $guard->call_count('CrystalBuild::Downloader::Shards', 'new'), 1; 38 | ok $downloader->called('download'); 39 | }; 40 | 41 | done_testing; 42 | -------------------------------------------------------------------------------- /t/crystal_build/installer/shards/_resolve.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use Test::MockObject; 6 | use Test::Mock::Guard qw/mock_guard/; 7 | 8 | use t::Util; 9 | use CrystalBuild::Installer::Shards; 10 | use CrystalBuild::Resolver::Shards; 11 | 12 | subtest basic => sub { 13 | my $fetcher = Test::MockObject->new; 14 | my $remote_cache_url = Test::MockObject->new; 15 | my $resolver = Test::MockObject->new; 16 | 17 | $resolver->mock(resolve => sub { 18 | my ($self, $crystal_version) = @_; 19 | 20 | is $crystal_version, '0.9.1'; 21 | }); 22 | 23 | my $guard = mock_guard('CrystalBuild::Resolver::Shards', { 24 | new => sub { 25 | my ($class, %opt) = @_; 26 | 27 | is $opt{fetcher}, $fetcher; 28 | is $opt{shards_releases_url}, $remote_cache_url; 29 | 30 | return $resolver; 31 | }, 32 | }); 33 | 34 | my $installer = CrystalBuild::Installer::Shards->new( 35 | fetcher => $fetcher, 36 | remote_cache_url => $remote_cache_url, 37 | ); 38 | $installer->_resolve('0.9.1'); 39 | 40 | is $guard->call_count('CrystalBuild::Resolver::Shards', 'new'), 1; 41 | ok $resolver->called('resolve'); 42 | }; 43 | 44 | done_testing; 45 | -------------------------------------------------------------------------------- /t/crystal_build/installer/shards/install.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings FATAL => 'all'; 3 | use utf8; 4 | 5 | use File::Path qw/mkpath/; 6 | use File::Temp qw/tempdir/; 7 | use File::Touch; 8 | use Test::MockObject; 9 | use Test::Mock::Guard qw/mock_guard/; 10 | use Capture::Tiny qw/capture_stdout/; 11 | 12 | use t::Util; 13 | use CrystalBuild::Installer::Shards; 14 | 15 | use constant CRYSTAL_VERSION => '0.15.0'; 16 | use constant CRYSTAL_DIR => '/home/user/.crenv/versions/0.15.0'; 17 | use constant EXTRACTED_DIR => '/home/user/.crenv/cache/0.15.0/shards-0.6.2'; 18 | use constant SHARDS_BIN => '/home/user/.crenv/cache/0.15.0/shards-0.6.2/bin/shards'; 19 | use constant TARBALL_URL => 20 | 'https://github.com/crystal-lang/shards/releases/download/v0.6.2/shards-0.6.2_linux_x86_64.gz'; 21 | 22 | subtest normal => sub { 23 | my $installer = bless {} => 'CrystalBuild::Installer::Shards'; 24 | 25 | my $guard = mock_guard($installer, { 26 | _resolve => sub { 27 | is $_[1], CRYSTAL_VERSION; 28 | return TARBALL_URL; 29 | }, 30 | _download => sub { 31 | is $_[1], TARBALL_URL; 32 | is $_[2], CRYSTAL_VERSION; 33 | return EXTRACTED_DIR; 34 | }, 35 | _build => sub { 36 | is $_[1], EXTRACTED_DIR; 37 | is $_[2], CRYSTAL_DIR; 38 | return SHARDS_BIN; 39 | }, 40 | _copy => sub { 41 | is $_[1], SHARDS_BIN; 42 | is $_[2], CRYSTAL_DIR.'/bin/shards'; 43 | }, 44 | }); 45 | 46 | my ($actual) = capture_stdout { 47 | $installer->install(CRYSTAL_VERSION, CRYSTAL_DIR); 48 | }; 49 | 50 | my $expected = <call_count($installer, '_resolve'), 1; 63 | is $guard->call_count($installer, '_download'), 1; 64 | is $guard->call_count($installer, '_build'), 1; 65 | is $guard->call_count($installer, '_copy'), 1; 66 | }; 67 | 68 | subtest exists => sub { 69 | my $crystal_dir = tempdir(); 70 | my $crystal_bin_dir = File::Spec->catfile($crystal_dir, 'bin'); 71 | my $shards_bin = File::Spec->catfile($crystal_bin_dir, 'shards'); 72 | 73 | mkpath $crystal_bin_dir; 74 | touch $shards_bin; 75 | chmod 755, $shards_bin; 76 | 77 | my $installer = bless {} => 'CrystalBuild::Installer::Shards'; 78 | my ($actual) = capture_stdout { 79 | $installer->install(CRYSTAL_VERSION, $crystal_dir); 80 | }; 81 | 82 | my $expected = < sub { 12 | my $fetcher = Test::MockObject->new; 13 | my $remote_cache_url = Test::MockObject->new; 14 | my $cache_dir = Test::MockObject->new; 15 | my $without_release = Test::MockObject->new; 16 | 17 | my $installer = CrystalBuild::Installer::Shards->new( 18 | fetcher => $fetcher, 19 | remote_cache_url => $remote_cache_url, 20 | cache_dir => $cache_dir, 21 | without_release => $without_release, 22 | ); 23 | 24 | isa_ok $installer, 'CrystalBuild::Installer::Shards'; 25 | 26 | is $installer->fetcher, $fetcher; 27 | is $installer->remote_cache_url, $remote_cache_url; 28 | is $installer->cache_dir, $cache_dir; 29 | is $installer->without_release, $without_release; 30 | }; 31 | 32 | done_testing; 33 | -------------------------------------------------------------------------------- /t/crystal_build/new.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use Test::MockObject; 6 | 7 | use t::Util; 8 | 9 | use CrystalBuild; 10 | 11 | subtest basic => sub { 12 | my $without_release = Test::MockObject->new; 13 | 14 | my $crenv = CrystalBuild->new( 15 | test_key => 'test_value', 16 | without_release => $without_release, 17 | ); 18 | 19 | isa_ok $crenv, 'CrystalBuild'; 20 | 21 | is $crenv->{test_key}, 'test_value'; 22 | is $crenv->{without_release}, $without_release; 23 | }; 24 | 25 | done_testing; 26 | -------------------------------------------------------------------------------- /t/crystal_build/resolver/crystal/_create_enable_resolvers.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use t::Util; 6 | use CrystalBuild::Resolver::Crystal; 7 | 8 | subtest '# empty' => sub { 9 | my $resolver = bless {} => 'CrystalBuild::Resolver::Crystal'; 10 | is @{ $resolver->_create_enable_resolvers }, 0; 11 | }; 12 | 13 | subtest '# remote' => sub { 14 | subtest '# only remote' => sub { 15 | my $resolver = bless { use_remote_cache => 1 } => 'CrystalBuild::Resolver::Crystal'; 16 | is @{ $resolver->_create_enable_resolvers }, 1; 17 | isa_ok $resolver->_create_enable_resolvers->[0], 'CrystalBuild::Resolver::Crystal::RemoteCache'; 18 | }; 19 | 20 | subtest '# with github' => sub { 21 | my $resolver = bless { use_remote_cache => 1, use_github => 1, } => 'CrystalBuild::Resolver::Crystal'; 22 | 23 | is @{ $resolver->_create_enable_resolvers }, 2; 24 | isa_ok $resolver->_create_enable_resolvers->[0], 'CrystalBuild::Resolver::Crystal::RemoteCache'; 25 | isa_ok $resolver->_create_enable_resolvers->[1], 'CrystalBuild::Resolver::Crystal::GitHub'; 26 | }; 27 | }; 28 | 29 | subtest '# github' => sub { 30 | my $resolver = bless { use_github => 1 } => 'CrystalBuild::Resolver::Crystal'; 31 | is @{ $resolver->_create_enable_resolvers }, 1; 32 | isa_ok $resolver->_create_enable_resolvers->[0], 'CrystalBuild::Resolver::Crystal::GitHub'; 33 | }; 34 | 35 | done_testing; 36 | -------------------------------------------------------------------------------- /t/crystal_build/resolver/crystal/_create_github_resolver.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use t::Util; 6 | use CrystalBuild::Resolver::Crystal; 7 | 8 | subtest basic => sub { 9 | my $resolver = bless { 10 | fetcher => '__FETCHER__', 11 | github_repository => '__REPO__', 12 | } => 'CrystalBuild::Resolver::Crystal'; 13 | 14 | my $github_resolver = $resolver->_create_github_resolver; 15 | 16 | is $github_resolver->{github}->{fetcher}, '__FETCHER__'; 17 | is $github_resolver->{github}->{github_repo}, '__REPO__'; 18 | }; 19 | 20 | done_testing; 21 | -------------------------------------------------------------------------------- /t/crystal_build/resolver/crystal/_create_remote_cache_resolver.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use t::Util; 6 | use CrystalBuild::Resolver::Crystal; 7 | 8 | subtest basic => sub { 9 | my $resolver = bless { 10 | fetcher => '__FETCHER__', 11 | remote_cache_url => '__URL__', 12 | } => 'CrystalBuild::Resolver::Crystal'; 13 | 14 | my $remote_cache_resolver = $resolver->_create_remote_cache_resolver; 15 | 16 | is $remote_cache_resolver->{fetcher}, '__FETCHER__'; 17 | is $remote_cache_resolver->{cache_url}, '__URL__'; 18 | }; 19 | 20 | done_testing; 21 | -------------------------------------------------------------------------------- /t/crystal_build/resolver/crystal/github/_find_binary_download_urls.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use t::Util; 6 | 7 | use CrystalBuild::Resolver::Crystal::GitHub; 8 | 9 | subtest 'if < v0.7.5' => sub { 10 | # 64 bit releases only 11 | my $assets = [ 12 | { 13 | name => 'crystal-0.7.5-1-darwin-x86_64.tar.gz', 14 | browser_download_url => 'http://www.example.com/darwin/x64', 15 | }, 16 | { 17 | name => 'crystal-0.7.5-1-linux-x86_64.tar.gz', 18 | browser_download_url => 'http://www.example.com/linux/x64', 19 | }, 20 | ]; 21 | 22 | my $urls = CrystalBuild::Resolver::Crystal::GitHub->_find_binary_download_urls($assets); 23 | is $urls->{'darwin-x64'}, 'http://www.example.com/darwin/x64'; 24 | is $urls->{'linux-x64'}, 'http://www.example.com/linux/x64'; 25 | ok not defined $urls->{'linux-x86'}; 26 | }; 27 | 28 | subtest basic => sub { 29 | my $assets = [ 30 | { 31 | name => 'crystal-0.7.5-1-darwin-x86_64.tar.gz', 32 | browser_download_url => 'http://www.example.com/darwin/x64', 33 | }, 34 | { 35 | name => 'crystal-0.7.5-1-linux-x86_64.tar.gz', 36 | browser_download_url => 'http://www.example.com/linux/x64', 37 | }, 38 | { 39 | name => 'crystal-0.7.5-1-linux-i686.tar.gz', 40 | browser_download_url => 'http://www.example.com/linux/x86', 41 | }, 42 | ]; 43 | 44 | my $urls = CrystalBuild::Resolver::Crystal::GitHub->_find_binary_download_urls($assets); 45 | is $urls->{'darwin-x64'}, 'http://www.example.com/darwin/x64'; 46 | is $urls->{'linux-x64'}, 'http://www.example.com/linux/x64'; 47 | is $urls->{'linux-x86'}, 'http://www.example.com/linux/x86'; 48 | }; 49 | 50 | done_testing; 51 | -------------------------------------------------------------------------------- /t/crystal_build/resolver/crystal/github/github.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use t::Util; 6 | 7 | use CrystalBuild::Resolver::Crystal::GitHub; 8 | 9 | subtest basic => sub { 10 | my $resolver = CrystalBuild::Resolver::Crystal::GitHub->new; 11 | $resolver->{github} = 'github'; 12 | 13 | can_ok $resolver, qw/github/; 14 | is $resolver->github, 'github'; 15 | }; 16 | 17 | done_testing; 18 | -------------------------------------------------------------------------------- /t/crystal_build/resolver/crystal/github/new.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use t::Util; 6 | 7 | use CrystalBuild::Resolver::Crystal::GitHub; 8 | 9 | subtest basic => sub { 10 | my $resolver = CrystalBuild::Resolver::Crystal::GitHub->new(test_key => 'test_value'); 11 | 12 | isa_ok $resolver, 'CrystalBuild::Resolver::Crystal::GitHub'; 13 | is $resolver->{test_key}, 'test_value'; 14 | }; 15 | 16 | done_testing; 17 | -------------------------------------------------------------------------------- /t/crystal_build/resolver/crystal/github/resolve.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use Test::MockObject; 6 | 7 | use t::Util; 8 | use CrystalBuild::Resolver::Crystal::GitHub; 9 | 10 | use constant LINUX_X86_URL => 'http://www.example.com/linux/x86'; 11 | use constant LINUX_X64_URL => 'http://www.example.com/linux/x64'; 12 | use constant DARWIN_X64_URL => 'http://www.example.com/darwin/x64'; 13 | use constant FREEBSD_X64_URL => 'http://www.example.com/freebsd/x64'; 14 | 15 | subtest basic => sub { 16 | my $github = Test::MockObject->new; 17 | $github->mock(fetch_release => sub { 18 | my ($self, $version) = @_; 19 | is $version, '0.7.5'; 20 | return { assets => [ $version ] }; 21 | }); 22 | 23 | my $guard = mock_guard( 24 | 'CrystalBuild::Resolver::Crystal::GitHub', { 25 | github => sub { $github }, 26 | _find_binary_download_urls => sub { 27 | my ($class, $assets) = @_; 28 | 29 | cmp_deeply $assets, [ '0.7.5' ]; 30 | 31 | return { 32 | 'linux-x86' => LINUX_X86_URL, 33 | 'linux-x64' => LINUX_X64_URL, 34 | 'darwin-x64' => DARWIN_X64_URL, 35 | 'freebsd-x64' => FREEBSD_X64_URL, 36 | }; 37 | }, 38 | }); 39 | 40 | my $resolver = CrystalBuild::Resolver::Crystal::GitHub->new; 41 | 42 | is $resolver->resolve('0.7.5', 'linux', 'x86'), LINUX_X86_URL; 43 | is $resolver->resolve('0.7.5', 'linux', 'x64'), LINUX_X64_URL; 44 | is $resolver->resolve('0.7.5', 'darwin', 'x64'), DARWIN_X64_URL; 45 | is $resolver->resolve('0.7.5', 'freebsd', 'x64'), FREEBSD_X64_URL; 46 | 47 | ok $github->called('fetch_release'); 48 | is $guard->call_count('CrystalBuild::Resolver::Crystal::GitHub', 'github'), 4; 49 | is $guard->call_count('CrystalBuild::Resolver::Crystal::GitHub', '_find_binary_download_urls'), 4; 50 | }; 51 | 52 | done_testing; 53 | -------------------------------------------------------------------------------- /t/crystal_build/resolver/crystal/github/versions.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use Test::MockObject; 6 | 7 | use t::Util; 8 | use CrystalBuild::Resolver::Crystal::GitHub; 9 | 10 | subtest basic => sub { 11 | my $github = Test::MockObject->new; 12 | $github->mock(fetch_releases => sub { 13 | my ($self) = @_; 14 | return [ 15 | { tag_name => '0.7.0' }, 16 | { tag_name => '0.7.1' }, 17 | { tag_name => '0.7.2' }, 18 | ]; 19 | }); 20 | 21 | my $guard = mock_guard( 22 | 'CrystalBuild::Resolver::Crystal::GitHub', { 23 | github => sub { $github }, 24 | }); 25 | 26 | my $resolver = CrystalBuild::Resolver::Crystal::GitHub->new; 27 | 28 | cmp_deeply 29 | $resolver->versions, 30 | [ '0.7.0', '0.7.1', '0.7.2' ]; 31 | 32 | ok $github->called('fetch_releases'); 33 | is $guard->call_count('CrystalBuild::Resolver::Crystal::GitHub', 'github'), 1; 34 | }; 35 | 36 | done_testing; 37 | -------------------------------------------------------------------------------- /t/crystal_build/resolver/crystal/new.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use Test::Mock::Guard qw/mock_guard/; 6 | 7 | use t::Util; 8 | use CrystalBuild::Resolver::Crystal; 9 | 10 | subtest basic => sub { 11 | my $guard = mock_guard('CrystalBuild::Resolver::Crystal', { 12 | _create_enable_resolvers => sub {}, 13 | }); 14 | 15 | my $resolver = CrystalBuild::Resolver::Crystal->new( 16 | fetcher => '__FETCHER__', 17 | github_repository => '__REPO__', 18 | remote_cache_url => '__URL__', 19 | use_remote_cache => '__USE_REMOTE_CACHE__', 20 | use_github => '__USE_GITHUB__', 21 | ); 22 | 23 | is $resolver->{fetcher}, '__FETCHER__'; 24 | is $resolver->{github_repository}, '__REPO__'; 25 | is $resolver->{remote_cache_url}, '__URL__'; 26 | is $resolver->{use_remote_cache}, '__USE_REMOTE_CACHE__'; 27 | is $resolver->{use_github}, '__USE_GITHUB__'; 28 | 29 | isa_ok $resolver, 'CrystalBuild::Resolver::Crystal'; 30 | is $guard->call_count('CrystalBuild::Resolver::Crystal', '_create_enable_resolvers'), 1; 31 | }; 32 | 33 | done_testing; 34 | -------------------------------------------------------------------------------- /t/crystal_build/resolver/crystal/remote_cache/cache_url.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use t::Util; 6 | 7 | use CrystalBuild::Resolver::Crystal::RemoteCache; 8 | 9 | subtest basic => sub { 10 | my $resolver = CrystalBuild::Resolver::Crystal::RemoteCache->new; 11 | $resolver->{cache_url} = 'cache_url'; 12 | 13 | can_ok $resolver, qw/cache_url/; 14 | is $resolver->cache_url, 'cache_url'; 15 | }; 16 | 17 | done_testing; 18 | -------------------------------------------------------------------------------- /t/crystal_build/resolver/crystal/remote_cache/fetcher.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use t::Util; 6 | 7 | use CrystalBuild::Resolver::Crystal::RemoteCache; 8 | 9 | subtest basic => sub { 10 | my $resolver = CrystalBuild::Resolver::Crystal::RemoteCache->new; 11 | $resolver->{fetcher} = 'fetcher'; 12 | 13 | can_ok $resolver, qw/fetcher/; 14 | is $resolver->fetcher, 'fetcher'; 15 | }; 16 | 17 | done_testing; 18 | -------------------------------------------------------------------------------- /t/crystal_build/resolver/crystal/remote_cache/new.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use t::Util; 6 | 7 | use CrystalBuild::Resolver::Crystal::RemoteCache; 8 | 9 | subtest basic => sub { 10 | my $resolver = CrystalBuild::Resolver::Crystal::RemoteCache->new(test_key => 'test_value'); 11 | 12 | isa_ok $resolver, 'CrystalBuild::Resolver::Crystal::RemoteCache'; 13 | is $resolver->{test_key}, 'test_value'; 14 | }; 15 | 16 | done_testing; 17 | -------------------------------------------------------------------------------- /t/crystal_build/resolver/crystal/remote_cache/resolve.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use Scope::Guard qw/guard/; 6 | use Test::MockObject; 7 | use Test::MockTime qw/set_fixed_time restore_time/; 8 | 9 | use t::Util; 10 | use CrystalBuild::Resolver::Crystal::RemoteCache; 11 | 12 | subtest basic => sub { 13 | my $time_guard = guard { restore_time() }; 14 | set_fixed_time(time); 15 | 16 | my $fetcher = Test::MockObject->new; 17 | $fetcher->mock(fetch => sub { 18 | my ($self, $url) = @_; 19 | 20 | is $url, 'http://www.example.com?'.time; 21 | 22 | return < sub { $fetcher }, 38 | cache_url => sub { 'http://www.example.com' }, 39 | }); 40 | 41 | my $resolver = CrystalBuild::Resolver::Crystal::RemoteCache->new; 42 | 43 | is 44 | $resolver->resolve('0.7.5', 'darwin', 'x64'), 45 | 'https://crystal.org/darwin-x64-0.7.5.tar.gz'; 46 | 47 | ok $fetcher->called('fetch'); 48 | is $guard->call_count('CrystalBuild::Resolver::Crystal::RemoteCache', 'fetcher'), 1; 49 | is $guard->call_count('CrystalBuild::Resolver::Crystal::RemoteCache', 'cache_url'), 1; 50 | }; 51 | 52 | done_testing; 53 | -------------------------------------------------------------------------------- /t/crystal_build/resolver/crystal/remote_cache/versions.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use Test::MockObject; 6 | 7 | use t::Util; 8 | use CrystalBuild::Resolver::Crystal::RemoteCache; 9 | 10 | subtest basic => sub { 11 | my $guard = mock_guard( 12 | 'CrystalBuild::Resolver::Crystal::RemoteCache', { 13 | _fetch => sub { 14 | return [ 15 | { tag_name => '0.7.0' }, 16 | { tag_name => '0.7.1' }, 17 | { tag_name => '0.7.2' }, 18 | ]; 19 | }, 20 | }); 21 | 22 | my $resolver = CrystalBuild::Resolver::Crystal::RemoteCache->new; 23 | 24 | cmp_deeply 25 | $resolver->versions, 26 | [ '0.7.0', '0.7.1', '0.7.2' ]; 27 | 28 | is $guard->call_count('CrystalBuild::Resolver::Crystal::RemoteCache', '_fetch'), 1; 29 | }; 30 | 31 | done_testing; 32 | -------------------------------------------------------------------------------- /t/crystal_build/resolver/crystal/resolve.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use Test::MockObject; 6 | use Test::Mock::Guard qw/mock_guard/; 7 | 8 | use t::Util; 9 | use CrystalBuild::Resolver::Crystal; 10 | 11 | subtest basic => sub { 12 | my @mock_resolvers = map { Test::MockObject->new } 0..2; 13 | $_->mock(name => sub { 'mock' }) for @mock_resolvers; 14 | 15 | $mock_resolvers[0]->mock(resolve => sub { undef }); 16 | $mock_resolvers[1]->mock(resolve => sub { 17 | my ($self, $version, $platform, $arch) = @_; 18 | return '__URL1__'; 19 | }); 20 | $mock_resolvers[2]->mock(resolve => sub { '__URL2__' }); 21 | 22 | my $resolver = bless { 23 | resolvers => \@mock_resolvers, 24 | } => 'CrystalBuild::Resolver::Crystal'; 25 | 26 | is $resolver->resolve('0.13.0', 'darwin', 'x64'), '__URL1__'; 27 | 28 | ok $mock_resolvers[0]->called('resolve'); 29 | ok $mock_resolvers[1]->called('resolve'); 30 | ok !$mock_resolvers[2]->called('resolve'); 31 | 32 | cmp_deeply 33 | [ $mock_resolvers[1]->call_args(2) ], 34 | [ $mock_resolvers[1], '0.13.0', 'darwin', 'x64' ]; 35 | }; 36 | 37 | subtest failed => sub { 38 | subtest '# no resolvers' => sub { 39 | my $resolver = bless { resolvers => [] } => 'CrystalBuild::Resolver::Crystal'; 40 | dies_ok { $resolver->resolve('0.13.0', 'darwin', 'x86') }; 41 | }; 42 | }; 43 | 44 | done_testing; 45 | -------------------------------------------------------------------------------- /t/crystal_build/resolver/crystal/resolve_by_version.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings FATAL => 'all'; 3 | use utf8; 4 | 5 | use Test::Mock::Guard qw/mock_guard/; 6 | 7 | use t::Util; 8 | use CrystalBuild::Resolver::Crystal; 9 | 10 | use constant CRYSTAL_VERSION => '0.15.0'; 11 | use constant PLATFORM => 'darwin'; 12 | use constant ARCHITECTURE => 'x64'; 13 | 14 | subtest basic => sub { 15 | my $guard_utils = mock_guard('CrystalBuild::Utils', { 16 | system_info => sub { (PLATFORM, ARCHITECTURE) }, 17 | }); 18 | 19 | my $guard_resolver = mock_guard('CrystalBuild::Resolver::Crystal', { 20 | resolve => sub { 21 | my ($self, $version, $platform, $arch) = @_; 22 | is $version, CRYSTAL_VERSION; 23 | is $platform, PLATFORM; 24 | is $arch, ARCHITECTURE; 25 | }, 26 | }); 27 | 28 | my $resolver = bless {} => 'CrystalBuild::Resolver::Crystal'; 29 | $resolver->resolve_by_version(CRYSTAL_VERSION); 30 | 31 | is $guard_utils->call_count('CrystalBuild::Utils', 'system_info'), 1; 32 | is $guard_resolver->call_count('CrystalBuild::Resolver::Crystal', 'resolve'), 1; 33 | }; 34 | 35 | done_testing; 36 | -------------------------------------------------------------------------------- /t/crystal_build/resolver/crystal/versions.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use Test::MockObject; 6 | use Test::Mock::Guard qw/mock_guard/; 7 | 8 | use t::Util; 9 | use CrystalBuild::Resolver::Crystal; 10 | 11 | subtest basic => sub { 12 | my @mock_resolvers = map { Test::MockObject->new } 0..2; 13 | $mock_resolvers[0]->mock(versions => sub { die }); 14 | $mock_resolvers[1]->mock(versions => sub { [ '0.13.0' ] }); 15 | $mock_resolvers[2]->mock(versions => sub { [ '0.14.0' ] }); 16 | 17 | my $resolver = bless { 18 | resolvers => \@mock_resolvers, 19 | } => 'CrystalBuild::Resolver::Crystal'; 20 | 21 | is $resolver->versions->[0], '0.13.0'; 22 | }; 23 | 24 | subtest failed => sub { 25 | subtest '# no versions' => sub { 26 | my $resolver = Test::MockObject->new; 27 | $resolver->mock(versions => sub { [] }); 28 | 29 | my $composite_resolver = 30 | bless { resolvers => [ $resolver ] } => 'CrystalBuild::Resolver::Crystal'; 31 | 32 | dies_ok { $composite_resolver->versions }; 33 | }; 34 | 35 | subtest '# no resolvers' => sub { 36 | my $resolver = bless { resolvers => [] } => 'CrystalBuild::Resolver::Crystal'; 37 | dies_ok { $resolver->versions }; 38 | }; 39 | }; 40 | 41 | done_testing; 42 | -------------------------------------------------------------------------------- /t/crystal_build/resolver/shards/_fetch.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use Test::MockObject; 6 | 7 | use t::Util; 8 | use CrystalBuild::Resolver::Shards; 9 | 10 | subtest basic => sub { 11 | my $url = Test::MockObject->new; 12 | my $fetcher = Test::MockObject->new; 13 | $fetcher->mock(fetch => sub { 14 | is $_[1], $url; 15 | return do { local $/; }; 16 | }); 17 | 18 | my $resolver = CrystalBuild::Resolver::Shards->new( 19 | fetcher => $fetcher, 20 | shards_releases_url => $url, 21 | ); 22 | 23 | cmp_deeply $resolver->_fetch, { result => "ok" }; 24 | ok $fetcher->called('fetch'); 25 | }; 26 | 27 | done_testing; 28 | __DATA__ 29 | { "result": "ok" } 30 | -------------------------------------------------------------------------------- /t/crystal_build/resolver/shards/new.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use t::Util; 6 | use CrystalBuild::Resolver::Shards; 7 | 8 | subtest basic => sub { 9 | my $resolver = CrystalBuild::Resolver::Shards->new( 10 | fetcher => '__FETCHER__', 11 | shards_releases_url => '__SHARDS_RELEASES_URL__', 12 | ); 13 | 14 | isa_ok $resolver, 'CrystalBuild::Resolver::Shards'; 15 | 16 | is $resolver->fetcher, '__FETCHER__'; 17 | is $resolver->shards_releases_url, '__SHARDS_RELEASES_URL__'; 18 | }; 19 | 20 | done_testing; 21 | -------------------------------------------------------------------------------- /t/crystal_build/resolver/shards/resolve.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use Test::Mock::Guard qw/mock_guard/; 6 | 7 | use t::Util; 8 | use CrystalBuild::Resolver::Shards; 9 | 10 | subtest basic => sub { 11 | my $releases; 12 | my $guard = mock_guard('CrystalBuild::Resolver::Shards', { 13 | _fetch => sub { $releases }, 14 | }); 15 | my $resolver = CrystalBuild::Resolver::Shards->new; 16 | 17 | subtest '#ok' => sub { 18 | $releases = { '0.11.0' => '__URL__' }; 19 | is $resolver->resolve('0.11.0'), '__URL__'; 20 | }; 21 | 22 | subtest '#ok ... default' => sub { 23 | $releases = { 'default' => '__URL__' }; 24 | is $resolver->resolve('0.10.0'), '__URL__'; 25 | }; 26 | 27 | subtest '#ng ... not found' => sub { 28 | $releases = { '0.11.0' => '__URL__' }; 29 | is $resolver->resolve('0.10.0'), undef; 30 | }; 31 | 32 | subtest '#ng ... null' => sub { 33 | $releases = undef; 34 | is $resolver->resolve('0.11.0'), undef; 35 | }; 36 | 37 | subtest '#ng ... string' => sub { 38 | $releases = 'STRING'; 39 | is $resolver->resolve('0.11.0'), undef; 40 | }; 41 | 42 | subtest '#ng ... array' => sub { 43 | $releases = []; 44 | is $resolver->resolve('0.11.0'), undef; 45 | }; 46 | }; 47 | 48 | done_testing; 49 | -------------------------------------------------------------------------------- /t/crystal_build/resolver/utils/normalize_version.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings FATAL => 'all'; 3 | use utf8; 4 | 5 | use Test::Mock::Guard qw/mock_guard/; 6 | 7 | use t::Util; 8 | use CrystalBuild::Resolver::Utils; 9 | 10 | sub normalize_version { CrystalBuild::Resolver::Utils->normalize_version(@_) } 11 | 12 | subtest default => sub { 13 | is normalize_version('0.7.0'), '0.7.0'; 14 | }; 15 | 16 | subtest prefix => sub { 17 | is normalize_version('v0.7.0'), '0.7.0'; 18 | }; 19 | 20 | subtest other => sub { 21 | is normalize_version('other'), 'other'; 22 | }; 23 | 24 | subtest failed => sub { 25 | dies_ok { normalize_version }; 26 | }; 27 | 28 | done_testing; 29 | -------------------------------------------------------------------------------- /t/crystal_build/resolver/utils/sort_version.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use t::Util; 6 | use CrystalBuild::Resolver::Utils; 7 | 8 | subtest basic => sub { 9 | my $versions = [ '0.1.0', '0.5.0', '0.4.5', '0.5.10', '0.1.1', '0.5.1' ]; 10 | my $sorted_versions = CrystalBuild::Resolver::Utils->sort_version($versions); 11 | 12 | cmp_deeply( 13 | $sorted_versions, 14 | [ '0.1.0', '0.1.1', '0.4.5', '0.5.0', '0.5.1', '0.5.10' ] 15 | ); 16 | }; 17 | 18 | 19 | done_testing; 20 | -------------------------------------------------------------------------------- /t/crystal_build/sense/import.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use t::Util; 6 | require CrystalBuild::Sense; 7 | 8 | subtest basic => sub { 9 | my @features; 10 | my $guard_strict = mock_guard('strict', { import => undef }); 11 | my $guard_warnings = mock_guard('warnings', { import => undef }); 12 | my $guard_utf8 = mock_guard('utf8', { import => undef }); 13 | my $guard_feature = mock_guard('feature', { import => sub { @features = @_[1..$#_ ] } }); 14 | 15 | import CrystalBuild::Sense; 16 | 17 | is $guard_strict->call_count('strict', 'import'), 1; 18 | is $guard_warnings->call_count('warnings', 'import'), 1; 19 | is $guard_utf8->call_count('utf8', 'import'), 1; 20 | is $guard_feature->call_count('feature', 'import'), 1; 21 | 22 | cmp_deeply [ @features ], [qw/say state/]; 23 | }; 24 | 25 | done_testing; 26 | -------------------------------------------------------------------------------- /t/crystal_build/show_definitions.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use Test::MockObject; 6 | use Capture::Tiny qw/capture_stdout/; 7 | 8 | use t::Util; 9 | 10 | subtest show_definitions => sub { 11 | my $installer = Test::MockObject->new; 12 | $installer->mock(versions => sub { [ '0.7.0', '0.5.0', '0.6.1' ] }); 13 | 14 | my $guard = mock_guard('CrystalBuild', { 15 | crystal_installer => sub { $installer }, 16 | }); 17 | 18 | my $crenv = create_crenv; 19 | 20 | # show_definitions doesn't sort 21 | my ($stdout) = capture_stdout { $crenv->show_definitions }; 22 | is $stdout, "0.7.0\n0.5.0\n0.6.1\n"; 23 | }; 24 | 25 | done_testing; 26 | -------------------------------------------------------------------------------- /t/crystal_build/utils/extract_tar.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use Cwd qw/abs_path/; 6 | use File::Slurp; 7 | use Archive::Tar; 8 | 9 | use t::Util; 10 | use CrystalBuild::Utils; 11 | 12 | subtest basic => sub { 13 | setup_dirs; 14 | 15 | ok not -d 't/tmp/archive'; 16 | 17 | CrystalBuild::Utils::extract_tar( 18 | abs_path('t/data/archive.tar.gz'), 19 | abs_path('t/tmp/') 20 | ); 21 | 22 | ok -d 't/tmp/archive'; 23 | ok -f 't/tmp/archive/archive.txt'; 24 | ok read_file('t/tmp/archive/archive.txt') =~ /archive\s*/; 25 | }; 26 | 27 | subtest tar_cmd => sub { 28 | my $guard = mock_guard('Archive::Tar', { 29 | new => sub { die 1 }, 30 | read => sub { }, 31 | }); 32 | 33 | setup_dirs; 34 | 35 | ok not -d 't/tmp/archive'; 36 | 37 | CrystalBuild::Utils::extract_tar( 38 | abs_path('t/data/archive.tar.gz'), 39 | abs_path('t/tmp/') 40 | ); 41 | 42 | ok -d 't/tmp/archive'; 43 | ok -f 't/tmp/archive/archive.txt'; 44 | ok read_file('t/tmp/archive/archive.txt') =~ /archive\s*/; 45 | 46 | is $guard->call_count('Archive::Tar', 'new'), 1; 47 | is $guard->call_count('Archive::Tar', 'read'), 0; 48 | }; 49 | 50 | done_testing; 51 | -------------------------------------------------------------------------------- /t/crystal_build/utils/parse_args.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use t::Util; 6 | 7 | use CrystalBuild::Utils; 8 | 9 | subtest basic => sub { 10 | my $args = CrystalBuild::Utils::parse_args('0.7.4', 'prefix', '1'); 11 | is $args->{version}, '0.7.4'; 12 | is $args->{prefix}, 'prefix'; 13 | is $args->{without_release}, '1'; 14 | ok not $args->{definitions}; 15 | }; 16 | 17 | subtest definitions => sub { 18 | push @ARGV, '--definitions'; 19 | 20 | my $args = CrystalBuild::Utils::parse_args; 21 | ok $args->{definitions}; 22 | }; 23 | 24 | done_testing; 25 | -------------------------------------------------------------------------------- /t/crystal_build/utils/system_info.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use Mac::OSVersion::Lite; 6 | use Test::Mock::Guard qw/mock_guard/; 7 | 8 | use t::Util; 9 | use CrystalBuild::Utils; 10 | 11 | subtest normal => sub { 12 | subtest '# Linux' => sub { 13 | my $guard = mock_guard('POSIX', { 14 | uname => sub { ('Linux', undef, undef, undef, 'x86_64') }, 15 | }); 16 | 17 | my @system_info = CrystalBuild::Utils::system_info; 18 | is $system_info[0], 'linux'; 19 | }; 20 | 21 | subtest '# Darwin' => sub { 22 | my $guard_posix = mock_guard('POSIX', { 23 | uname => sub { ('Darwin', undef, undef, undef, 'x86_64') }, 24 | }); 25 | 26 | my $guard_osx = mock_guard('Mac::OSVersion::Lite', { 27 | new => do { my $v = Mac::OSVersion::Lite->new('10.11'); sub { $v } }, 28 | }); 29 | 30 | my @system_info = CrystalBuild::Utils::system_info; 31 | is $system_info[0], 'darwin'; 32 | is $system_info[2], 'el_capitan'; 33 | 34 | is $guard_posix->call_count('POSIX', 'uname'), 1; 35 | is $guard_osx->call_count('Mac::OSVersion::Lite', 'new'), 1; 36 | }; 37 | 38 | subtest '# x64' => sub { 39 | subtest '# Linux or Darwin' => sub { 40 | my $guard = mock_guard('POSIX', { 41 | uname => sub { ('Linux', undef, undef, undef, 'x86_64') }, 42 | }); 43 | 44 | my @system_info = CrystalBuild::Utils::system_info; 45 | is $system_info[1], 'x64'; 46 | }; 47 | 48 | subtest '# FreeBSD' => sub { 49 | my $guard = mock_guard('POSIX', { 50 | uname => sub { ('Linux', undef, undef, undef, 'amd64') }, 51 | }); 52 | 53 | my @system_info = CrystalBuild::Utils::system_info; 54 | is $system_info[1], 'x64'; 55 | }; 56 | }; 57 | 58 | subtest '# x86' => sub { 59 | my $guard = mock_guard('POSIX', { 60 | uname => sub { ('Linux', undef, undef, undef, 'i686') }, 61 | }); 62 | 63 | my @system_info = CrystalBuild::Utils::system_info; 64 | is $system_info[1], 'x86'; 65 | }; 66 | }; 67 | 68 | subtest anomaly => sub { 69 | my $guard = mock_guard('POSIX', { 70 | uname => sub { ('Linux', undef, undef, undef, 'unknown') }, 71 | }); 72 | 73 | dies_ok { CrystalBuild::Utils::system_info }; 74 | }; 75 | 76 | done_testing; 77 | -------------------------------------------------------------------------------- /t/data/archive.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crenv/crystal-build/abe65ce45e47ca1e6d255b6252304802e2f4f8fc/t/data/archive.tar.gz -------------------------------------------------------------------------------- /t/data/crystal-0.7.7-1.tar.gz: -------------------------------------------------------------------------------- 1 | crystal-0.7.4-1/0000775000175000017500000000000012561113337012015 5ustar pinepinecrystal-0.7.4-1/bin/0000775000175000017500000000000012561113303012556 5ustar pinepinecrystal-0.7.4-1/bin/crystal0000775000175000017500000000003212561113303014160 0ustar pinepine#!/bin/bash 2 | 3 | echo crystal 4 | crystal-0.7.4-1/samples/0000775000175000017500000000000012561113337013461 5ustar pinepinecrystal-0.7.4-1/embedded/0000775000175000017500000000000012561113334013543 5ustar pinepinecrystal-0.7.4-1/src/0000775000175000017500000000000012561113314012577 5ustar pinepine -------------------------------------------------------------------------------- /t/data/test.txt: -------------------------------------------------------------------------------- 1 | test 2 | -------------------------------------------------------------------------------- /t/t/create_crenv.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use t::Util; 6 | 7 | subtest basic => sub { 8 | isa_ok create_crenv, 'CrystalBuild'; 9 | }; 10 | 11 | done_testing; 12 | -------------------------------------------------------------------------------- /t/t/uri_for.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use t::Util; 6 | 7 | my $server = create_server; 8 | 9 | subtest basic => sub { 10 | is uri_for('test.txt'), 'http://127.0.0.1:' . $server->port . '/test.txt'; 11 | }; 12 | 13 | done_testing; 14 | -------------------------------------------------------------------------------- /test/run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | exec bats ${CI:+--tap} test 5 | -------------------------------------------------------------------------------- /vendor/lib/Class/Accessor/Lite.pm: -------------------------------------------------------------------------------- 1 | package Class::Accessor::Lite; 2 | 3 | use strict; 4 | 5 | our $VERSION = '0.08'; 6 | 7 | sub croak {require Carp; Carp::croak(@_)} 8 | 9 | sub import { 10 | shift; 11 | my %args = @_; 12 | my $pkg = caller(0); 13 | my %key_ctor = ( 14 | rw => \&_mk_accessors, 15 | ro => \&_mk_ro_accessors, 16 | wo => \&_mk_wo_accessors, 17 | ); 18 | for my $key (sort keys %key_ctor) { 19 | if (defined $args{$key}) { 20 | croak("value of the '$key' parameter should be an arrayref") 21 | unless ref($args{$key}) eq 'ARRAY'; 22 | $key_ctor{$key}->($pkg, @{$args{$key}}); 23 | } 24 | } 25 | _mk_new($pkg) 26 | if $args{new}; 27 | 1; 28 | } 29 | 30 | sub mk_new_and_accessors { 31 | (undef, my @properties) = @_; 32 | my $pkg = caller(0); 33 | _mk_new($pkg); 34 | _mk_accessors($pkg, @properties); 35 | } 36 | 37 | sub mk_new { 38 | my $pkg = caller(0); 39 | _mk_new($pkg); 40 | } 41 | 42 | sub mk_accessors { 43 | (undef, my @properties) = @_; 44 | my $pkg = caller(0); 45 | _mk_accessors($pkg, @properties); 46 | } 47 | 48 | sub mk_ro_accessors { 49 | (undef, my @properties) = @_; 50 | my $pkg = caller(0); 51 | _mk_ro_accessors($pkg, @properties); 52 | } 53 | 54 | sub mk_wo_accessors { 55 | (undef, my @properties) = @_; 56 | my $pkg = caller(0); 57 | _mk_wo_accessors($pkg, @properties); 58 | } 59 | 60 | sub _mk_new { 61 | my $pkg = shift; 62 | no strict 'refs'; 63 | *{$pkg . '::new'} = __m_new($pkg); 64 | } 65 | 66 | sub _mk_accessors { 67 | my $pkg = shift; 68 | no strict 'refs'; 69 | for my $n (@_) { 70 | *{$pkg . '::' . $n} = __m($n); 71 | } 72 | } 73 | 74 | sub _mk_ro_accessors { 75 | my $pkg = shift; 76 | no strict 'refs'; 77 | for my $n (@_) { 78 | *{$pkg . '::' . $n} = __m_ro($pkg, $n); 79 | } 80 | } 81 | 82 | sub _mk_wo_accessors { 83 | my $pkg = shift; 84 | no strict 'refs'; 85 | for my $n (@_) { 86 | *{$pkg . '::' . $n} = __m_wo($pkg, $n); 87 | } 88 | } 89 | 90 | sub __m_new { 91 | my $pkg = shift; 92 | no strict 'refs'; 93 | return sub { 94 | my $klass = shift; 95 | bless { 96 | (@_ == 1 && ref($_[0]) eq 'HASH' ? %{$_[0]} : @_), 97 | }, $klass; 98 | }; 99 | } 100 | 101 | sub __m { 102 | my $n = shift; 103 | sub { 104 | return $_[0]->{$n} if @_ == 1; 105 | return $_[0]->{$n} = $_[1] if @_ == 2; 106 | shift->{$n} = \@_; 107 | }; 108 | } 109 | 110 | sub __m_ro { 111 | my ($pkg, $n) = @_; 112 | sub { 113 | if (@_ == 1) { 114 | return $_[0]->{$n} if @_ == 1; 115 | } else { 116 | my $caller = caller(0); 117 | croak("'$caller' cannot access the value of '$n' on objects of class '$pkg'"); 118 | } 119 | }; 120 | } 121 | 122 | sub __m_wo { 123 | my ($pkg, $n) = @_; 124 | sub { 125 | if (@_ == 1) { 126 | my $caller = caller(0); 127 | croak("'$caller' cannot alter the value of '$n' on objects of class '$pkg'") 128 | } else { 129 | return $_[0]->{$n} = $_[1] if @_ == 2; 130 | shift->{$n} = \@_; 131 | } 132 | }; 133 | } 134 | 135 | 136 | 1; 137 | 138 | __END__ 139 | 140 | =head1 NAME 141 | 142 | Class::Accessor::Lite - a minimalistic variant of Class::Accessor 143 | 144 | =head1 SYNOPSIS 145 | 146 | package MyPackage; 147 | 148 | use Class::Accessor::Lite ( 149 | new => 1, 150 | rw => [ qw(foo bar) ], 151 | ro => [ qw(baz) ], 152 | wo => [ qw(hoge) ], 153 | ); 154 | 155 | =head1 DESCRIPTION 156 | 157 | The module is a variant of C. It is fast and requires less typing, has no dependencies to other modules, and does not mess up the @ISA. 158 | 159 | =head1 THE USE STATEMENT 160 | 161 | The use statement (i.e. the C function) of the module takes a single hash as an argument that specifies the types and the names of the properties. Recognises the following keys. 162 | 163 | =over 4 164 | 165 | =item new => $true_or_false 166 | 167 | the default constructor is created if the value evaluates to true, otherwise nothing is done (the default behaviour) 168 | 169 | =item rw => \@name_of_the_properties 170 | 171 | creates a read / write accessor for the name of the properties passed through as an arrayref 172 | 173 | =item ro => \@name_of_the_properties 174 | 175 | creates a read-only accessor for the name of the properties passed through as an arrayref 176 | 177 | =item wo => \@name_of_the_properties 178 | 179 | creates a write-only accessor for the name of the properties passed through as an arrayref 180 | 181 | =back 182 | 183 | For more detailed explanation read the following section describing the behaviour of each function that actually creates the accessors. 184 | 185 | =head1 FUNCTIONS 186 | 187 | As of version 0.04 the properties can be specified as the arguments to the C statement (as can be seen in the SYNOPSIS) which is now the recommended way of using the module, but for compatibility the following functions are provided as well. 188 | 189 | =head2 Class::Accessor::Lite->mk_accessors(@name_of_the_properties) 190 | 191 | Creates an accessor in current package under the name specified by the arguments that access the properties (of a hashref) with the same name. 192 | 193 | =head2 Class::Accessor::Lite->mk_ro_accessors(@name_of_the_properties) 194 | 195 | Same as mk_accessors() except it will generate read-only accessors (i.e. true accessors). If you attempt to set a value with these accessors it will throw an exception. 196 | 197 | =head2 Class::Accessor::Lite->mk_wo_accessors(@name_of_the_properties) 198 | 199 | Same as mk_accessors() except it will generate write-only accessors (i.e. mutators). If you attempt to read a value with these accessors it will throw an exception. 200 | 201 | =head2 Class::Accessor::Lite->mk_new() 202 | 203 | Creates the C function that accepts a hash or a hashref as the initial properties of the object. 204 | 205 | =head2 Class::Accessor::Lite->mk_new_and_accessors(@name_of_the_properties) 206 | 207 | DEPRECATED. Use the new "use Class::Accessor::Lite (...)" style. 208 | 209 | =head1 FAQ 210 | 211 | =head2 Can I use C in an inherited module? 212 | 213 | Yes in most cases, when the class object in the super class is implemented using a hashref. However you _should_ _not_ create the constructor for the inherited class by calling C<new()>> or by C< 1)>>. The only other thing that C does is to set up the accessor functions for given property names through a blessed hashref. 214 | 215 | =head2 What happens when passing more than one arguments to the accessor? 216 | 217 | When the accessor built by Class::Accessor::Lite is given more than one arguments, a reference to the arguments will be saved as an arrayref. This behaviour might not be necessary but is implemented as is to maintain compatibility with L. 218 | 219 | my @data = (1, 2, 3); 220 | $obj->someproperty(@data); 221 | 222 | $obj->someproperty->[2]++; # $data[3] is incremented 223 | 224 | In general, you should pass an arrayref to set an arrayref to a property. 225 | 226 | my @data = (1, 2, 3); 227 | $obj->someproperty([ @data ]); # save a copy using arrayref 228 | 229 | $obj->someproper->[2]++; # @data is not modified 230 | 231 | =head1 SEE ALSO 232 | 233 | L 234 | 235 | L 236 | 237 | =head1 AUTHORS 238 | 239 | Copyright (C) 2008 - 2010 Kazuho Oku 240 | 241 | =head1 LICENSE 242 | 243 | This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.6 or, at your option, any later version of Perl 5 you may have available. 244 | 245 | =cut 246 | 247 | -------------------------------------------------------------------------------- /vendor/lib/File/Which.pm: -------------------------------------------------------------------------------- 1 | package File::Which; 2 | 3 | use strict; 4 | use warnings; 5 | use Exporter (); 6 | use File::Spec (); 7 | 8 | # ABSTRACT: Perl implementation of the which utility as an API 9 | our $VERSION = '1.19'; # VERSION 10 | 11 | 12 | our @ISA = 'Exporter'; 13 | our @EXPORT = 'which'; 14 | our @EXPORT_OK = 'where'; 15 | 16 | use constant IS_VMS => ($^O eq 'VMS'); 17 | use constant IS_MAC => ($^O eq 'MacOS'); 18 | use constant IS_DOS => ($^O eq 'MSWin32' or $^O eq 'dos' or $^O eq 'os2'); 19 | use constant IS_CYG => ($^O eq 'cygwin'); 20 | 21 | # For Win32 systems, stores the extensions used for 22 | # executable files 23 | # For others, the empty string is used 24 | # because 'perl' . '' eq 'perl' => easier 25 | my @PATHEXT = (''); 26 | if ( IS_DOS ) { 27 | # WinNT. PATHEXT might be set on Cygwin, but not used. 28 | if ( $ENV{PATHEXT} ) { 29 | push @PATHEXT, split ';', $ENV{PATHEXT}; 30 | } else { 31 | # Win9X or other: doesn't have PATHEXT, so needs hardcoded. 32 | push @PATHEXT, qw{.com .exe .bat}; 33 | } 34 | } elsif ( IS_VMS ) { 35 | push @PATHEXT, qw{.exe .com}; 36 | } elsif ( IS_CYG ) { 37 | # See this for more info 38 | # http://cygwin.com/cygwin-ug-net/using-specialnames.html#pathnames-exe 39 | push @PATHEXT, qw{.exe .com}; 40 | } 41 | 42 | 43 | sub which { 44 | my ($exec) = @_; 45 | 46 | return undef unless $exec; 47 | 48 | my $all = wantarray; 49 | my @results = (); 50 | 51 | # check for aliases first 52 | if ( IS_VMS ) { 53 | my $symbol = `SHOW SYMBOL $exec`; 54 | chomp($symbol); 55 | unless ( $? ) { 56 | return $symbol unless $all; 57 | push @results, $symbol; 58 | } 59 | } 60 | if ( IS_MAC ) { 61 | my @aliases = split /\,/, $ENV{Aliases}; 62 | foreach my $alias ( @aliases ) { 63 | # This has not been tested!! 64 | # PPT which says MPW-Perl cannot resolve `Alias $alias`, 65 | # let's just hope it's fixed 66 | if ( lc($alias) eq lc($exec) ) { 67 | chomp(my $file = `Alias $alias`); 68 | last unless $file; # if it failed, just go on the normal way 69 | return $file unless $all; 70 | push @results, $file; 71 | # we can stop this loop as if it finds more aliases matching, 72 | # it'll just be the same result anyway 73 | last; 74 | } 75 | } 76 | } 77 | 78 | return $exec 79 | if !IS_VMS and !IS_MAC and !IS_DOS and $exec =~ /\// and -f $exec and -x $exec; 80 | 81 | my @path = File::Spec->path; 82 | if ( IS_DOS or IS_VMS or IS_MAC ) { 83 | unshift @path, File::Spec->curdir; 84 | } 85 | 86 | foreach my $base ( map { File::Spec->catfile($_, $exec) } @path ) { 87 | for my $ext ( @PATHEXT ) { 88 | my $file = $base.$ext; 89 | 90 | # We don't want dirs (as they are -x) 91 | next if -d $file; 92 | 93 | if ( 94 | # Executable, normal case 95 | -x _ 96 | or ( 97 | # MacOS doesn't mark as executable so we check -e 98 | IS_MAC 99 | || 100 | ( 101 | ( IS_DOS or IS_CYG ) 102 | and 103 | grep { 104 | $file =~ /$_\z/i 105 | } @PATHEXT[1..$#PATHEXT] 106 | ) 107 | # DOSish systems don't pass -x on 108 | # non-exe/bat/com files. so we check -e. 109 | # However, we don't want to pass -e on files 110 | # that aren't in PATHEXT, like README. 111 | and -e _ 112 | ) 113 | ) { 114 | return $file unless $all; 115 | push @results, $file; 116 | } 117 | } 118 | } 119 | 120 | if ( $all ) { 121 | return @results; 122 | } else { 123 | return undef; 124 | } 125 | } 126 | 127 | 128 | sub where { 129 | # force wantarray 130 | my @res = which($_[0]); 131 | return @res; 132 | } 133 | 134 | 1; 135 | 136 | __END__ 137 | 138 | =pod 139 | 140 | =encoding UTF-8 141 | 142 | =head1 NAME 143 | 144 | File::Which - Perl implementation of the which utility as an API 145 | 146 | =head1 VERSION 147 | 148 | version 1.19 149 | 150 | =head1 SYNOPSIS 151 | 152 | use File::Which; # exports which() 153 | use File::Which qw(which where); # exports which() and where() 154 | 155 | my $exe_path = which 'perldoc'; 156 | 157 | my @paths = where 'perl'; 158 | # Or 159 | my @paths = which 'perl'; # an array forces search for all of them 160 | 161 | =head1 DESCRIPTION 162 | 163 | L finds the full or relative paths to executable programs on 164 | the system. This is normally the function of C utility. C is 165 | typically implemented as either a program or a built in shell command. On 166 | some platforms, such as Microsoft Windows it is not provided as part of the 167 | core operating system. This module provides a consistent API to this 168 | functionality regardless of the underlying platform. 169 | 170 | The focus of this module is correctness and portability. As a consequence 171 | platforms where the current directory is implicitly part of the search path 172 | such as Microsoft Windows will find executables in the current directory, 173 | whereas on platforms such as UNIX where this is not the case executables 174 | in the current directory will only be found if the current directory is 175 | explicitly added to the path. 176 | 177 | If you need a portable C on the command line in an environment that 178 | does not provide it, install L which provides a command line 179 | interface to this API. 180 | 181 | =head2 Implementations 182 | 183 | L searches the directories of the user's C (the current 184 | implementation uses L to determine the correct C), 185 | looking for executable files having the name specified as a parameter to 186 | L. Under Win32 systems, which do not have a notion of directly 187 | executable files, but uses special extensions such as C<.exe> and C<.bat> 188 | to identify them, C takes extra steps to assure that 189 | you will find the correct file (so for example, you might be searching for 190 | C, it'll try F, F, etc.) 191 | 192 | =head3 Linux, *BSD and other UNIXes 193 | 194 | There should not be any surprises here. The current directory will not be 195 | searched unless it is explicitly added to the path. 196 | 197 | =head3 Modern Windows (including NT, XP, Vista, 7, 8, 10 etc) 198 | 199 | Windows NT has a special environment variable called C, which is used 200 | by the shell to look for executable files. Usually, it will contain a list in 201 | the form C<.EXE;.BAT;.COM;.JS;.VBS> etc. If C finds such an 202 | environment variable, it parses the list and uses it as the different 203 | extensions. 204 | 205 | =head3 Cygwin 206 | 207 | Cygwin provides a Unix-like environment for Microsoft Windows users. In most 208 | ways it works like other Unix and Unix-like environments, but in a few key 209 | aspects it works like Windows. As with other Unix environments, the current 210 | directory is not included in the search unless it is explicitly included in 211 | the search path. Like on Windows, files with C<.EXE> or <.BAT> extensions will 212 | be discovered even if they are not part of the query. C<.COM> or extensions 213 | specified using the C environment variable will NOT be discovered 214 | without the fully qualified name, however. 215 | 216 | =head3 Windows 95, 98, ME, MS-DOS, OS/2 217 | 218 | This set of operating systems don't have the C variable, and usually 219 | you will find executable files there with the extensions C<.exe>, C<.bat> and 220 | (less likely) C<.com>. C uses this hardcoded list if it's running 221 | under Win32 but does not find a C variable. 222 | 223 | As of 2015 none of these platforms are tested frequently (or perhaps ever), 224 | but the current maintainer is determined not to intentionally remove support 225 | for older operating systems. 226 | 227 | =head3 VMS 228 | 229 | Same case as Windows 9x: uses C<.exe> and C<.com> (in that order). 230 | 231 | As of 2015 the current maintainer does not test on VMS, and is in fact not 232 | certain it has ever been tested on VMS. If this platform is important to you 233 | and you can help me verify and or support it on that platform please contact 234 | me. 235 | 236 | =head1 FUNCTIONS 237 | 238 | =head2 which 239 | 240 | my $path = which $short_exe_name; 241 | my @paths = which $short_exe_name; 242 | 243 | Exported by default. 244 | 245 | C<$short_exe_name> is the name used in the shell to call the program (for 246 | example, C). 247 | 248 | If it finds an executable with the name you specified, C will return 249 | the absolute path leading to this executable (for example, F or 250 | F). 251 | 252 | If it does I find the executable, it returns C. 253 | 254 | If C is called in list context, it will return I the 255 | matches. 256 | 257 | =head2 where 258 | 259 | my @paths = where $short_exe_name; 260 | 261 | Not exported by default. 262 | 263 | Same as L in array context. Same as the 264 | C utility, will return an array containing all the path names 265 | matching C<$short_exe_name>. 266 | 267 | =head1 CAVEATS 268 | 269 | This module has no non-core requirements for Perl 5.6.2 and better. 270 | 271 | This module is fully supported back to Perl 5.8.1. It may work on 5.8.0. 272 | It should work on Perl 5.6.x and I may even test on 5.6.2. I will accept 273 | patches to maintain compatibility for such older Perls, but you may 274 | need to fix it on 5.6.x / 5.8.0 and send me a patch. 275 | 276 | Not tested on VMS although there is platform specific code 277 | for those. Anyone who haves a second would be very kind to send me a 278 | report of how it went. 279 | 280 | =head1 SUPPORT 281 | 282 | Bugs should be reported via the GitHub issue tracker 283 | 284 | L 285 | 286 | For other issues, contact the maintainer. 287 | 288 | =head1 SEE ALSO 289 | 290 | =over 4 291 | 292 | =item L, L 293 | 294 | Command line interface to this module. 295 | 296 | =item L 297 | 298 | Comes with a C function with slightly different semantics that 299 | the traditional UNIX where. It will find executables in the current 300 | directory, even though the current directory is not searched for by 301 | default on Unix. 302 | 303 | =item L 304 | 305 | This module purports to "check that a command is available", but does not 306 | provide any documentation on how you might use it. 307 | 308 | =back 309 | 310 | =head1 AUTHORS 311 | 312 | =over 4 313 | 314 | =item * 315 | 316 | Per Einar Ellefsen 317 | 318 | =item * 319 | 320 | Adam Kennedy 321 | 322 | =item * 323 | 324 | Graham Ollis 325 | 326 | =back 327 | 328 | =head1 COPYRIGHT AND LICENSE 329 | 330 | This software is copyright (c) 2002 by Per Einar Ellefsen . 331 | 332 | This is free software; you can redistribute it and/or modify it under 333 | the same terms as the Perl 5 programming language system itself. 334 | 335 | =cut 336 | -------------------------------------------------------------------------------- /vendor/lib/HTTP/Command/Wrapper.pm: -------------------------------------------------------------------------------- 1 | package HTTP::Command::Wrapper; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | our $VERSION = "0.04"; 7 | 8 | use File::Which qw/which/; 9 | 10 | use HTTP::Command::Wrapper::Curl; 11 | use HTTP::Command::Wrapper::Wget; 12 | 13 | sub create { 14 | my $class = shift; 15 | my $opt = {}; 16 | my $type = undef; 17 | 18 | $opt = $_[0] if ref $_[0] eq 'HASH'; 19 | $opt = $_[1] if ref $_[1] eq 'HASH'; 20 | 21 | $type = $_[0] unless ref $_[0]; 22 | $type = $class->_detect_type unless defined $type; 23 | 24 | return HTTP::Command::Wrapper::Curl->new($opt) if $type eq 'curl'; 25 | return HTTP::Command::Wrapper::Wget->new($opt) if $type eq 'wget'; 26 | 27 | die 'Command not detected (curl or wget)'; 28 | } 29 | 30 | sub _detect_type { 31 | my $class = shift; 32 | 33 | return 'curl' if $class->_which('curl'); 34 | return 'wget' if $class->_which('wget'); 35 | return undef; 36 | } 37 | 38 | sub _which { which($_[1]) } 39 | 40 | 1; 41 | __END__ 42 | 43 | =encoding utf-8 44 | 45 | =head1 NAME 46 | 47 | HTTP::Command::Wrapper - The command based HTTP client (wget/curl wrapper). Too minimum dependencies! 48 | 49 | =head1 SYNOPSIS 50 | 51 | use HTTP::Command::Wrapper; 52 | 53 | my $client = HTTP::Command::Wrapper->create; # auto detecting (curl or wget) 54 | my $content = $client->fetch('https://github.com/'); 55 | 56 | print "$content\n"; 57 | 58 | =head1 DESCRIPTION 59 | 60 | HTTP::Command::Wrapper is a very simple HTTP client module. 61 | It can wrap C or C command, and can use same interface. 62 | 63 | =head1 METHODS 64 | 65 | =head2 CLASS METHODS 66 | 67 | =head3 C 68 | 69 | Create new wrapper instance using automatic commands detecting. 70 | 71 | =head3 C 72 | 73 | Create new wrapper instance. C<'wget'> or C<'curl'> can be specified as C<$type> value. 74 | 75 | =head2 METHODS 76 | 77 | =head3 C 78 | 79 | Fetch http/https contents from C<$url>. Return a content body as string. 80 | 81 | =head3 C 82 | 83 | Return true if C<$url> contents can fetch (status code is C<200>). 84 | 85 | =head3 C 86 | 87 | Fetch http/https contents from C<$url>. Save in file. Return process exit code as boolean. 88 | 89 | =head1 LICENSE 90 | 91 | The MIT License (MIT) 92 | 93 | Copyright (c) 2015 Pine Mizune 94 | 95 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 96 | 97 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 98 | 99 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 100 | 101 | =head1 AUTHOR 102 | 103 | Pine Mizune Epinemz@gmail.comE 104 | 105 | =cut 106 | 107 | -------------------------------------------------------------------------------- /vendor/lib/HTTP/Command/Wrapper/Curl.pm: -------------------------------------------------------------------------------- 1 | package HTTP::Command::Wrapper::Curl; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | sub new { 7 | my ($class, $opt) = @_; 8 | return bless { opt => $opt } => $class; 9 | } 10 | 11 | sub fetch_able { 12 | my ($self, $url, $headers) = @_; 13 | 14 | `curl -LIs @{[$self->_headers($headers)]} "$url"` =~ m/200 OK/; 15 | } 16 | 17 | sub fetch { 18 | my ($self, $url, $headers) = @_; 19 | 20 | `curl -Ls @{[$self->_headers($headers)]} "$url"`; 21 | } 22 | 23 | sub download { 24 | my ($self, $url, $path, $headers) = @_; 25 | 26 | system("curl -L @{[$self->_headers($headers)]} \"$url\" -o \"$path\"") == 0; 27 | } 28 | 29 | sub _headers { 30 | my ($self, $headers) = @_; 31 | $headers = [] unless defined $headers; 32 | 33 | return join(' ', map { "-H \"$_\"" } @$headers); 34 | } 35 | 36 | 1; 37 | -------------------------------------------------------------------------------- /vendor/lib/HTTP/Command/Wrapper/Wget.pm: -------------------------------------------------------------------------------- 1 | package HTTP::Command::Wrapper::Wget; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | sub new { 7 | my ($class, $opt) = @_; 8 | return bless { opt => $opt } => $class; 9 | } 10 | 11 | sub fetch_able { 12 | my ($self, $url, $headers) = @_; 13 | 14 | `wget @{[$self->_headers($headers)]} -Sq --spider "$url" 2>&1` =~ m/200 OK/; 15 | } 16 | 17 | sub fetch { 18 | my ($self, $url, $headers) = @_; 19 | 20 | `wget @{[$self->_headers($headers)]} -q $url -O -`; 21 | } 22 | 23 | sub download { 24 | my ($self, $url, $path, $headers) = @_; 25 | 26 | system(qq{wget -c @{[$self->_headers($headers)]} "$url" -O "$path"}) == 0; 27 | } 28 | 29 | sub _headers { 30 | my ($self, $headers) = @_; 31 | $headers = [] unless defined $headers; 32 | 33 | return join(' ', map { "--header=\"$_\"" } @$headers); 34 | } 35 | 36 | 1; 37 | -------------------------------------------------------------------------------- /vendor/lib/JSON/PP/Boolean.pm: -------------------------------------------------------------------------------- 1 | =head1 NAME 2 | 3 | JSON::PP::Boolean - dummy module providing JSON::PP::Boolean 4 | 5 | =head1 SYNOPSIS 6 | 7 | # do not "use" yourself 8 | 9 | =head1 DESCRIPTION 10 | 11 | This module exists only to provide overload resolution for Storable and similar modules. See 12 | L for more info about this class. 13 | 14 | =cut 15 | 16 | use JSON::PP (); 17 | use strict; 18 | 19 | 1; 20 | 21 | =head1 AUTHOR 22 | 23 | This idea is from L written by Marc Lehmann 24 | 25 | =cut 26 | 27 | -------------------------------------------------------------------------------- /vendor/lib/Mac/OSVersion/Lite.pm: -------------------------------------------------------------------------------- 1 | package Mac::OSVersion::Lite; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | our $VERSION = "0.07"; 7 | 8 | use constant VERSION_FORMAT => qr/(?[0-9]+)(?:\.(?[0-9]+))?(?:\.(?[0-9]+))?/; 9 | use constant MAC_VERSION_NAMES => { 10 | high_sierra => "10.13", 11 | sierra => "10.12", 12 | el_capitan => "10.11", 13 | yosemite => "10.10", 14 | mavericks => "10.9", 15 | mountain_lion => "10.8", 16 | lion => "10.7", 17 | snow_leopard => "10.6", 18 | leopard => "10.5", 19 | tiger => "10.4", 20 | }; 21 | 22 | use overload ( 23 | q{""} => \&as_string, 24 | q{<=>} => \&_cmp, 25 | fallback => 1, 26 | ); 27 | 28 | sub major { shift->{major} } 29 | sub minor { shift->{minor} } 30 | 31 | sub new { 32 | my $class = shift; 33 | my $self = bless {} => $class; 34 | 35 | $self->_init_by_current_version if @_ == 0; 36 | $self->_init_by_version_string(@_) if @_ == 1; 37 | $self->_init_by_version_numbers(@_) if @_ >= 2; 38 | 39 | return $self; 40 | } 41 | 42 | sub _init_by_current_version { 43 | my $self = shift; 44 | my $command = '/usr/bin/sw_vers -productVersion'; 45 | my $version = `$command`; 46 | 47 | die "Command \`$command\` failed: $version (exit code: $?)\n" if $? != 0; 48 | 49 | $self->_init_by_version_string($version); 50 | } 51 | 52 | sub _init_by_version_string { 53 | my ($self, $string) = @_; 54 | 55 | if (defined MAC_VERSION_NAMES->{$string}) { 56 | $string = MAC_VERSION_NAMES->{$string}; 57 | } 58 | 59 | die "Invalid format: $string\n" unless $string =~ qr/^@{[VERSION_FORMAT]}$/; 60 | 61 | $self->{major} = $+{major}; 62 | $self->{minor} = $+{minor} // 0; 63 | } 64 | 65 | sub _init_by_version_numbers { 66 | my ($self, $major, $minor) = @_; 67 | 68 | $self->{major} = $major // 0; 69 | $self->{minor} = $minor // 0; 70 | } 71 | 72 | sub name { 73 | my $self = shift; 74 | my %map = reverse %{ MAC_VERSION_NAMES() }; 75 | return $map{$self->as_string}; 76 | } 77 | 78 | sub as_string { 79 | my $self = shift; 80 | return $self->{major}.'.'.$self->{minor}; 81 | } 82 | 83 | sub _cmp { 84 | my ($self, $other) = @_; 85 | 86 | return $self->{major} <=> $other->{major} if $self->{major} != $other->{major}; 87 | return $self->{minor} <=> $other->{minor}; 88 | } 89 | 90 | 1; 91 | __END__ 92 | 93 | =encoding utf-8 94 | 95 | =head1 NAME 96 | 97 | Mac::OSVersion::Lite - It's the lightweight version object for Mac OS X 98 | 99 | =head1 SYNOPSIS 100 | 101 | use Mac::OSVersion::Lite; 102 | use feature qw/say/; 103 | 104 | my $version = Mac::OSVersion::Lite->new; 105 | say $version->major; # 10 106 | say $version->minor; # 11 107 | say $version->name; # el_capitan 108 | 109 | =head1 DESCRIPTION 110 | 111 | Mac::OSVersion::Lite is the lightweight version object for Mac OS X with auto detection. 112 | 113 | =head1 METHODS 114 | 115 | =head2 CLASS METHODS 116 | 117 | =head3 C 118 | 119 | Create new C instance with auto detection. 120 | 121 | =head3 C 122 | 123 | Create new C instance from a version string. 124 | Cnew('10.11')> equals Cnew(10, 11)>. 125 | 126 | =head3 C 127 | 128 | Create new C instance from version numbers. 129 | Cnew(10, 11)> equals Cnew('10.11')>. 130 | 131 | =head2 METHODS 132 | 133 | =head3 C 134 | 135 | Get the major version number. 136 | 137 | =head3 C 138 | 139 | Return the minor version number. 140 | 141 | =head3 C=E> 142 | 143 | Compare two C instances. 144 | 145 | =head3 C<""> 146 | 147 | Convert a C instance to string. 148 | 149 | =head3 C 150 | 151 | Convert a C instance to string. 152 | 153 | =head1 SEE ALSO 154 | 155 | =over 156 | 157 | =item * L 158 | 159 | =back 160 | 161 | =head1 LICENSE 162 | 163 | The MIT License (MIT) 164 | 165 | Copyright (c) 2017 Pine Mizune 166 | 167 | Permission is hereby granted, free of charge, to any person obtaining a copy 168 | of this software and associated documentation files (the "Software"), to deal 169 | in the Software without restriction, including without limitation the rights 170 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 171 | copies of the Software, and to permit persons to whom the Software is 172 | furnished to do so, subject to the following conditions: 173 | 174 | The above copyright notice and this permission notice shall be included in 175 | all copies or substantial portions of the Software. 176 | 177 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 178 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 179 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 180 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 181 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 182 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 183 | THE SOFTWARE. 184 | 185 | =head1 AUTHOR 186 | 187 | Pine Mizune Epinemz@gmail.comE 188 | 189 | =cut 190 | 191 | -------------------------------------------------------------------------------- /vendor/lib/SemVer/V2/Strict.pm: -------------------------------------------------------------------------------- 1 | package SemVer::V2::Strict; 2 | use strict; 3 | use warnings; 4 | use utf8; 5 | 6 | our $VERSION = '0.10'; 7 | 8 | use constant PRE_RELEASE_FORMAT => qr/(?:-(?[a-zA-Z0-9.\-]+))?/; 9 | use constant BUILD_METADATA_FORMAT => qr/(?:\+(?[a-zA-Z0-9.\-]+))?/; 10 | use constant VERSION_FORMAT => 11 | qr/(?[0-9]+)(?:\.(?[0-9]+))?(?:\.(?[0-9]+))?@{[PRE_RELEASE_FORMAT]}@{[BUILD_METADATA_FORMAT]}/; 12 | 13 | use List::Util qw/min max/; 14 | use Scalar::Util qw/looks_like_number/; 15 | 16 | use overload ( 17 | q{""} => \&as_string, 18 | q{<=>} => \&_cmp, 19 | fallback => 1, 20 | ); 21 | 22 | sub major { shift->{major} } 23 | sub minor { shift->{minor} } 24 | sub patch { shift->{patch} } 25 | sub pre_release { shift->{pre_release} } 26 | sub build_metadata { shift->{build_metadata} } 27 | 28 | sub new { 29 | my $class = shift; 30 | my $self = bless {} => $class; 31 | 32 | $self->_init_by_version_numbers if @_ == 0; 33 | $self->_init_by_version_string(@_) if @_ == 1; 34 | $self->_init_by_version_numbers(@_) if @_ >= 2; 35 | 36 | return $self; 37 | } 38 | 39 | sub _init_by_version_string { 40 | my ($self, $version) = @_; 41 | 42 | die 'Invalid format' unless $version =~ qr/^@{[VERSION_FORMAT]}$/; 43 | 44 | $self->{major} = $+{major}; 45 | $self->{minor} = $+{minor} // 0; 46 | $self->{patch} = $+{patch} // 0; 47 | $self->{pre_release} = $+{pre_release}; 48 | $self->{build_metadata} = $+{build_metadata}; 49 | } 50 | 51 | sub _init_by_version_numbers { 52 | my ($self, $major, $minor, $patch, $pre_release, $build_metadata) = @_; 53 | 54 | $self->{major} = $major // 0; 55 | $self->{minor} = $minor // 0; 56 | $self->{patch} = $patch // 0; 57 | $self->{pre_release} = $pre_release; 58 | $self->{build_metadata} = $build_metadata; 59 | } 60 | 61 | sub clean { 62 | my ($class, $version) = @_; 63 | 64 | $version =~ s/^\s*(.*?)\s*$/$1/; # trim 65 | $version =~ s/^[=v]+//; 66 | 67 | return eval { $class->new($version)->as_string }; 68 | } 69 | 70 | 71 | sub as_string { 72 | my $self = shift; 73 | 74 | my $string = $self->major.'.'.$self->minor.'.'.$self->patch; 75 | $string .= '-'.$self->pre_release if $self->pre_release; 76 | $string .= '+'.$self->build_metadata if $self->build_metadata; 77 | 78 | return $string; 79 | } 80 | 81 | sub _cmp { 82 | my ($self, $other) = @_; 83 | 84 | return $self->major <=> $other->major if $self->major != $other->major; 85 | return $self->minor <=> $other->minor if $self->minor != $other->minor; 86 | return $self->patch <=> $other->patch if $self->patch != $other->patch; 87 | return _compare_pre_release($self->pre_release, $other->pre_release); 88 | } 89 | 90 | sub _compare_pre_release { 91 | my ($a, $b) = @_; 92 | 93 | return 1 if !defined $a && $b; 94 | return -1 if $a && !defined $b; 95 | 96 | if ($a && $b) { 97 | my @left = split /-|\./, $a; 98 | my @right = split /-|\./, $b; 99 | my $max = max(scalar @left, scalar @right); 100 | 101 | for (my $i = 0; $i < $max; ++$i) { 102 | my $a = $left[$i] // 0; 103 | my $b = $right[$i] // 0; 104 | 105 | if (looks_like_number($a) && looks_like_number($b)) { 106 | return $a <=> $b if $a != $b; 107 | } 108 | 109 | my $min = min(length $a, length $b); 110 | for (my $n = 0; $n < $min; ++$n) { 111 | my $c = substr($a, $n, 1) cmp substr($b, $n, 1); 112 | return $c if $c != 0; 113 | } 114 | } 115 | } 116 | 117 | return 0; 118 | } 119 | 120 | 1; 121 | __END__ 122 | 123 | =encoding utf-8 124 | 125 | =head1 NAME 126 | 127 | SemVer::V2::Strict - Semantic version v2.0 object for Perl 128 | 129 | =head1 SYNOPSIS 130 | 131 | use SemVer::V2::Strict; 132 | 133 | my $v1 = SemVer::V2::Strict->new('1.0.2'); 134 | my $v2 = SemVer::V2::Strict->new('2.0.0-alpha.10'); 135 | 136 | if ($v1 < $v2) { 137 | print "$v1 < $v2\n"; # => '1.0.2 < 2.0.0-alpha.10' 138 | } 139 | 140 | =head1 DESCRIPTION 141 | 142 | This module subclasses version to create semantic versions, as defined by the L Specification. 143 | 144 | =head1 METHODS 145 | 146 | =head2 CLASS METHODS 147 | 148 | =head3 C 149 | 150 | Create new empty C instance. 151 | Cnew()> equals Cnew('0.0.0')>. 152 | 153 | =head3 C 154 | 155 | Create new C instance from a version string. 156 | Cnew('1.0.0')> equals Cnew(1, 0, 0)>. 157 | 158 | =head3 C 159 | 160 | Create new C instance from version numbers. 161 | Cnew('1.0.0-alpha+100')> equals Cnew(1, 0, 0, 'alpha', '100')>. 162 | 163 | =head3 C 164 | 165 | Clean version string. Trim spaces and C<'v'> prefix. 166 | 167 | =head2 METHODS 168 | 169 | =head3 C 170 | 171 | Get the major version number. 172 | 173 | =head3 C 174 | 175 | Return the minor version number. 176 | 177 | =head3 C 178 | 179 | Return the patch version number. 180 | 181 | =head3 C 182 | 183 | Return the pre_release string. 184 | 185 | =head3 C 186 | 187 | Return the build_metadata string. 188 | 189 | =head3 C=E> 190 | 191 | Compare two C instances. 192 | 193 | =head3 C<""> 194 | 195 | Convert a C instance to string. 196 | 197 | =head1 SEE ALSO 198 | 199 | =over 200 | 201 | =item * L 202 | 203 | =item * L 204 | 205 | =back 206 | 207 | =head1 LICENSE 208 | 209 | The MIT License (MIT) 210 | 211 | Copyright (c) 2015 Pine Mizune 212 | 213 | Permission is hereby granted, free of charge, to any person obtaining a copy 214 | of this software and associated documentation files (the "Software"), to deal 215 | in the Software without restriction, including without limitation the rights 216 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 217 | copies of the Software, and to permit persons to whom the Software is 218 | furnished to do so, subject to the following conditions: 219 | 220 | The above copyright notice and this permission notice shall be included in 221 | all copies or substantial portions of the Software. 222 | 223 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 224 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 225 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 226 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 227 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 228 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 229 | THE SOFTWARE. 230 | 231 | =head1 ACKNOWLEDGEMENT 232 | 233 | C is based from rosylilly's L. 234 | Thank you. 235 | 236 | =head1 AUTHOR 237 | 238 | Pine Mizune Epinemz@gmail.comE 239 | 240 | =cut 241 | 242 | -------------------------------------------------------------------------------- /vendor/lib/Text/Caml.pm: -------------------------------------------------------------------------------- 1 | package Text::Caml; 2 | 3 | use strict; 4 | use warnings; 5 | 6 | require Carp; 7 | require Scalar::Util; 8 | use File::Spec (); 9 | 10 | our $VERSION = '0.14'; 11 | 12 | our $LEADING_SPACE = qr/(?:\n [ ]*)?/x; 13 | our $TRAILING_SPACE = qr/(?:[ ]* \n)?/x; 14 | our $START_TAG = qr/\{\{/x; 15 | our $END_TAG = qr/\}\}/x; 16 | 17 | our $START_OF_PARTIAL = quotemeta '>'; 18 | our $START_OF_SECTION = quotemeta '#'; 19 | our $START_OF_INVERTED_SECTION = quotemeta '^'; 20 | our $END_OF_SECTION = quotemeta '/'; 21 | our $START_OF_TEMPLATE_INHERITANCE = quotemeta '<'; 22 | our $END_OF_TEMPLATE_INHERITANCE = quotemeta '/'; 23 | our $START_OF_BLOCK = quotemeta '$'; 24 | our $END_OF_BLOCK = quotemeta '/'; 25 | 26 | sub new { 27 | my $class = shift; 28 | my (%params) = @_; 29 | 30 | my $self = {}; 31 | bless $self, $class; 32 | 33 | $self->{templates_path} = $params{templates_path}; 34 | $self->{default_partial_extension} = $params{default_partial_extension}; 35 | 36 | $self->set_templates_path('.') 37 | unless $self->templates_path; 38 | 39 | return $self; 40 | } 41 | 42 | sub templates_path { $_[0]->{templates_path} } 43 | sub set_templates_path { $_[0]->{templates_path} = $_[1] } 44 | 45 | sub render { 46 | my $self = shift; 47 | my $template = shift; 48 | my $context = ref $_[0] eq 'HASH' ? $_[0] : {@_}; 49 | 50 | $self->_parse($template, $context); 51 | } 52 | 53 | sub render_file { 54 | my $self = shift; 55 | my $template = shift; 56 | my $context = ref $_[0] eq 'HASH' ? $_[0] : {@_}; 57 | 58 | $template = $self->_slurp_template($template); 59 | return $self->_parse($template, $context); 60 | } 61 | 62 | sub _parse { 63 | my $self = shift; 64 | my $template = shift; 65 | my $context = shift; 66 | my $override = shift; 67 | 68 | my $output = ''; 69 | 70 | pos $template = 0; 71 | while (pos $template < length $template) { 72 | if ($template =~ m/($LEADING_SPACE)?\G $START_TAG /gcxms) { 73 | my $chunk = ''; 74 | 75 | my $leading_newline = !!$1; 76 | 77 | # Tripple 78 | if ($template =~ m/\G { (.*?) } $END_TAG/gcxms) { 79 | $chunk .= $self->_parse_tag($1, $context); 80 | } 81 | 82 | # Replace 83 | elsif ($template =~ m/\G - (.*?) $END_TAG/gcxms) { 84 | $chunk .= '{{' . $1 . '}}'; 85 | } 86 | 87 | # Comment 88 | elsif ($template =~ m/\G ! .*? $END_TAG/gcxms) { 89 | } 90 | 91 | # Section 92 | elsif ($template 93 | =~ m/\G $START_OF_SECTION \s* (.*?) \s* $END_TAG ($TRAILING_SPACE)?/gcxms 94 | ) 95 | { 96 | my $name = $1; 97 | my $end_of_section = $name; 98 | 99 | if ($template 100 | =~ m/\G (.*?) ($LEADING_SPACE)? $START_TAG $END_OF_SECTION $end_of_section $END_TAG ($TRAILING_SPACE)?/gcxms 101 | ) 102 | { 103 | $chunk .= $self->_parse_section($name, $1, $context); 104 | } 105 | else { 106 | Carp::croak("Section's '$name' end not found"); 107 | } 108 | } 109 | 110 | # Inverted section 111 | elsif ($template 112 | =~ m/\G $START_OF_INVERTED_SECTION (.*?) $END_TAG ($TRAILING_SPACE)?/gcxms 113 | ) 114 | { 115 | my $name = $1; 116 | 117 | if ($template 118 | =~ m/ \G (.*?) ($LEADING_SPACE)? $START_TAG $END_OF_SECTION $name $END_TAG ($TRAILING_SPACE)?/gcxms 119 | ) 120 | { 121 | $chunk 122 | .= $self->_parse_inverted_section($name, $1, $context); 123 | } 124 | else { 125 | Carp::croak("Section's '$name' end not found"); 126 | } 127 | } 128 | 129 | # End of section 130 | elsif ($template =~ m/\G $END_OF_SECTION (.*?) $END_TAG/gcxms) { 131 | Carp::croak("Unexpected end of section '$1'"); 132 | } 133 | 134 | # Partial 135 | elsif ($template =~ m/\G $START_OF_PARTIAL \s* (.*?) \s* $END_TAG/gcxms) { 136 | $chunk .= $self->_parse_partial($1, $context); 137 | } 138 | 139 | # Inherited template 140 | elsif ($template =~ m/\G $START_OF_TEMPLATE_INHERITANCE \s* (.*?) \s* $END_TAG/gcxms) 141 | { 142 | my $name = $1; 143 | my $end_of_inherited_template = $name; 144 | 145 | if ($template 146 | =~ m/\G (.*?) ($LEADING_SPACE)? $START_TAG $END_OF_TEMPLATE_INHERITANCE $end_of_inherited_template $END_TAG ($TRAILING_SPACE)?/gcxms 147 | ) 148 | { 149 | $chunk .= $self->_parse_inherited_template($name, $1, $context); 150 | } 151 | else { 152 | Carp::croak("Nested template's '$name' end not found"); 153 | } 154 | } 155 | 156 | # block 157 | elsif ($template =~ m/\G $START_OF_BLOCK \s* (.*?) \s* $END_TAG/gcxms) { 158 | my $name = $1; 159 | my $end_of_block = $name; 160 | 161 | if ($template 162 | =~ m/\G (.*?) ($LEADING_SPACE)? $START_TAG $END_OF_BLOCK $end_of_block $END_TAG/gcxms 163 | ) 164 | { 165 | $chunk .= $self->_parse_block($name, $1, $context, $override); 166 | } 167 | else { 168 | Carp::croak("Block's '$name' end not found"); 169 | } 170 | } 171 | 172 | # Tag 173 | elsif ($template =~ m/\G (.*?) $END_TAG/gcxms) { 174 | $chunk .= $self->_parse_tag_escaped($1, $context); 175 | } 176 | else { 177 | Carp::croak("Can't find where tag is closed"); 178 | } 179 | 180 | if ($chunk ne '') { 181 | $output .= $chunk; 182 | } 183 | elsif ($output eq '' || $leading_newline) { 184 | if ($template =~ m/\G $TRAILING_SPACE/gcxms) { 185 | $output =~ s/[ ]*\z//xms; 186 | } 187 | } 188 | } 189 | 190 | # Text before tag 191 | elsif ($template =~ m/\G (.*?) (?=$START_TAG\{?)/gcxms) { 192 | $output .= $1; 193 | } 194 | 195 | # Other text 196 | else { 197 | $output .= substr($template, pos($template)); 198 | last; 199 | } 200 | } 201 | 202 | return $output; 203 | } 204 | 205 | sub _parse_tag { 206 | my $self = shift; 207 | my ($name, $context) = @_; 208 | 209 | my $value; 210 | my %args; 211 | 212 | # Current element 213 | if ($name eq '.') { 214 | return '' if $self->_is_empty($context, $name); 215 | 216 | $value = $context->{$name}; 217 | } 218 | 219 | else { 220 | $value = $self->_get_value($context, $name); 221 | } 222 | 223 | if (ref $value eq 'CODE') { 224 | my $content = $value->($self, '', $context); 225 | $content = '' unless defined $content; 226 | return $self->_parse($content, $context); 227 | } 228 | 229 | return $value; 230 | } 231 | 232 | sub _find_value { 233 | my $self = shift; 234 | my ($context, $name) = @_; 235 | 236 | my @parts = split /\./ => $name; 237 | 238 | my $value = $context; 239 | 240 | foreach my $part (@parts) { 241 | if ( ref $value eq "HASH" 242 | && exists $value->{'_with'} 243 | && Scalar::Util::blessed($value->{'_with'}) 244 | && $value->{'_with'}->can($part)) 245 | { 246 | $value = $value->{'_with'}->$part; 247 | next; 248 | } 249 | 250 | if( ref $value eq "ARRAY" ) { 251 | $value = $value->[$part]; 252 | next; 253 | } 254 | 255 | if ( exists $value->{'.'} 256 | && Scalar::Util::blessed($value->{'.'}) 257 | && $value->{'.'}->can($part)) 258 | { 259 | $value = $value->{'.'}->$part; 260 | next; 261 | } 262 | 263 | return undef if $self->_is_empty($value, $part); 264 | $value = 265 | Scalar::Util::blessed($value) ? $value->$part : $value->{$part}; 266 | } 267 | 268 | return \$value; 269 | } 270 | 271 | sub _get_value { 272 | my $self = shift; 273 | my ($context, $name) = @_; 274 | 275 | if ($name eq '.') { 276 | return '' if $self->_is_empty($context, $name); 277 | return $context->{$name}; 278 | } 279 | 280 | my $value = $self->_find_value($context, $name); 281 | 282 | return $value ? $$value : ''; 283 | } 284 | 285 | sub _parse_tag_escaped { 286 | my $self = shift; 287 | my ($tag, $context) = @_; 288 | 289 | my $do_not_escape; 290 | if ($tag =~ s/\A \&//xms) { 291 | $do_not_escape = 1; 292 | } 293 | 294 | my $output = $self->_parse_tag($tag, $context); 295 | 296 | $output = $self->_escape($output) unless $do_not_escape; 297 | 298 | return $output; 299 | } 300 | 301 | sub _parse_section { 302 | my $self = shift; 303 | my ($name, $template, $context) = @_; 304 | 305 | my $value = $self->_get_value($context, $name); 306 | 307 | my $output = ''; 308 | 309 | if (ref $value eq 'HASH') { 310 | $output .= $self->_parse($template, {%$context, %$value}); 311 | } 312 | elsif (ref $value eq 'ARRAY') { 313 | my $idx = 0; 314 | foreach my $el (@$value) { 315 | my %subcontext = ref $el eq 'HASH' ? %$el : ('.' => $el); 316 | $subcontext{'_idx'} = $idx; 317 | 318 | $subcontext{'_even'} = $idx % 2 == 0; 319 | $subcontext{'_odd'} = $idx % 2 != 0; 320 | 321 | $subcontext{'_first'} = $idx == 0; 322 | $subcontext{'_last'} = $idx == $#$value; 323 | 324 | $output .= $self->_parse($template, {%$context, %subcontext}); 325 | 326 | $idx++; 327 | } 328 | } 329 | elsif (ref $value eq 'CODE') { 330 | $template = $self->_parse($template, $context); 331 | $output 332 | .= $self->_parse($value->($self, $template, $context), $context); 333 | } 334 | elsif (ref $value) { 335 | $output .= $self->_parse($template, {%$context, _with => $value}); 336 | } 337 | elsif ($value) { 338 | $output .= $self->_parse($template, $context); 339 | } 340 | 341 | return $output; 342 | } 343 | 344 | sub _parse_inverted_section { 345 | my $self = shift; 346 | my ($name, $template, $context) = @_; 347 | 348 | my $value = $self->_find_value($context, $name); 349 | return $self->_parse($template, $context) 350 | unless defined $value; 351 | 352 | $value = $$value; 353 | my $output = ''; 354 | 355 | if (ref $value eq 'HASH') { 356 | } 357 | elsif (ref $value eq 'ARRAY') { 358 | return '' if @$value; 359 | 360 | $output .= $self->_parse($template, $context); 361 | } 362 | elsif (!$value) { 363 | $output .= $self->_parse($template, $context); 364 | } 365 | 366 | return $output; 367 | } 368 | 369 | sub _parse_partial { 370 | my $self = shift; 371 | my ($template, $context) = @_; 372 | 373 | if (my $ext = $self->{default_partial_extension}) { 374 | $template = "$template.$ext"; 375 | } 376 | 377 | my $content = $self->_slurp_template($template); 378 | 379 | return $self->_parse($content, $context); 380 | } 381 | 382 | sub _parse_inherited_template { 383 | my $self = shift; 384 | my ($name, $override, $context) = @_; 385 | 386 | if (my $ext = $self->{default_partial_extension}) { 387 | $name = "$name.$ext"; 388 | } 389 | 390 | my $content = $self->_slurp_template($name); 391 | 392 | return $self->_parse($content, $context, $override); 393 | } 394 | 395 | sub _parse_block { 396 | my $self = shift; 397 | my ($name, $template, $context, $override) = @_; 398 | 399 | # get block content from override 400 | my $content; 401 | 402 | # first, see if we can find any starting block with this name in the override 403 | if ($override =~ m/ $START_OF_BLOCK \s* $name \s* $END_TAG/gcxms) { 404 | # get the content of the override block and make sure there's a corresponding end-block tag for it! 405 | if ($override =~ m/ (.*) $START_TAG $END_OF_BLOCK \s* $name \s* $END_TAG/gcxms){ 406 | my $content = $1; 407 | return $self->_parse($content, $context); 408 | } else { 409 | Carp::croak("Block's '$name' end not found"); 410 | } 411 | } 412 | 413 | return $self->_parse($template, $context); 414 | } 415 | 416 | sub _slurp_template { 417 | my $self = shift; 418 | my ($template) = @_; 419 | 420 | my $path = 421 | defined $self->templates_path 422 | && !(File::Spec->file_name_is_absolute($template)) 423 | ? File::Spec->catfile($self->templates_path, $template) 424 | : $template; 425 | 426 | Carp::croak("Can't find '$path'") unless defined $path && -f $path; 427 | 428 | my $content = do { 429 | local $/; 430 | open my $file, '<:encoding(UTF-8)', $path or return; 431 | <$file>; 432 | }; 433 | 434 | Carp::croak("Can't open '$template'") unless defined $content; 435 | 436 | chomp $content; 437 | 438 | return $content; 439 | } 440 | 441 | sub _is_empty { 442 | my $self = shift; 443 | my ($vars, $name) = @_; 444 | 445 | my $var; 446 | 447 | if (Scalar::Util::blessed($vars)) { 448 | $var = $vars->$name; 449 | } 450 | else { 451 | return 1 unless exists $vars->{$name}; 452 | $var = $vars->{$name}; 453 | } 454 | 455 | return 1 unless defined $var; 456 | return 1 if $var eq ''; 457 | 458 | return 0; 459 | } 460 | 461 | sub _escape { 462 | my $self = shift; 463 | my $value = shift; 464 | 465 | $value =~ s/&/&/g; 466 | $value =~ s//>/g; 468 | $value =~ s/"/"/g; 469 | 470 | return $value; 471 | } 472 | 473 | 1; 474 | __END__ 475 | 476 | =head1 NAME 477 | 478 | Text::Caml - Mustache template engine 479 | 480 | =head1 SYNOPSIS 481 | 482 | my $view = Text::Caml->new; 483 | 484 | my $output = $view->render_file('template', {title => 'Hello', body => 'there!'}); 485 | 486 | # template 487 | 488 | 489 | {{title}} 490 | 491 | 492 | {{body}} 493 | 494 | 495 | 496 | $output = $view->render('{{hello}}', {hello => 'hi'}); 497 | 498 | =head1 DESCRIPTION 499 | 500 | L is a Mustache-like (L) template engine. 501 | That means it tends to have no logic in template files. 502 | 503 | =head2 Syntax 504 | 505 | =head3 Context 506 | 507 | Context is the data passed to the template. Context can change during template 508 | rendering and be specific in various cases. 509 | 510 | =head3 Variables 511 | 512 | Variables are inserted using C<{{foo}}> syntax. If a variable is not defined or 513 | empty it is simply ignored. 514 | 515 | Hello {{user}}! 516 | 517 | By default every variable is escaped when parsed. This can be omitted using C<&> 518 | flag. 519 | 520 | # user is '1 > 2' 521 | Hello {{user}}! => Hello 1 > 2! 522 | 523 | Hello {{&user}}! => Hello 1 > 2! 524 | 525 | Using a C<.> syntax it is possible to access deep hash structures. 526 | 527 | # user => {name => 'Larry'} 528 | {{user.name}} 529 | 530 | Larry 531 | 532 | =head3 Comments 533 | 534 | Comments are ignored. They can be multiline too. 535 | 536 | foo{{! Comment}}bar 537 | 538 | foo{{! 539 | Comment 540 | }}bar 541 | 542 | =head3 Sections 543 | 544 | Sections are like iterators that iterate over your data. Depending on a 545 | variable type different iterators are created. 546 | 547 | =over 4 548 | 549 | =item * 550 | 551 | Boolean, C is defined, not zero and not empty. 552 | 553 | # have_comments => 1 554 | {{#have_comments}} 555 | We have comments! 556 | {{/have_comments}} 557 | 558 | We have comments! 559 | 560 | =item * 561 | 562 | Array, C is a non-empty array reference. Special variable C<{{.}}> is 563 | created to point to the current element. 564 | 565 | # list => [1, 2, 3] 566 | {{#list}}{{.}}{{/list}} 567 | 568 | 123 569 | 570 | =item * 571 | 572 | Hash, C is a non-empty hash reference. Context is swithed to the 573 | elements. 574 | 575 | # hash => {one => 1, two => 2, three => 3} 576 | {{#hash}} 577 | {{one}}{{two}}{{three}} 578 | {{/hash}} 579 | 580 | 123 581 | 582 | =item * 583 | 584 | Lambda, C is an anonymous subroutine, that's called with three 585 | arguments: current object instance, template and the context. This can be used 586 | for subrendering, helpers etc. 587 | 588 | wrapped => sub { 589 | my $self = shift; 590 | my $text = shift; 591 | 592 | return '' . $self->render($text, @_) . ''; 593 | }; 594 | 595 | {{#wrapped}} 596 | {{name}} is awesome. 597 | {{/wrapped}} 598 | 599 | Willy is awesome. 600 | 601 | =back 602 | 603 | =head3 Inverted sections 604 | 605 | Inverted sections are run in those situations when normal sections don't. When 606 | boolean value is false, array is empty etc. 607 | 608 | # repo => [] 609 | {{#repo}} 610 | {{name}} 611 | {{/repo}} 612 | {{^repo}} 613 | No repos :( 614 | {{/repo}} 615 | 616 | No repos :( 617 | 618 | =head3 Partials 619 | 620 | Partials are like C in other templates engines. They are run with the 621 | current context and can be recursive. 622 | 623 | {{#articles}} 624 | {{>article_summary}} 625 | {{/articles}} 626 | 627 | =head3 Nested Templates 628 | 629 | This gives horgan.js style template inheritance. 630 | 631 | {{! header.mustache }} 632 | 633 | {{$title}}Default title{{/title}} 634 | 635 | 636 | {{! base.mustache }} 637 | 638 | {{$header}}{{/header}} 639 | {{$content}}{{/content}} 640 | 641 | 642 | {{! mypage.mustache }} 643 | {{Hello world 652 | {{/content}} 653 | {{/base}} 654 | 655 | Rendering mypage.mustache would output: 656 | My page title

Hello world

657 | 658 | =cut 659 | 660 | =head1 ATTRIBUTES 661 | 662 | =head2 C 663 | 664 | my $path = $engine->templates_path; 665 | 666 | Return path where templates are searched. 667 | 668 | =head2 C 669 | 670 | my $path = $engine->set_templates_path('templates'); 671 | 672 | Set base path under which templates are searched. 673 | 674 | =head2 C 675 | 676 | If this option is set that the extension is automatically added to the partial 677 | filenames. 678 | 679 | my $engine = Text::Caml->new(default_partial_extension => 'caml'); 680 | 681 | --- 682 | {{#articles}} 683 | {{>article_summary}} # article_summary.caml will be searched 684 | {{/articles}} 685 | 686 | =head1 METHODS 687 | 688 | =head2 C 689 | 690 | my $engine = Text::Caml->new; 691 | 692 | Create a new L object. 693 | 694 | =head2 C 695 | 696 | $engine->render('{{foo}}', {foo => 'bar'}); 697 | 698 | Render template from string. 699 | 700 | =head2 C 701 | 702 | $engine->render_file('template.mustache', {foo => 'bar'}); 703 | 704 | Render template from file. 705 | 706 | =head1 DEVELOPMENT 707 | 708 | =head2 Repository 709 | 710 | http://github.com/vti/text-caml 711 | 712 | =head1 AUTHOR 713 | 714 | Viacheslav Tykhanovskyi, C 715 | 716 | =head1 CREDITS 717 | 718 | Sergey Zasenko (und3f) 719 | 720 | Andrew Rodland (arodland) 721 | 722 | Alex Balhatchet (kaoru) 723 | 724 | Yves Chevallier 725 | 726 | Ovidiu Stateina 727 | 728 | Fernando Oliveira 729 | 730 | =head1 COPYRIGHT AND LICENSE 731 | 732 | Copyright (C) 2011-2015, Viacheslav Tykhanovskyi 733 | 734 | This program is free software, you can redistribute it and/or modify it under 735 | the terms of the Artistic License version 2.0. 736 | 737 | =cut 738 | -------------------------------------------------------------------------------- /vendor/lib/parent.pm: -------------------------------------------------------------------------------- 1 | package parent; 2 | use strict; 3 | use vars qw($VERSION); 4 | $VERSION = '0.234'; 5 | 6 | sub import { 7 | my $class = shift; 8 | 9 | my $inheritor = caller(0); 10 | 11 | if ( @_ and $_[0] eq '-norequire' ) { 12 | shift @_; 13 | } else { 14 | for ( my @filename = @_ ) { 15 | s{::|'}{/}g; 16 | require "$_.pm"; # dies if the file is not found 17 | } 18 | } 19 | 20 | { 21 | no strict 'refs'; 22 | push @{"$inheritor\::ISA"}, @_; 23 | }; 24 | }; 25 | 26 | "All your base are belong to us" 27 | 28 | __END__ 29 | 30 | =encoding utf8 31 | 32 | =head1 NAME 33 | 34 | parent - Establish an ISA relationship with base classes at compile time 35 | 36 | =head1 SYNOPSIS 37 | 38 | package Baz; 39 | use parent qw(Foo Bar); 40 | 41 | =head1 DESCRIPTION 42 | 43 | Allows you to both load one or more modules, while setting up inheritance from 44 | those modules at the same time. Mostly similar in effect to 45 | 46 | package Baz; 47 | BEGIN { 48 | require Foo; 49 | require Bar; 50 | push @ISA, qw(Foo Bar); 51 | } 52 | 53 | By default, every base class needs to live in a file of its own. 54 | If you want to have a subclass and its parent class in the same file, you 55 | can tell C not to load any modules by using the C<-norequire> switch: 56 | 57 | package Foo; 58 | sub exclaim { "I CAN HAS PERL" } 59 | 60 | package DoesNotLoadFooBar; 61 | use parent -norequire, 'Foo', 'Bar'; 62 | # will not go looking for Foo.pm or Bar.pm 63 | 64 | This is equivalent to the following code: 65 | 66 | package Foo; 67 | sub exclaim { "I CAN HAS PERL" } 68 | 69 | package DoesNotLoadFooBar; 70 | push @DoesNotLoadFooBar::ISA, 'Foo', 'Bar'; 71 | 72 | This is also helpful for the case where a package lives within 73 | a differently named file: 74 | 75 | package MyHash; 76 | use Tie::Hash; 77 | use parent -norequire, 'Tie::StdHash'; 78 | 79 | This is equivalent to the following code: 80 | 81 | package MyHash; 82 | require Tie::Hash; 83 | push @ISA, 'Tie::StdHash'; 84 | 85 | If you want to load a subclass from a file that C would 86 | not consider an eligible filename (that is, it does not end in 87 | either C<.pm> or C<.pmc>), use the following code: 88 | 89 | package MySecondPlugin; 90 | require './plugins/custom.plugin'; # contains Plugin::Custom 91 | use parent -norequire, 'Plugin::Custom'; 92 | 93 | =head1 HISTORY 94 | 95 | This module was forked from L to remove the cruft 96 | that had accumulated in it. 97 | 98 | =head1 CAVEATS 99 | 100 | =head1 SEE ALSO 101 | 102 | L 103 | 104 | =head1 AUTHORS AND CONTRIBUTORS 105 | 106 | Rafaël Garcia-Suarez, Bart Lateur, Max Maischein, Anno Siegel, Michael Schwern 107 | 108 | =head1 MAINTAINER 109 | 110 | Max Maischein C< corion@cpan.org > 111 | 112 | Copyright (c) 2007-10 Max Maischein C<< >> 113 | Based on the idea of C, which was introduced with Perl 5.004_04. 114 | 115 | =head1 LICENSE 116 | 117 | This module is released under the same terms as Perl itself. 118 | 119 | =cut 120 | -------------------------------------------------------------------------------- /xt/00_lint.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | 5 | use t::Util; 6 | 7 | eval { 8 | require Test::Perl::Critic; 9 | Test::Perl::Critic->import( -profile => 'xt/perlcriticrc' ); 10 | }; 11 | 12 | all_critic_ok('lib', 't', 'xt', 'bin/crystal-build'); 13 | -------------------------------------------------------------------------------- /xt/perlcriticrc: -------------------------------------------------------------------------------- 1 | only = 1 2 | include = Objects::ProhibitIndirectSyntax, References::ProhibitDoubleSigils, Variables::ProtectPrivateVars, Variables::ProhibitPunctuationVars, Variables::RequireNegativeIndices, Variables::ProhibitAugmentedAssignmentInDeclaration, Variables::ProhibitReusedNames, Variables::ProhibitMatchVars, Variables::ProhibitUnusedVariables, Variables::RequireLexicalLoopIterators, Variables::RequireLocalizedPunctuationVars, Variables::ProhibitPackageVars, Variables::ProhibitEvilVariables, Variables::ProhibitConditionalDeclarations, Variables::ProhibitPerl4PackageNames, Variables::ProhibitLocalVars, Variables::RequireInitializationForLocalVars, Modules::ProhibitConditionalUseStatements, Modules::RequireEndWithOne, Modules::RequireFilenameMatchesPackage, Modules::RequireVersionVar, Modules::RequireBarewordIncludes, Modules::ProhibitEvilModules, Modules::ProhibitMultiplePackages, Modules::ProhibitAutomaticExportation, Modules::RequireNoMatchVarsWithUseEnglish, Modules::RequireExplicitPackage, Modules::ProhibitExcessMainComplexity, Miscellanea::ProhibitFormats, Miscellanea::ProhibitTies, Miscellanea::ProhibitUnrestrictedNoCritic, Miscellanea::ProhibitUselessNoCritic, RegularExpressions::ProhibitEscapedMetacharacters, RegularExpressions::ProhibitEnumeratedClasses, RegularExpressions::RequireExtendedFormatting, RegularExpressions::RequireBracesForMultiline, RegularExpressions::ProhibitSingleCharAlternation, RegularExpressions::ProhibitCaptureWithoutTest, RegularExpressions::ProhibitUnusedCapture, RegularExpressions::RequireDotMatchAnything, RegularExpressions::ProhibitUselessTopic, RegularExpressions::ProhibitUnusualDelimiters, RegularExpressions::ProhibitFixedStringMatches, RegularExpressions::ProhibitComplexRegexes, RegularExpressions::RequireLineBoundaryMatching, Subroutines::ProhibitManyArgs, Subroutines::ProhibitBuiltinHomonyms, Subroutines::ProhibitSubroutinePrototypes, Subroutines::ProhibitAmpersandSigils, Subroutines::ProhibitExcessComplexity, Subroutines::ProhibitNestedSubs, Subroutines::ProhibitUnusedPrivateSubroutines, Subroutines::ProhibitReturnSort, Subroutines::RequireFinalReturn, Subroutines::RequireArgUnpacking, Subroutines::ProhibitExplicitReturnUndef, Subroutines::ProtectPrivateSubs, InputOutput::ProhibitBarewordFileHandles, InputOutput::RequireBriefOpen, InputOutput::ProhibitExplicitStdin, InputOutput::RequireBracedFileHandleWithPrint, InputOutput::RequireCheckedClose, InputOutput::RequireCheckedSyscalls, InputOutput::RequireEncodingWithUTF8Layer, InputOutput::ProhibitInteractiveTest, InputOutput::ProhibitBacktickOperators, InputOutput::ProhibitOneArgSelect, InputOutput::ProhibitJoinedReadline, InputOutput::ProhibitTwoArgOpen, InputOutput::RequireCheckedOpen, InputOutput::ProhibitReadlineInForLoop, NamingConventions::ProhibitAmbiguousNames, NamingConventions::Capitalization, CodeLayout::ProhibitTrailingWhitespace, CodeLayout::RequireTrailingCommas, CodeLayout::ProhibitQuotedWordLists, CodeLayout::RequireTidyCode, CodeLayout::RequireConsistentNewlines, CodeLayout::ProhibitHardTabs, CodeLayout::ProhibitParensWithBuiltins, BuiltinFunctions::ProhibitComplexMappings, BuiltinFunctions::ProhibitVoidMap, BuiltinFunctions::ProhibitUniversalCan, BuiltinFunctions::RequireBlockGrep, BuiltinFunctions::ProhibitReverseSortBlock, BuiltinFunctions::ProhibitStringyEval, BuiltinFunctions::ProhibitVoidGrep, BuiltinFunctions::ProhibitUniversalIsa, BuiltinFunctions::ProhibitBooleanGrep, BuiltinFunctions::ProhibitUselessTopic, BuiltinFunctions::ProhibitLvalueSubstr, BuiltinFunctions::ProhibitStringySplit, BuiltinFunctions::RequireSimpleSortBlock, BuiltinFunctions::RequireGlobFunction, BuiltinFunctions::RequireBlockMap, BuiltinFunctions::ProhibitSleepViaSelect, ClassHierarchies::ProhibitAutoloading, ClassHierarchies::ProhibitExplicitISA, ClassHierarchies::ProhibitOneArgBless, TestingAndDebugging::ProhibitNoWarnings, TestingAndDebugging::ProhibitProlongedStrictureOverride, TestingAndDebugging::RequireTestLabels, TestingAndDebugging::ProhibitNoStrict, TestingAndDebugging::RequireUseWarnings, TestingAndDebugging::RequireUseStrict, ErrorHandling::RequireCheckingReturnValueOfEval, ErrorHandling::RequireCarping, Documentation::PodSpelling, Documentation::RequirePackageMatchesPodName, Documentation::RequirePodAtEnd, Documentation::RequirePodLinksIncludeText, Documentation::RequirePodSections, ControlStructures::ProhibitUnreachableCode, ControlStructures::ProhibitDeepNests, ControlStructures::ProhibitCascadingIfElse, ControlStructures::ProhibitUntilBlocks, ControlStructures::ProhibitPostfixControls, ControlStructures::ProhibitNegativeExpressionsInUnlessAndUntilConditions, ControlStructures::ProhibitLabelsWithSpecialBlockNames, ControlStructures::ProhibitCStyleForLoops, ControlStructures::ProhibitMutatingListFunctions, ControlStructures::ProhibitUnlessBlocks, ValuesAndExpressions::RequireNumberSeparators, ValuesAndExpressions::RequireUpperCaseHeredocTerminator, ValuesAndExpressions::ProhibitConstantPragma, ValuesAndExpressions::ProhibitMagicNumbers, ValuesAndExpressions::RequireConstantVersion, ValuesAndExpressions::ProhibitComplexVersion, ValuesAndExpressions::ProhibitEscapedCharacters, ValuesAndExpressions::ProhibitInterpolationOfLiterals, ValuesAndExpressions::RequireQuotedHeredocTerminator, ValuesAndExpressions::ProhibitSpecialLiteralHeredocTerminator, ValuesAndExpressions::ProhibitLongChainsOfMethodCalls, ValuesAndExpressions::ProhibitEmptyQuotes, ValuesAndExpressions::ProhibitMismatchedOperators, ValuesAndExpressions::ProhibitNoisyQuotes, ValuesAndExpressions::ProhibitCommaSeparatedStatements, ValuesAndExpressions::ProhibitVersionStrings, ValuesAndExpressions::ProhibitImplicitNewlines, ValuesAndExpressions::ProhibitMixedBooleanOperators, ValuesAndExpressions::ProhibitLeadingZeros, ValuesAndExpressions::RequireInterpolationOfMetachars, ValuesAndExpressions::ProhibitQuotesAsQuotelikeOperatorDelimiters 3 | 4 | --------------------------------------------------------------------------------