Compare commits

...

96 commits

Author SHA1 Message Date
Fred Hebert
4ffd5dd550
Merge pull request #189 from ariel-anieli/ec-gb-trees-tests
Some checks failed
Integration tests / OTP 23 on ubuntu-latest (push) Has been cancelled
Integration tests / OTP 25 on ubuntu-latest (push) Has been cancelled
Integration tests / OTP 27 on ubuntu-latest (push) Has been cancelled
Moved `ec_gb_trees` tests into separate file
2024-11-24 17:44:09 -05:00
Ariel Otilibili
a5712997ef Moved ec_gb_trees tests into separate file
Part of #179.

Signed-off-by: Ariel Otilibili <otilibil@eurecom.fr>
2024-11-23 13:08:05 +01:00
Fred Hebert
20119880fd
Merge pull request #188 from ariel-anieli/ec-list-tests
Moved `ec_lists` tests into separate file, and chores in GitHub actions
2024-11-21 09:27:30 -05:00
Ariel Otilibili
1a08e33b83 Dropped minor versions in GitHub actions
Lastest minor in given version will be taken.

Signed-off-by: Ariel Otilibili <otilibil@eurecom.fr>
2024-11-18 21:25:19 +01:00
Ariel Otilibili
47f7a5540c Moved ec_lists tests into separate file
Part of #179

Signed-off-by: Ariel Otilibili <otilibil@eurecom.fr>
2024-11-18 21:25:06 +01:00
Fred Hebert
2ccc40f89b
Merge pull request #187 from ariel-anieli/ec-file-tests
Moved `ec_file` tests into sepate file; bumped actions/checkout
2024-11-13 09:34:58 -05:00
Ariel Otilibili
fa25b703e5 Bumped actions/checkout to v4
https://github.com/actions/checkout/releases/tag/v4.2.2

Signed-off-by: Ariel Otilibili <otilibil@eurecom.fr>
2024-11-05 22:33:20 +01:00
Ariel Otilibili
8de367f996 Moved ec_file tests into separate file
Signed-off-by: Ariel Otilibili <otilibil@eurecom.fr>
2024-11-05 22:32:48 +01:00
Fred Hebert
279d116dca
Merge pull request #186 from ariel-anieli/ec-semver-tests
Moved `ec_semver` tests in separate file.
2024-10-07 09:05:58 -04:00
Ariel Otilibili
23b00904c8 Moved ec_semver tests in separate file.
Part of #179.

Signed-off-by: Ariel Otilibili <otilibil@eurecom.fr>
2024-10-04 18:16:27 +02:00
Fred Hebert
6cd37a4f9b
Merge pull request #185 from ariel-anieli/ec-talk-test
Moved `ec_talk` tests into separate file
2024-09-24 09:06:37 -04:00
Ariel Otilibili
5305348899 Moved ec_talk tests into separate file
Part of #179.

Signed-off-by: Ariel Otilibili <otilibil@eurecom.fr>
2024-09-23 21:32:58 +02:00
Fred Hebert
6d4e7d14ce
Merge pull request #184 from ariel-anieli/test-cmd-log-cnv
Moved `ec_cmd_log` & `ec_cnv` tests into separate files
2024-08-12 16:39:28 -04:00
Ariel Otilibili
677984e961 Moved ec_env tests into a separate file
Signed-off-by: Ariel Otilibili <otilibil@eurecom.fr>
2024-08-12 20:06:56 +02:00
Ariel Otilibili
ca7581cbb0 Moved ec_cmd_log tests into separate file
* moved colour macros into `src/ec_cmd_log.hrl`
* moved `ec_cmd_log` tests into `test/ec_cmd_log_tests.erl`.

Signed-off-by: Ariel Otilibili <otilibil@eurecom.fr>
2024-08-12 20:05:55 +02:00
Fred Hebert
fc69576978
Merge pull request #183 from ariel-anieli/git-vsn-tests
Moved `ec_git_vsn` tests into separate file
2024-07-31 13:11:35 -04:00
Ariel Otilibili
d24ad72034 Moved ec_git_vsn tests into separate file 2024-07-29 17:24:41 +02:00
Fred Hebert
a54f0623c5
Merge pull request #181 from ariel-anieli/badges-hex-ci
Updated CI/CD and hex badges
2024-07-03 09:32:52 -04:00
Ariel Otilibili
515df6b21e Updated CI/CD and hex badges 2024-07-03 07:31:57 +02:00
Fred Hebert
1fd0a513ff
Merge pull request #178 from ariel-anieli/remove-random-uniform
Replaced random_uniform/1 by rand:uniform/1
2024-07-02 16:01:21 -04:00
Fred Hebert
3d0006fe89
Merge pull request #180 from ariel-anieli/thousand-separators
Use thousand separators for large digits
2024-07-02 15:57:04 -04:00
Ariel Otilibili
15126e0048 Replaced random_uniform/1 by rand:uniform/1
* leftover of 5118421f6f
* code base contains only one occurence of random_uniform/1

```
$ git grep random_uniform
src/ec_file.erl:    UniqueNumber = erlang:integer_to_list(erlang:trunc(random_uniform() * 1000000000000)),
src/ec_file.erl:random_uniform() ->
```
2024-06-20 13:17:41 +02:00
Ariel Otilibili
cdd9240142 Use thousand separators for large digits
```
$ git grep -nP '\d{4,}[^a-zA-Z\"]' | grep \.erl | grep -vi copyright > /dev/null; echo $?
0
```
2024-06-14 21:42:36 +02:00
Fred Hebert
1a42c54981
Merge pull request #177 from ariel-anieli/issue-173-and-type-annotation-change 2024-06-03 19:58:48 -04:00
Ariel Otilibili
ccc1be32be
Changed type annotation
Used 'intensity' in declaration of 'state_t'.
2024-06-03 23:33:10 +02:00
Ariel Otilibili
5f40d8f061
Resolved dialyzer warning on colorize/4
Fixes #173.
2024-06-03 23:32:17 +02:00
Fred Hebert
75c6bae602
Merge pull request #176 from ariel-anieli/github-action-and-otp-bump
GitHub action renaming & OTP bump
2024-06-01 10:54:37 -04:00
Ariel Otilibili
ab7eb3874f OTP bump
```
curl --silent https://packages.debian.org/search?keywords=erlang | \
    sed -ne '/Package erlang</,/<\/ul>/{/<\/a>/p; /br/p}' | \
    sed -e 's/<a.*$//' | \
    perl -0777 -pE 's/<li class="(\w+)">\n/$1/g; s/<br>([^\s]+)/$1/g; s/\+.*(?=\n)//g' | \
    sed -e '1i name otp-version' | \
    column -t

name          otp-version
buster        1:22.2.7
bullseye      1:23.2.6
bookworm      1:25.2.3
trixie        1:25.3.2.12
sid           1:25.3.2.12
experimental  1:27.0
```
2024-05-27 16:26:41 +02:00
Ariel Otilibili
2636b5e21d Renaming in GitHub actions 2024-05-27 16:26:41 +02:00
Fred Hebert
d6315a9541
Merge pull request #175 from ariel-anieli/remove-rebar2-cfg-script
Removed rebar2 case in rebar.config.script
2024-05-16 09:44:14 -04:00
Ariel Otilibili
f9ffd1ce6b Removed rebar2 case in rebar.config.script
* introduced by 505d35996d
* rebar2 is now deprecated [1].

[1] https://github.com/rebar/rebar
2024-05-15 22:20:43 +02:00
Fred Hebert
182c30a950
Merge pull request #172 from ariel-anieli/otp-bump
OTP bump
2024-03-18 13:17:36 -04:00
Ariel Otilibili
63b1798b1e OTP bump
* sequel of 17e6f89078
* added R26 in CI/CD, and cleared out dialyzer warnings
* from R26, by default, `-Wno_unknown` suppresses warnings [1]
* in R25, it was the reverse behavior: `-Wunknown` allows warnings [2].

[1] https://www.erlang.org/doc/man/dialyzer.html#warning_options
[2] https://www.erlang.org/docs/25/man/dialyzer#format_warning-1
2024-03-18 01:01:08 +01:00
Fred Hebert
cb3983741e
Merge pull request #171 from ariel-anieli/pr-stacktrace
Removed unicode_str & fun_stacktrace
2024-03-13 14:21:29 -04:00
Ariel Otilibili
f378d3ec46 Removed unicode_str
* introduced in f8f72b7cc5
* introduced for working around compile warning starting from R20
* CI/CD uses R23 and onwards.
2024-03-07 06:58:59 +01:00
Ariel Otilibili
c0a02892cd Removed fun_stacktrace
* introduced in ad2d57d8b6
* CI/CD uses R23 and onwards
* erlang:get_stacktrace/0 removed in R23.

[1] https://www.erlang.org/doc/general_info/removed#functions-removed-in-otp-23
2024-03-07 06:58:52 +01:00
Fred Hebert
5de3c80cc2
Merge pull request #170 from ariel-anieli/pr-otp-bump
Bumped OTP versions
2024-02-21 16:45:29 -05:00
Ariel Otilibili
17e6f89078 Bumped OTP versions
* aligned OTP versions with Debian
* keeps versions from bullseye (old stable) to sid (unstable).

```
$ curl --silent https://packages.debian.org/search?keywords=erlang | \
  sed -ne '/Package erlang</,/<\/ul>/{/<\/a>/p; /br/p}' | \
  sed -e 's/<a.*$//' | \
  perl -0777 -pE 's/<li class="(\w+)">\n/$1/g; s/<br>([^\s]+)/$1/g; s/\+.*(?=\n)//g' | \
  sed -e '1i name otp-version' | \
  column -t
name          otp-version
buster        1:22.2.7
bullseye      1:23.2.6
bookworm      1:25.2.3
trixie        1:25.3.2.8
sid           1:25.3.2.8
experimental  1:26.2.1

$ curl -I  https://packages.debian.org/search?keywords=erlang
HTTP/2 200
date: Tue, 20 Feb 2024 22:21:53 GMT
server: Apache
last-modified: Tue, 20 Feb 2024 21:50:40 GMT
vary: Accept-Encoding,negotiate,accept-language
x-clacks-overhead: GNU Terry Pratchett
expires: Wed, 21 Feb 2024 09:04:02 +0000
x-content-type-options: nosniff
x-frame-options: sameorigin
referrer-policy: no-referrer
x-xss-protection: 1
permissions-policy: interest-cohort=()
strict-transport-security: max-age=15552000
age: 1873
content-length: 184625
content-type: text/html; charset=UTF-8
```
2024-02-21 22:05:14 +01:00
Fred Hebert
86a6c6ea65
Merge pull request #169 from ariel-anieli/pr-ns-types
Removed namespaced_types
2024-02-20 12:47:25 -05:00
Ariel Otilibili
2286a6ed9b Removed namespaced_types
* introduced for handling deprecated types existing before R17
* introduced in 523a66ad74
* CI/CD handles R19 up to R24
* R19 and onwards have dict:dict/0 [1,2]

[1] https://www.erlang.org/docs/19/man/dict
[2] https://www.erlang.org/docs/24/man/dict#type-dict
2024-02-17 22:04:50 +01:00
Fred Hebert
7b7d5b559d
Merge pull request #168 from ariel-anieli/pr-callback
Removed have_callback_support
2024-02-07 15:51:03 -05:00
Ariel Otilibili
eca2d2129c Removed have_callback_support
* introduced by 95f723e1e0
* mentioned as well in 47bcbd49b6
* introduced for compatibility with OTP R14
* CI/CD now tests from R19 and onwards.
2024-02-04 12:39:42 +01:00
Fred Hebert
20d049ea4f
Merge pull request #167 from ariel-anieli/pr-deprecation-cond
Removed checks on deprecated crypto:sha/1 & random:uniform/0
2024-01-12 15:59:41 -05:00
Ariel Otilibili
5118421f6f Removed checks on deprecated random module 2024-01-12 19:50:54 +01:00
Ariel Otilibili
685f08621b Removed conditions on deprecated cryptos and rand modoles
* CI/CD lowest OTP version is 19.3
* from 19.3, `rand` module exists (https://www.erlang.org/docs/19/man/rand)
* from 19.3, `crypto:hash/1` exists (https://www.erlang.org/docs/19/man/crypto).
2024-01-12 19:46:27 +01:00
Fred Hebert
952a1d2bc6
Merge pull request #166 from ariel-anieli/pr-typo-checksums-ec_file
[src/ec_file.erl] Factorization & typos, in sha1sum/1 & md5sum/1
2024-01-08 11:58:18 -05:00
Ariel Otilibili
fc69b3630c turn_digest_into_hex/1 renamed as bin_to_hex/1 2023-12-21 08:43:36 +01:00
Ariel Otilibili
6781f1ba6a Factorized digest-to-hex transform; used in three functions 2023-12-20 23:58:44 +01:00
Ariel Otilibili
bbdbbf313f Typos, and replaced 'checksum' with 'digest': more consistent with Erlang documentation 2023-12-20 23:56:27 +01:00
Fred Hebert
19c717fb97
Merge pull request #165 from ariel-anieli/pr-typos
[doc/signatures.md] Fixed typos, mis-naming, and syntax highlighting
2023-12-18 16:18:28 -05:00
Ariel
6e9c1b0a22 Another typo in math mode 2023-12-18 21:26:06 +01:00
Ariel
7e69d4949e Replaced tab by four spaces 2023-12-18 21:19:24 +01:00
Ariel
68e9bbcd0f Typo in math mode 2023-12-18 21:00:25 +01:00
Ariel
cd88825861 Syntax highlighted in code snippets 2023-12-18 20:59:57 +01:00
Ariel
5c5c264241 For formulae, used Markdown math mode 2023-12-18 20:33:17 +01:00
Ariel
6d4c471ff6 Fixed typos and misnamings 2023-12-18 20:31:49 +01:00
Ariel
62a985b937 Instead of three colons, syntax is highlighted with three backticks 2023-12-18 20:29:17 +01:00
Fred Hebert
7c4911795e
Merge pull request #164 from ariel-anieli/pr-redundant-parse
Removed other redudant clauses in local parse/3
2023-12-10 11:15:31 -05:00
Ariel
ad4b944fc6 Removed other redudant clauses; same behaviour than https://github.com/erlware/erlware_commons/pull/162 2023-12-10 17:08:41 +01:00
Fred Hebert
4d5811d99b
Merge pull request #162 from ariel-anieli/issue-70-redundant-parse
Removed redudant clauses in local parse/3
2023-12-09 19:48:06 -05:00
Fred Hebert
04c0d4fc84
Merge pull request #163 from ariel-anieli/issue-138-rumany-typos
runmany_wrap/2 didn't call Fun: missing parens
2023-12-09 19:47:05 -05:00
Ariel
378b88587c Fix for https://github.com/erlware/erlware_commons/issues/138 2023-12-10 01:04:46 +01:00
Ariel
d5183f5336 Removed redudant clauses in local parse/3 2023-12-09 14:07:13 +01:00
Fred Hebert
eeb25f4b7f
Merge pull request #161 from kianmeng/fix-typos
Fix typos
2021-12-20 09:14:07 -05:00
Kian-Meng, Ang
8dd7378a75 Fix typos 2021-12-19 17:36:19 +08:00
Fred Hebert
4406d56135
Merge pull request #160 from FlyingLu/patch-1
fixed incorrect 'G' in the format parameter string
2021-11-02 12:52:06 -04:00
FlyingLu
e89e95de5f
Added more test cases.
Added more test cases to show the difference between 'G' and 'H'.
2021-11-03 00:11:05 +08:00
FlyingLu
791729c30a
Fixed the incorrect macro. 2021-11-02 11:27:57 +08:00
FlyingLu
d34da1d107
fixed incorrect 'G' in the format parameter string
In the format parameter string, 'G' should have represented 24-hour format of an hour WITHOUT leading zeros.
But it actually results in 24-hour format of an hour WITH leading zeros.
2021-11-01 16:24:15 +08:00
Tristan Sloughter
ad559ae1f5
Merge pull request #158 from ferd/support-unicode-strings
Support Unicode string formatting
2021-08-09 09:33:43 -06:00
Fred Hebert
916539338f Support Unicode string formatting
Fixes https://github.com/erlware/erlware_commons/issues/157
2021-08-09 15:06:57 +00:00
Fred Hebert
2a758c9ec7
Merge pull request #156 from paulo-ferraz-oliveira/feature/ci-otp-24.0
Tweak CI versions (add 24.0, move 23.2 to 23.3)
2021-05-14 08:27:54 -04:00
Paulo F. Oliveira
4c0180f157 Tweak CI versions (add 24.0, move 23.2 to 23.3) 2021-05-14 10:11:56 +01:00
Fred Hebert
6f7a32487a
Merge pull request #150 from enidgjoleka/remove-unused-file-attribute
Remove -file attribute pointing to a non-existing file on ec_semver_parser
2021-05-12 08:16:38 -04:00
Fred Hebert
9cdca1c2e1
Merge pull request #154 from paulo-ferraz-oliveira/feature/license-info
Improve on license-related information
2021-04-09 21:21:39 -04:00
Fred Hebert
26bcdf8030
Merge pull request #155 from paulo-ferraz-oliveira/feature/symlinks_in_copy
Keep symlinks in copies
2021-04-09 21:17:36 -04:00
Paulo F. Oliveira
c47c938537 Keep symlinks in copies 2021-04-02 22:44:02 +01:00
Paulo F. Oliveira
f0347d88d7 Improve on license-related information 2021-04-02 21:25:49 +01:00
Fred Hebert
0318b467bc
Merge pull request #152 from paulo-ferraz-oliveira/fix/for_dialyzer
Fixes for dialyzer
2021-03-26 13:13:13 -04:00
Paulo F. Oliveira
9d67e26510 Solve dialyzer warnings while approaching expected behaviour
(with minor unit tests)
2021-03-25 22:32:33 +00:00
Paulo F. Oliveira
f5e8aa6551 gitignore test-generated elements 2021-03-25 00:03:37 +00:00
Paulo F. Oliveira
4406953a87 Have CI working with extra checks 2021-03-25 00:03:13 +00:00
Paulo F. Oliveira
0a4fde35e7 Remove dead code 2021-03-25 00:02:53 +00:00
Enid Gjoleka
01e08a3605 Remove unused -file attribute on ec_semver_parser 2021-02-10 14:43:53 +01:00
Tristan Sloughter
d9874feccf
Merge pull request #149 from tsloughter/readme-badge
update readme status badge and CI branch
2021-01-04 12:22:12 -07:00
Tristan Sloughter
32e62781bb
set TERM os var to xterm to get cf to not drop color in logs 2021-01-04 11:52:55 -07:00
Tristan Sloughter
2256b68e4f
update readme status badge and CI branch 2021-01-04 10:18:25 -07:00
Tristan Sloughter
45d79af620
Merge pull request #146 from michaelklishin/patch-1
Avoid a warning on Erlang/OTP 24
2021-01-04 09:53:24 -07:00
Tristan Sloughter
c7f166a8a7
Merge pull request #148 from tsloughter/github-actions
move to github actions for CI
2021-01-04 09:51:40 -07:00
Tristan Sloughter
2e1b59ece6
move to github actions for CI 2021-01-04 09:44:13 -07:00
Michael Klishin
8eef97234f
Better comment wording as suggested by @ferd
Co-authored-by: Fred Hebert <mononcqc@ferd.ca>
2020-12-16 00:10:38 +03:00
Michael Klishin
c2b7863a53
Avoid a warning on Erlang/OTP 24
to make sure Rebar 3 can bootstrap on that version with warnings-as-errors compiler settings.

Closes #145.
2020-12-15 01:45:43 +03:00
Tristan Sloughter
f41b847b0c
Merge pull request #142 from rlipscombe/patch-1
Replace ericbmerritt links in README
2019-08-19 15:55:14 -06:00
Roger Lipscombe
0dc260c04c
Replace ericbmerritt links in README 2019-08-19 22:49:35 +01:00
Tristan Sloughter
aad7ae4241
Merge pull request #141 from martinrehfeld/patch-1
Fix spelling/grammar in signatures.md
2019-02-21 09:12:46 -07:00
Martin Rehfeld
8aadd8b278
Fix spelling/grammar in signatures.md
While reading through the document, I came across some spelling/grammar issues and thought I could just as well help fix them.
2019-02-21 17:11:30 +01:00
35 changed files with 1286 additions and 1387 deletions

31
.github/workflows/main.yml vendored Normal file
View file

@ -0,0 +1,31 @@
name: Integration tests
on:
pull_request:
branches:
- 'master'
push:
branches:
- 'master'
jobs:
build:
name: OTP ${{ matrix.otp_version }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
container:
image: erlang:${{matrix.otp_version}}
strategy:
matrix:
otp_version: ['27', '25', '23']
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v4
- name: Compile
run: rebar3 compile
- name: Dialyzer
run: rebar3 as test dialyzer
- name: EUnit
run: TERM=xterm rebar3 eunit

3
.gitignore vendored
View file

@ -7,9 +7,12 @@ doc/edoc-info
doc/erlang.png
ebin/*
.*
!.github
_build
erl_crash.dump
*.pyc
*~
TEST-*.xml
/foo
src/ec_semver_parser.peg

View file

@ -1,22 +0,0 @@
language: erlang
otp_release:
- 18.0
- 17.0
- R16B03-1
- R16B03
- R16B02
- R16B01
- R16B
script: "./rebar3 update && ./rebar3 compile && ./rebar3 eunit"
branches:
only:
- master
notifications:
email:
- core@erlware.org
irc:
channels:
- "irc.freenode.org#erlware"
use_notice: true
skip_join: true
sudo: false

View file

@ -73,7 +73,7 @@ $ git stash pop
```
You SHOULD use these commands both before working on your patch and before
submitting the pull request. If conflicts arise it is your responsability
submitting the pull request. If conflicts arise it is your responsibility
to deal with them.
You MUST create a new branch for your work. First make sure you have

View file

@ -3,7 +3,9 @@ Erlware Commons
Current Status
--------------
[![Build Status](https://secure.travis-ci.org/erlware/erlware_commons.png)](http://travis-ci.org/erlware/erlware_commons)
[![Hex.pm](https://img.shields.io/hexpm/v/erlware_commons)](https://hex.pm/packages/erlware_commons)
[![Tests](https://github.com/erlware/erlware_commons/workflows/EUnit/badge.svg)](https://github.com/erlware/erlware_commons/actions)
Introduction
------------
@ -24,6 +26,18 @@ Goals for the project
* Well Documented
* Well Tested
Licenses
--------
This project contains elements licensed with Apache License, Version 2.0,
as well as elements licensed with The MIT License.
You'll find license-related information in the header of specific files,
where warranted.
In cases where no such information is present refer to
[COPYING](COPYING).
Currently Available Modules/Systems
------------------------------------
@ -56,7 +70,7 @@ href="http://www.erlang.org/doc/man/lists.html">lists</a>, making most
list operations parallel. It can operate on each element in parallel,
for IO-bound operations, on sublists in parallel, for taking advantage
of multi-core machines with CPU-bound operations, and across erlang
nodes, for parallizing inside a cluster. It handles errors and node
nodes, for parallelizing inside a cluster. It handles errors and node
failures. It can be configured, tuned, and tweaked to get optimal
performance while minimizing overhead.
@ -64,7 +78,7 @@ Almost all the functions are identical to equivalent functions in
lists, returning exactly the same result, and having both a form with
an identical syntax that operates on each element in parallel and a
form which takes an optional "malt", a specification for how to
parallize the operation.
parallelize the operation.
fold is the one exception, parallel fold is different from linear
fold. This module also include a simple mapreduce implementation, and
@ -76,7 +90,7 @@ runmany, which is as a generalization of parallel list operations.
A complete parser for the [semver](http://semver.org/)
standard. Including a complete set of conforming comparison functions.
### [ec_lists](https://github.com/ericbmerritt/erlware_commons/blob/master/src/ec_lists.erl)
### [ec_lists](https://github.com/erlware/erlware_commons/blob/master/src/ec_lists.erl)
A set of additional list manipulation functions designed to supliment
the `lists` module in stdlib.
@ -93,7 +107,7 @@ Other languages, have built in support for **Interface** or
**signature** functionality. Java has Interfaces, SML has
Signatures. Erlang, though, doesn't currently support this model, at
least not directly. There are a few ways you can approximate it. We
have defined a mechnism called *signatures* and several modules that
have defined a mechanism called *signatures* and several modules that
to serve as examples and provide a good set of *dictionary*
signatures. More information about signatures can be found at
[signature](https://github.com/erlware/erlware_commons/blob/master/doc/signatures.md).
@ -110,19 +124,19 @@ This provides an implementation of the ec_dictionary signature using
erlang's dicts as a base. The function documentation for ec_dictionary
applies here as well.
### [ec_gb_trees](https://github.com/ericbmerritt/erlware_commons/blob/master/src/ec_gb_trees.erl)
### [ec_gb_trees](https://github.com/erlware/erlware_commons/blob/master/src/ec_gb_trees.erl)
This provides an implementation of the ec_dictionary signature using
erlang's gb_trees as a base. The function documentation for
ec_dictionary applies here as well.
### [ec_orddict](https://github.com/ericbmerritt/erlware_commons/blob/master/src/ec_orddict.erl)
### [ec_orddict](https://github.com/erlware/erlware_commons/blob/master/src/ec_orddict.erl)
This provides an implementation of the ec_dictionary signature using
erlang's orddict as a base. The function documentation for
ec_dictionary applies here as well.
### [ec_rbdict](https://github.com/ericbmerritt/erlware_commons/blob/master/src/ec_rbdict.erl)
### [ec_rbdict](https://github.com/erlware/erlware_commons/blob/master/src/ec_rbdict.erl)
This provides an implementation of the ec_dictionary signature using
Robert Virding's rbdict module as a base. The function documentation

View file

@ -2,10 +2,10 @@ Signatures
==========
It often occurs in coding that we need a library, a set of
functionaly. Often there are several algorithms that could provide
this functionality. However, the code that uses it, either doesn't
functionalities. Often there are several algorithms that could provide
each of these functionalities. However, the code that uses it, either doesn't
care about the individual algorithm or wishes to delegate choosing
that algorithm to some higher level. Lets take the concrete example of
that algorithm to some higher level. Let's take the concrete example of
dictionaries. A dictionary provides the ability to access a value via
a key (other things as well but primarily this). There are may ways to
implement a dictionary. Just a few are:
@ -16,17 +16,17 @@ implement a dictionary. Just a few are:
* [Skip Lists](http://en.wikipedia.org/wiki/Skip_list)
* Many, many more ....
Each of these approaches has there own performance characteristics,
memory footprints etc. For example, a table of size n with open
addressing has no collisions and holds up to n elements, with a single
comparison for successful lookup, and a table of size n with chaining
and k keys has the minimum max(0, k-n) collisions and O(1 + k/n)
Each of these approaches has their own performance characteristics,
memory footprints, etc. For example, a table of size $n$ with open
addressing has no collisions and holds up to $n$ elements, with a single
comparison for successful lookup, and a table of size $n$ with chaining
and $k$ keys has the minimum $\max(0, k-n)$ collisions and $\mathcal{O}(1 + k/n)$
comparisons for lookup. While for skip lists the performance
characteristics are about as good as that of randomly-built binary
search trees - namely (O log n). So the choice of which to select
search trees - namely $\mathcal{O}(\log n)$. So the choice of which to select
depends very much on memory available, insert/read characteristics,
etc. So delegating the choice to a single point in your code is a very
good idea. Unfortunately, in Erlang thats ot so easy to do at the moment.
good idea. Unfortunately, in Erlang that's so easy to do at the moment.
Other languages, have built in support for this
functionality. [Java](http://en.wikipedia.org/wiki/Java_(programming_language))
@ -39,17 +39,20 @@ directly. There are a few ways you can approximate it. One way is to
pass the Module name to the calling functions along with the data that
it is going to be called on.
:::erlang
```erlang
add(ModuleToUse, Key, Value, DictData) ->
ModuleToUse:add(Key, Value, DictData).
```
This works, and you can vary how you want to pass the data. For
example, you could easily use a tuple to contain the data. That is,
you could pass in `{ModuleToUse, DictData}` and that would make it a
bit cleaner.
:::erlang
```erlang
add(Key, Value, {ModuleToUse, DictData}) ->
ModuleToUse:add(Key, Value, DictData).
```
Either way, there are a few problems with this approach. One of the
biggest is that you lose code locality, by looking at this bit of code
@ -63,21 +66,22 @@ mistakes that you might have made. Tools like
[Dialyzer](http://www.erlang.org/doc/man/dialyzer.html) have just as
hard a time figuring out the what `ModuleToUse` is pointing to as you
do. So they can't give you warnings about potential problems. In fact
someone could inadvertantly pass an unexpected function name as
someone could inadvertently pass an unexpected function name as
`ModuleToUse` and you would never get any warnings, just an exception
at run time.
Fortunately, Erlang is a pretty flexable language so we can use a
Fortunately, Erlang is a pretty flexible language so we can use a
similar approach with a few adjustments to give us the best of both
worlds. Both the flexibiltiy of ignoreing a specific implementation
worlds. Both the flexibility of ignoring a specific implementation
and keeping all the nice locality we get by using an explicit module
name.
So what we actually want to do is something mole like this:
:::erlang
```erlang
add(Key, Value, DictData) ->
dictionary:add(Key, Value, DictData).
```
Doing this we retain the locality. We can easily look up the
`dictionary` Module. We immediately have a good idea what a
@ -90,14 +94,14 @@ reasons, this is a much better approach to the problem. This is what
Signatures
----------
How do we actually do this in Erlang now that Erlang is missing what Java, SML and friends has built in?
How do we actually do this in Erlang now that Erlang is missing what Java, SML and friends have built in?
The first thing we need to do is to define
a [Behaviour](http://metajack.im/2008/10/29/custom-behaviors-in-erlang/)
for our functionality. To continue our example we will define a
Behaviour for dictionaries. That Behaviour looks like this:
:::erlang
```erlang
-module(ec_dictionary).
-export([behaviour_info/1]).
@ -115,16 +119,17 @@ Behaviour for dictionaries. That Behaviour looks like this:
{keys, 1}];
behaviour_info(_) ->
undefined.
```
So we have our Behaviour now. Unfortunately, this doesn't give us much
yet. It will make sure that any dictionaries we write will have all
the functions they need to have, but it wont help use actually use the
the functions they need to have, but it won't help us actually use the
dictionaries in an abstract way in our code. To do that we need to add
a bit of functionality. We do that by actually implementing our own
behaviour, starting with `new/1`.
:::erlang
```erlang
%% @doc create a new dictionary object from the specified module. The
%% module should implement the dictionary behaviour.
%%
@ -132,12 +137,13 @@ behaviour, starting with `new/1`.
-spec new(module()) -> dictionary(_K, _V).
new(ModuleName) when is_atom(ModuleName) ->
#dict_t{callback = ModuleName, data = ModuleName:new()}.
```
This code creates a new dictionary for us. Or to be more specific it
actually creates a new dictionary Signature record, that will be used
subsequently in other calls. This might look a bit familiar from our
previous less optimal approach. We have both the module name and the
data. here in the record. We call the module name named in
data in the record. We call the module name named in
`ModuleName` to create the initial data. We then construct the record
and return that record to the caller and we have a new
dictionary. What about the other functions, the ones that don't create
@ -148,7 +154,7 @@ dictionary and another that just retrieves data.
The first we will look at is the one that updates the dictionary by
adding a value.
:::erlang
```erlang
%% @doc add a new value to the existing dictionary. Return a new
%% dictionary containing the value.
%%
@ -158,6 +164,7 @@ adding a value.
-spec add(key(K), value(V), dictionary(K, V)) -> dictionary(K, V).
add(Key, Value, #dict_t{callback = Mod, data = Data} = Dict) ->
Dict#dict_t{data = Mod:add(Key, Value, Data)}.
```
There are two key things here.
@ -173,7 +180,7 @@ implementation to do the work itself.
Now lets do a data retrieval function. In this case, the `get` function
of the dictionary Signature.
:::erlang
```erlang
%% @doc given a key return that key from the dictionary. If the key is
%% not found throw a 'not_found' exception.
%%
@ -183,6 +190,7 @@ of the dictionary Signature.
-spec get(key(K), dictionary(K, V)) -> value(V).
get(Key, #dict_t{callback = Mod, data = Data}) ->
Mod:get(Key, Data).
```
In this case, you can see a very similar approach to deconstructing
the dict record. We still need to pull out the callback module and the
@ -197,7 +205,7 @@ implementation in
Using Signatures
----------------
Its a good idea to work through an example so we have a bit better
It's a good idea to work through an example so we have a bit better
idea of how to use these Signatures. If you are like me, you probably
have some questions about what kind of performance burden this places
on the code. At the very least we have an additional function call
@ -206,7 +214,7 @@ lets write a little timing test, so we can get a good idea of how much
this is all costing us.
In general, there are two kinds of concrete implementations for
Signatures. The first is a native implementations, the second is a
Signatures. The first is a native implementation, the second is a
wrapper.
### Native Signature Implementations
@ -223,20 +231,20 @@ implements the ec_dictionary module directly.
A Signature Wrapper is a module that wraps another module. Its
purpose is to help a preexisting module implement the Behaviour
defined by a Signature. A good example if this in our current example
defined by a Signature. A good example of this in our current example
is the
[erlware_commons/ec_dict](https://github.com/ericbmerritt/erlware_commons/blob/types/src/ec_dict.erl)
module. It implements the ec_dictionary Behaviour, but all the
module. It implements the `ec_dictionary` Behaviour, but all the
functionality is provided by the
[stdlib/dict](http://www.erlang.org/doc/man/dict.html) module
itself. Lets take a look at one example to see how this is done.
itself. Let's take a look at one example to see how this is done.
We will take a look at one of the functions we have already seen. The
`get` function an ec_dictionary `get` doesn't have quite the same
semantics as any of the functions in the dict module. So a bit of
translation needs to be done. We do that in the ec_dict module `get` function.
`get` function in `ec_dictionary` doesn't have quite the same
semantics as any of the functions in the `dict` module. So a bit of
translation needs to be done. We do that in the `ec_dict:get/2` function.
:::erlang
```erlang
-spec get(ec_dictionary:key(K), Object::dictionary(K, V)) ->
ec_dictionary:value(V).
get(Key, Data) ->
@ -246,9 +254,10 @@ translation needs to be done. We do that in the ec_dict module `get` function.
error ->
throw(not_found)
end.
```
So the ec_dict module's purpose for existence is to help the
preexisting dict module implement the Behaviour defined by the
So the `ec_dict` module's purpose for existence is to help the
preexisting `dict` module implement the Behaviour defined by the
Signature.
@ -258,24 +267,25 @@ the mix and that adds a bit of additional overhead.
### Creating the Timing Module
We are going to creating timings for both Native Signature
We are going to be creating timings for both Native Signature
Implementations and Signature Wrappers.
Lets get started by looking at some helper functions. We want
dictionaries to have a bit of data in them. So to that end we are will
Let's get started by looking at some helper functions. We want
dictionaries to have a bit of data in them. So to that end we will
create a couple of functions that create dictionaries for each type we
want to test. The first we want to time is the Signature Wrapper, so
`dict` vs `ec_dict` called as a Signature.
:::erlang
```erlang
create_dict() ->
lists:foldl(fun(El, Dict) ->
dict:store(El, El, Dict)
end, dict:new(),
lists:seq(1,100)).
```
The only thing we do here is create a sequence of numbers 1 to 100,
and then add each of those to the dict as an entry. We aren't too
and then add each of those to the `dict` as an entry. We aren't too
worried about replicating real data in the dictionary. We care about
timing the function call overhead of Signatures, not the performance
of the dictionaries themselves.
@ -283,26 +293,27 @@ of the dictionaries themselves.
We need to create a similar function for our Signature based
dictionary `ec_dict`.
:::erlang
```erlang
create_dictionary(Type) ->
lists:foldl(fun(El, Dict) ->
ec_dictionary:add(El, El, Dict)
end,
ec_dictionary:new(Type),
lists:seq(1,100)).
```
Here we actually create everything using the Signature. So we don't
need one function for each type. We can have one function that can
create anything that implements the Signature. That is the magic of
Signatures. Otherwise, this does the exact same thing as the dict
`create_dict/1`.
Signatures. Otherwise, this does the exact same thing as the dictionary
given by `create_dict/0`.
We are going to use two function calls in our timing. One that updates
data and one that returns data, just to get good coverage. For our
dictionaries that we are going to use the `size` function as well as
dictionaries we are going to use the `size` function as well as
the `add` function.
:::erlang
```erlang
time_direct_vs_signature_dict() ->
io:format("Timing dict~n"),
Dict = create_dict(),
@ -312,18 +323,19 @@ the `add` function.
1000000),
io:format("Timing ec_dict implementation of ec_dictionary~n"),
time_dict_type(ec_dict).
```
The `test_avg` function runs the provided function the number of times
specified in the second argument and collects timing information. We
are going to run these one million times to get a good average (its
fast so it doesn't take long). You can see that in the anonymous
are going to run these one million times to get a good average (it's
fast so it doesn't take long). You can see in the anonymous
function that we directly call `dict:size/1` and `dict:store/3` to perform
the test. However, because we are in the wonderful world of Signatures
we don't have to hard code the calls for the Signature
implementations. Lets take a look at the `time_dict_type` function.
:::erlang
```erlang
time_dict_type(Type) ->
io:format("Testing ~p~n", [Type]),
Dict = create_dictionary(Type),
@ -331,10 +343,11 @@ implementations. Lets take a look at the `time_dict_type` function.
ec_dictionary:size(ec_dictionary:add(some_key, some_value, Dict))
end,
1000000).
```
As you can see we take the type as an argument (we need it for `dict`
creation) and call our create function. Then we run the same timings
that we did for ec dict. In this case though, the type of dictionary
that we did for `ec_dict`. In this case though, the type of dictionary
is never specified, we only ever call ec_dictionary, so this test will
work for anything that implements that Signature.
@ -343,7 +356,7 @@ work for anything that implements that Signature.
So we have our tests, what was the result. Well on my laptop this is
what it looked like.
:::sh
```sh
Erlang R14B01 (erts-5.8.2) [source] [64-bit] [smp:4:4] [rq:4] [async-threads:0] [hipe] [kernel-poll:false]
Eshell V5.8.2 (abort with ^G)
@ -359,9 +372,10 @@ what it looked like.
Median: 3 mics
Average: 4 mics
2>
```
So for the direct dict call, we average about 3 mics per call, while
for the Signature Wrapper we average around 4. Thats a 25% cost for
So for the direct `dict` call, we average about 3 mics per call, while
for the Signature Wrapper we average around 4. That's a 25% cost for
Signature Wrappers in this example, for a very small number of
calls. Depending on what you are doing that is going to be greater or
lesser. In any case, we can see that there is some cost associated
@ -373,21 +387,22 @@ Signature, but it is not a Signature Wrapper. It is a native
implementation of the Signature. To use `ec_rbdict` directly we have
to create a creation helper just like we did for dict.
:::erlang
```erlang
create_rbdict() ->
lists:foldl(fun(El, Dict) ->
ec_rbdict:add(El, El, Dict)
end, ec_rbdict:new(),
lists:seq(1,100)).
```
This is exactly the same as `create_dict` with the exception that dict
is replaced by `ec_rbdict`.
The timing function itself looks very similar as well. Again notice
that we have to hard code the concrete name for the concrete
implementation, but we don't for the ec_dictionary test.
implementation, but we don't for the `ec_dictionary` test.
:::erlang
```erlang
time_direct_vs_signature_rbdict() ->
io:format("Timing rbdict~n"),
Dict = create_rbdict(),
@ -397,6 +412,7 @@ implementation, but we don't for the ec_dictionary test.
1000000),
io:format("Timing ec_dict implementation of ec_dictionary~n"),
time_dict_type(ec_rbdict).
```
And there we have our test. What do the results look like?
@ -406,7 +422,7 @@ The main thing we are timing here is the additional cost of the
dictionary Signature itself. Keep that in mind as we look at the
results.
:::sh
```sh
Erlang R14B01 (erts-5.8.2) [source] [64-bit] [smp:4:4] [rq:4] [async-threads:0] [hipe] [kernel-poll:false]
Eshell V5.8.2 (abort with ^G)
@ -422,18 +438,19 @@ results.
Median: 7 mics
Average: 7 mics
2>
```
So no difference it time. Well the reality is that there is a
difference in timing, there must be, but we don't have enough
resolution in the timing system to be able to figure out what that
difference is. Essentially that means its really, really small - or small
difference is. Essentially that means it's really, really small - or small
enough not to worry about at the very least.
Conclusion
----------
Signatures are a viable, useful approach to the problem of interfaces
in Erlang. The have little or no over head depending on the type of
in Erlang. They have little or no overhead depending on the type of
implementation, and greatly increase the flexibility of the a library
while retaining testability and locality.

View file

@ -8,16 +8,7 @@
{erl_first_files, ["ec_dictionary", "ec_vsn"]}.
%% Compiler Options ============================================================
{erl_opts,
[{platform_define, "^[0-9]+", namespaced_types},
{platform_define, "^[0-9]+", have_callback_support},
{platform_define, "^R1[4|5]", deprecated_crypto},
{platform_define, "^1[8|9]", rand_module},
{platform_define, "^2", rand_module},
{platform_define, "^2", unicode_str},
{platform_define, "^(R|1|20)", fun_stacktrace},
debug_info,
warnings_as_errors]}.
{erl_opts, [debug_info, warnings_as_errors]}.
%% EUnit =======================================================================
{eunit_opts, [verbose,

View file

@ -1,17 +1,7 @@
IsRebar3 = case application:get_key(rebar, vsn) of
{ok, Vsn} ->
[MajorVersion|_] = string:tokens(Vsn, "."),
(list_to_integer(MajorVersion) >= 3);
undefined ->
false
end,
NoDialWarns = {dialyzer, [{warnings, [no_unknown]}]},
OTPRelease = erlang:list_to_integer(erlang:system_info(otp_release)),
Rebar2Deps = [
{cf, ".*", {git, "https://github.com/project-fifo/cf", {tag, "0.2.2"}}}
],
case IsRebar3 of
case OTPRelease<26 of
true -> CONFIG;
false ->
lists:keyreplace(deps, 1, CONFIG, {deps, Rebar2Deps})
false -> lists:keystore(dialyzer, 1, CONFIG, NoDialWarns)
end.

View file

@ -1,6 +1,8 @@
{"1.1.0",
{"1.2.0",
[{<<"cf">>,{pkg,<<"cf">>,<<"0.3.1">>},0}]}.
[
{pkg_hash,[
{<<"cf">>, <<"5CB902239476E141EA70A740340233782D363A31EEA8AD37049561542E6CD641">>}]}
{<<"cf">>, <<"5CB902239476E141EA70A740340233782D363A31EEA8AD37049561542E6CD641">>}]},
{pkg_hash_ext,[
{<<"cf">>, <<"315E8D447D3A4B02BCDBFA397AD03BBB988A6E0AA6F44D3ADD0F4E3C3BF97672">>}]}
].

View file

@ -19,9 +19,12 @@
%%% @copyright (C) 2012 Erlware, LLC.
%%%
%%% @doc This provides simple output functions for command line apps. You should
%%% use this to talk to the users if you are wrting code for the system
%%% use this to talk to the users if you are writing code for the system
-module(ec_cmd_log).
%% Avoid clashing with `error/3` BIF added in Erlang/OTP 24
-compile({no_auto_import,[error/3]}).
-export([new/1,
new/2,
new/3,
@ -37,22 +40,17 @@
warn/3,
log_level/1,
atom_log_level/1,
colorize/4,
format/1]).
-include("ec_cmd_log.hrl").
-define(RED, $r).
-define(GREEN, $g).
-define(YELLOW, $y).
-define(BLUE, $b).
-define(MAGENTA, $m).
-define(CYAN, $c).
-include("include/ec_cmd_log.hrl").
-include("src/ec_cmd_log.hrl").
-define(PREFIX, "===> ").
-record(state_t, {log_level=0 :: int_log_level(),
caller=api :: caller(),
intensity=low :: none | low | high}).
intensity=low :: intensity()}).
%%============================================================================
%% types
@ -123,10 +121,10 @@ debug(LogState, Fun)
colorize(LogState, ?CYAN, false, Fun())
end);
debug(LogState, String) ->
debug(LogState, "~s~n", [String]).
debug(LogState, "~ts~n", [String]).
%% @doc log at the debug level given the current log state with a format string
%% and argements @see io:format/2
%% and arguments @see io:format/2
-spec debug(t(), string(), [any()]) -> ok.
debug(LogState, FormatString, Args) ->
log(LogState, ?EC_DEBUG, colorize(LogState, ?CYAN, false, FormatString), Args).
@ -140,10 +138,10 @@ info(LogState, Fun)
colorize(LogState, ?GREEN, false, Fun())
end);
info(LogState, String) ->
info(LogState, "~s~n", [String]).
info(LogState, "~ts~n", [String]).
%% @doc log at the info level given the current log state with a format string
%% and argements @see io:format/2
%% and arguments @see io:format/2
-spec info(t(), string(), [any()]) -> ok.
info(LogState, FormatString, Args) ->
log(LogState, ?EC_INFO, colorize(LogState, ?GREEN, false, FormatString), Args).
@ -157,10 +155,10 @@ error(LogState, Fun)
colorize(LogState, ?RED, false, Fun())
end);
error(LogState, String) ->
error(LogState, "~s~n", [String]).
error(LogState, "~ts~n", [String]).
%% @doc log at the error level given the current log state with a format string
%% and argements @see io:format/2
%% and arguments @see io:format/2
-spec error(t(), string(), [any()]) -> ok.
error(LogState, FormatString, Args) ->
log(LogState, ?EC_ERROR, colorize(LogState, ?RED, false, FormatString), Args).
@ -172,10 +170,10 @@ warn(LogState, Fun)
when erlang:is_function(Fun) ->
log(LogState, ?EC_WARN, fun() -> colorize(LogState, ?MAGENTA, false, Fun()) end);
warn(LogState, String) ->
warn(LogState, "~s~n", [String]).
warn(LogState, "~ts~n", [String]).
%% @doc log at the warn level given the current log state with a format string
%% and argements @see io:format/2
%% and arguments @see io:format/2
-spec warn(t(), string(), [any()]) -> ok.
warn(LogState, FormatString, Args) ->
log(LogState, ?EC_WARN, colorize(LogState, ?MAGENTA, false, FormatString), Args).
@ -184,7 +182,7 @@ warn(LogState, FormatString, Args) ->
-spec log(t(), int_log_level(), log_fun()) -> ok.
log(#state_t{log_level=DetailLogLevel}, LogLevel, Fun)
when DetailLogLevel >= LogLevel ->
io:format("~s~n", [Fun()]);
io:format("~ts~n", [Fun()]);
log(_, _, _) ->
ok.
@ -240,61 +238,20 @@ format(Log) ->
colorize(#state_t{intensity=none}, _, _, Msg) ->
Msg;
%% When it is suposed to be bold and we already have a uppercase
%% When it is supposed to be bold and we already have a uppercase
%% (bold color) we don't need to modify the color
colorize(State, Color, true, Msg) when ?VALID_COLOR(Color),
Color >= $A, Color =< $Z ->
colorize(State, Color, false, Msg);
%% We're sneaky we can substract 32 to get the uppercase character if we want
%% We're sneaky we can subtract 32 to get the uppercase character if we want
%% bold but have a non bold color.
colorize(State, Color, true, Msg) when ?VALID_COLOR(Color) ->
colorize(State, Color - 32, false, Msg);
colorize(#state_t{caller=command_line, intensity = high},
Color, false, Msg) when ?VALID_COLOR(Color) ->
lists:flatten(cf:format("~!" ++ [Color] ++"~s~s", [?PREFIX, Msg]));
lists:flatten(cf:format("~!" ++ [Color] ++"~ts~ts", [?PREFIX, Msg]));
colorize(#state_t{caller=command_line, intensity = low},
Color, false, Msg) when ?VALID_COLOR(Color) ->
lists:flatten(cf:format("~!" ++ [Color] ++"~s~!!~s", [?PREFIX, Msg]));
lists:flatten(cf:format("~!" ++ [Color] ++"~ts~!!~ts", [?PREFIX, Msg]));
colorize(_LogState, _Color, _Bold, Msg) ->
Msg.
%%%===================================================================
%%% Test Functions
%%%===================================================================
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
should_test() ->
ErrorLogState = new(error),
?assertMatch(true, should(ErrorLogState, ?EC_ERROR)),
?assertMatch(true, not should(ErrorLogState, ?EC_INFO)),
?assertMatch(true, not should(ErrorLogState, ?EC_DEBUG)),
?assertEqual(?EC_ERROR, log_level(ErrorLogState)),
?assertEqual(error, atom_log_level(ErrorLogState)),
InfoLogState = new(info),
?assertMatch(true, should(InfoLogState, ?EC_ERROR)),
?assertMatch(true, should(InfoLogState, ?EC_INFO)),
?assertMatch(true, not should(InfoLogState, ?EC_DEBUG)),
?assertEqual(?EC_INFO, log_level(InfoLogState)),
?assertEqual(info, atom_log_level(InfoLogState)),
DebugLogState = new(debug),
?assertMatch(true, should(DebugLogState, ?EC_ERROR)),
?assertMatch(true, should(DebugLogState, ?EC_INFO)),
?assertMatch(true, should(DebugLogState, ?EC_DEBUG)),
?assertEqual(?EC_DEBUG, log_level(DebugLogState)),
?assertEqual(debug, atom_log_level(DebugLogState)).
no_color_test() ->
LogState = new(debug, command_line, none),
?assertEqual("test",
colorize(LogState, ?RED, true, "test")).
color_test() ->
LogState = new(debug, command_line, high),
?assertEqual("\e[1;31m===> test\e[0m",
colorize(LogState, ?RED, true, "test")).
-endif.

7
src/ec_cmd_log.hrl Normal file
View file

@ -0,0 +1,7 @@
%%% @copyright 2024 Erlware, LLC.
-define(RED, $r).
-define(GREEN, $g).
-define(YELLOW, $y).
-define(BLUE, $b).
-define(MAGENTA, $m).
-define(CYAN, $c).

View file

@ -212,36 +212,3 @@ to_atom(X)
erlang:list_to_existing_atom(X);
to_atom(X) ->
to_atom(to_list(X)).
%%%===================================================================
%%% Tests
%%%===================================================================
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
to_integer_test() ->
?assertError(badarg, to_integer(1.5, strict)).
to_float_test() ->
?assertError(badarg, to_float(10, strict)).
to_atom_test() ->
?assertMatch(true, to_atom("true")),
?assertMatch(true, to_atom(<<"true">>)),
?assertMatch(false, to_atom(<<"false">>)),
?assertMatch(false, to_atom(false)),
?assertError(badarg, to_atom("hello_foo_bar_baz")),
S = erlang:list_to_atom("1"),
?assertMatch(S, to_atom(1)).
to_boolean_test()->
?assertMatch(true, to_boolean(<<"true">>)),
?assertMatch(true, to_boolean("true")),
?assertMatch(true, to_boolean(true)),
?assertMatch(false, to_boolean(<<"false">>)),
?assertMatch(false, to_boolean("false")),
?assertMatch(false, to_boolean(false)).
-endif.

View file

@ -38,7 +38,7 @@ beam_to_erl_source(BeamFName, ErlFName) ->
Src =
erl_prettypr:format(erl_syntax:form_list(tl(Forms))),
{ok, Fd} = file:open(ErlFName, [write]),
io:fwrite(Fd, "~s~n", [Src]),
io:fwrite(Fd, "~ts~n", [Src]),
file:close(Fd);
Error ->
Error

View file

@ -44,9 +44,9 @@
-define( is_month(X), ( (is_integer(X) andalso X =< 12) orelse ?is_hinted_month(X) ) ).
-define( is_tz_offset(H1,H2,M1,M2), (?is_num(H1) andalso ?is_num(H2) andalso ?is_num(M1) andalso ?is_num(M2)) ).
-define(GREGORIAN_SECONDS_1970, 62167219200).
-define(ISO_8601_DATETIME_FORMAT, "Y-m-dTG:i:sZ").
-define(ISO_8601_DATETIME_WITH_MS_FORMAT, "Y-m-dTG:i:s.fZ").
-define(GREGORIAN_SECONDS_1970, 62_167_219_200).
-define(ISO_8601_DATETIME_FORMAT, "Y-m-dTH:i:sZ").
-define(ISO_8601_DATETIME_WITH_MS_FORMAT, "Y-m-dTH:i:s.fZ").
-type year() :: non_neg_integer().
-type month() :: 1..12 | {?MONTH_TAG, 1..12}.
@ -54,7 +54,7 @@
-type hour() :: 0..23.
-type minute() :: 0..59.
-type second() :: 0..59.
-type microsecond() :: 0..999999.
-type microsecond() :: 0..999_999.
-type daynum() :: 1..7.
-type date() :: {year(),month(),day()}.
@ -101,7 +101,7 @@ parse(Date, Now) ->
do_parse(Date, Now, []).
do_parse(Date, Now, Opts) ->
case filter_hints(parse(tokenise(uppercase(Date), []), Now, Opts)) of
case filter_hints(parse(tokenise(string:uppercase(Date), []), Now, Opts)) of
{error, bad_date} ->
erlang:throw({?MODULE, {bad_date, Date}});
{D1, T1} = {{Y, M, D}, {H, M1, S}}
@ -138,11 +138,11 @@ nparse(Date) ->
{DateS, {H, M, S, Ms} } ->
GSeconds = calendar:datetime_to_gregorian_seconds({DateS, {H, M, S} }),
ESeconds = GSeconds - ?GREGORIAN_SECONDS_1970,
{ESeconds div 1000000, ESeconds rem 1000000, Ms};
{ESeconds div 1_000_000, ESeconds rem 1_000_000, Ms};
DateTime ->
GSeconds = calendar:datetime_to_gregorian_seconds(DateTime),
ESeconds = GSeconds - ?GREGORIAN_SECONDS_1970,
{ESeconds div 1000000, ESeconds rem 1000000, 0}
{ESeconds div 1_000_000, ESeconds rem 1_000_000, 0}
end.
%%
@ -151,7 +151,7 @@ nparse(Date) ->
parse([Year, X, Month, X, Day, Hour, $:, Min, $:, Sec, $., Micros, $Z ], _Now, _Opts)
when ?is_world_sep(X)
andalso (Micros >= 0 andalso Micros < 1000000)
andalso (Micros >= 0 andalso Micros < 1_000_000)
andalso Year > 31 ->
{{Year, Month, Day}, {hour(Hour, []), Min, Sec}, {Micros}};
@ -162,7 +162,7 @@ parse([Year, X, Month, X, Day, Hour, $:, Min, $:, Sec, $Z ], _Now, _Opts)
parse([Year, X, Month, X, Day, Hour, $:, Min, $:, Sec, $., Micros, $+, Off | _Rest ], _Now, _Opts)
when (?is_us_sep(X) orelse ?is_world_sep(X))
andalso (Micros >= 0 andalso Micros < 1000000)
andalso (Micros >= 0 andalso Micros < 1_000_000)
andalso Year > 31 ->
{{Year, Month, Day}, {hour(Hour, []) - Off, Min, Sec}, {Micros}};
@ -173,7 +173,7 @@ parse([Year, X, Month, X, Day, Hour, $:, Min, $:, Sec, $+, Off | _Rest ], _Now,
parse([Year, X, Month, X, Day, Hour, $:, Min, $:, Sec, $., Micros, $-, Off | _Rest ], _Now, _Opts)
when (?is_us_sep(X) orelse ?is_world_sep(X))
andalso (Micros >= 0 andalso Micros < 1000000)
andalso (Micros >= 0 andalso Micros < 1_000_000)
andalso Year > 31 ->
{{Year, Month, Day}, {hour(Hour, []) + Off, Min, Sec}, {Micros}};
@ -197,17 +197,6 @@ parse([Day,X,Month,X,Year,Hour,$:,Min,$:,Sec,$., Ms | PAM], _Now, _Opts)
andalso ?is_year(Year) ->
{{Year, Month, Day}, {hour(Hour, PAM), Min, Sec}, {Ms}};
parse([Year,X,Month,X,Day,Hour,$:,Min,$:,Sec,$., Ms], _Now, _Opts)
when (?is_us_sep(X) orelse ?is_world_sep(X))
andalso ?is_year(Year) ->
{{Year, Month, Day}, {hour(Hour,[]), Min, Sec}, {Ms}};
parse([Month,X,Day,X,Year,Hour,$:,Min,$:,Sec,$., Ms], _Now, _Opts)
when ?is_us_sep(X) andalso ?is_month(Month) ->
{{Year, Month, Day}, {hour(Hour, []), Min, Sec}, {Ms}};
parse([Day,X,Month,X,Year,Hour,$:,Min,$:,Sec,$., Ms ], _Now, _Opts)
when ?is_world_sep(X) andalso ?is_month(Month) ->
{{Year, Month, Day}, {hour(Hour, []), Min, Sec}, {Ms}};
%% Date/Times Dec 1st, 2012 6:25 PM
parse([Month,Day,Year,Hour,$:,Min,$:,Sec | PAM], _Now, _Opts)
when ?is_meridian(PAM) andalso ?is_hinted_month(Month) andalso ?is_day(Day) ->
@ -219,14 +208,6 @@ parse([Month,Day,Year,Hour | PAM], _Now, _Opts)
when ?is_meridian(PAM) andalso ?is_hinted_month(Month) andalso ?is_day(Day) ->
{{Year, Month, Day}, {hour(Hour, PAM), 0, 0}};
%% Date/Times Dec 1st, 2012 18:25:15 (no AM/PM)
parse([Month,Day,Year,Hour,$:,Min,$:,Sec], _Now, _Opts)
when ?is_hinted_month(Month) andalso ?is_day(Day) ->
{{Year, Month, Day}, {hour(Hour, []), Min, Sec}};
parse([Month,Day,Year,Hour,$:,Min], _Now, _Opts)
when ?is_hinted_month(Month) andalso ?is_day(Day) ->
{{Year, Month, Day}, {hour(Hour, []), Min, 0}};
%% Date/Times Fri Nov 21 14:55:26 +0000 2014 (Twitter format)
parse([Month, Day, Hour,$:,Min,$:,Sec, Year], _Now, _Opts)
when ?is_hinted_month(Month), ?is_day(Day), ?is_year(Year) ->
@ -335,11 +316,11 @@ tokenise([$., N1, N2, N3, N4 | Rest], Acc)
when ?is_num(N1), ?is_num(N2), ?is_num(N3), ?is_num(N4) ->
tokenise(Rest, [ ltoi([N1, N2, N3, N4]) * 100, $. | Acc]);
tokenise([$., N1, N2, N3 | Rest], Acc) when ?is_num(N1), ?is_num(N2), ?is_num(N3) ->
tokenise(Rest, [ ltoi([N1, N2, N3]) * 1000, $. | Acc]);
tokenise(Rest, [ ltoi([N1, N2, N3]) * 1_000, $. | Acc]);
tokenise([$., N1, N2 | Rest], Acc) when ?is_num(N1), ?is_num(N2) ->
tokenise(Rest, [ ltoi([N1, N2]) * 10000, $. | Acc]);
tokenise(Rest, [ ltoi([N1, N2]) * 10_000, $. | Acc]);
tokenise([$., N1 | Rest], Acc) when ?is_num(N1) ->
tokenise(Rest, [ ltoi([N1]) * 100000, $. | Acc]);
tokenise(Rest, [ ltoi([N1]) * 100_000, $. | Acc]);
tokenise([N1, N2, N3, N4, N5, N6 | Rest], Acc)
when ?is_num(N1), ?is_num(N2), ?is_num(N3), ?is_num(N4), ?is_num(N5), ?is_num(N6) ->
@ -522,7 +503,7 @@ format([$g|T], {_,{H,_,_}}=Dt, Acc) when H > 12 ->
format([$g|T], {_,{H,_,_}}=Dt, Acc) ->
format(T, Dt, [itol(H)|Acc]);
format([$G|T], {_,{H,_,_}}=Dt, Acc) ->
format(T, Dt, [pad2(H)|Acc]);
format(T, Dt, [itol(H)|Acc]);
format([$h|T], {_,{H,_,_}}=Dt, Acc) when H > 12 ->
format(T, Dt, [pad2(H-12)|Acc]);
format([$h|T], {_,{H,_,_}}=Dt, Acc) ->
@ -728,12 +709,6 @@ pad6(X) when is_integer(X) ->
ltoi(X) ->
list_to_integer(X).
-ifdef(unicode_str).
uppercase(Str) -> string:uppercase(Str).
-else.
uppercase(Str) -> string:to_upper(Str).
-endif.
%%%===================================================================
%%% Tests
%%%===================================================================
@ -743,7 +718,7 @@ uppercase(Str) -> string:to_upper(Str).
-define(DATE, {{2001,3,10},{17,16,17}}).
-define(DATEMS, {{2001,3,10},{17,16,17,123456}}).
-define(DATEMS, {{2001,3,10},{17,16,17,123_456}}).
-define(DATE_NOON, {{2001,3,10},{12,0,0}}).
-define(DATE_MIDNIGHT, {{2001,3,10},{0,0,0}}).
-define(ISO, "o \\WW").
@ -762,6 +737,8 @@ basic_format_test_() ->
?_assertEqual(format("H:i:s",?DATE), "17:16:17"),
?_assertEqual(format("z",?DATE), "68"),
?_assertEqual(format("D M j G:i:s Y",?DATE), "Sat Mar 10 17:16:17 2001"),
?_assertEqual(format("D M j G:i:s Y", {{2001,3,10},{5,16,17}}), "Sat Mar 10 5:16:17 2001"),
?_assertEqual(format("D M j H:i:s Y", {{2001,3,10},{5,16,17}}), "Sat Mar 10 05:16:17 2001"),
?_assertEqual(format("ga",?DATE_NOON), "12pm"),
?_assertEqual(format("gA",?DATE_NOON), "12PM"),
?_assertEqual(format("ga",?DATE_MIDNIGHT), "12am"),
@ -978,7 +955,7 @@ ms_test_() ->
Now=os:timestamp(),
[
?_assertEqual({{2012,12,12}, {12,12,12,1234}}, parse("2012-12-12T12:12:12.001234")),
?_assertEqual({{2012,12,12}, {12,12,12,123000}}, parse("2012-12-12T12:12:12.123")),
?_assertEqual({{2012,12,12}, {12,12,12,123_000}}, parse("2012-12-12T12:12:12.123")),
?_assertEqual(format("H:m:s.f \\m \\i\\s \\m\\o\\n\\t\\h",?DATEMS),
"17:03:17.123456 m is month"),
?_assertEqual(format("Y-m-d\\TH:i:s.f",?DATEMS),
@ -1017,21 +994,21 @@ format_iso8601_test_() ->
?_assertEqual("2001-03-10T17:16:17.000000Z",
format_iso8601({{2001,3,10},{17,16,17,0}})),
?_assertEqual("2001-03-10T17:16:17.100000Z",
format_iso8601({{2001,3,10},{17,16,17,100000}})),
format_iso8601({{2001,3,10},{17,16,17,100_000}})),
?_assertEqual("2001-03-10T17:16:17.120000Z",
format_iso8601({{2001,3,10},{17,16,17,120000}})),
format_iso8601({{2001,3,10},{17,16,17,120_000}})),
?_assertEqual("2001-03-10T17:16:17.123000Z",
format_iso8601({{2001,3,10},{17,16,17,123000}})),
format_iso8601({{2001,3,10},{17,16,17,123_000}})),
?_assertEqual("2001-03-10T17:16:17.123400Z",
format_iso8601({{2001,3,10},{17,16,17,123400}})),
format_iso8601({{2001,3,10},{17,16,17,123_400}})),
?_assertEqual("2001-03-10T17:16:17.123450Z",
format_iso8601({{2001,3,10},{17,16,17,123450}})),
format_iso8601({{2001,3,10},{17,16,17,123_450}})),
?_assertEqual("2001-03-10T17:16:17.123456Z",
format_iso8601({{2001,3,10},{17,16,17,123456}})),
format_iso8601({{2001,3,10},{17,16,17,123_456}})),
?_assertEqual("2001-03-10T17:16:17.023456Z",
format_iso8601({{2001,3,10},{17,16,17,23456}})),
format_iso8601({{2001,3,10},{17,16,17,23_456}})),
?_assertEqual("2001-03-10T17:16:17.003456Z",
format_iso8601({{2001,3,10},{17,16,17,3456}})),
format_iso8601({{2001,3,10},{17,16,17,3_456}})),
?_assertEqual("2001-03-10T17:16:17.000456Z",
format_iso8601({{2001,3,10},{17,16,17,456}})),
?_assertEqual("2001-03-10T17:16:17.000056Z",
@ -1043,21 +1020,21 @@ format_iso8601_test_() ->
?_assertEqual("2001-03-10T07:16:17.000000Z",
format_iso8601({{2001,3,10},{07,16,17,0}})),
?_assertEqual("2001-03-10T07:16:17.100000Z",
format_iso8601({{2001,3,10},{07,16,17,100000}})),
format_iso8601({{2001,3,10},{07,16,17,100_000}})),
?_assertEqual("2001-03-10T07:16:17.120000Z",
format_iso8601({{2001,3,10},{07,16,17,120000}})),
format_iso8601({{2001,3,10},{07,16,17,120_000}})),
?_assertEqual("2001-03-10T07:16:17.123000Z",
format_iso8601({{2001,3,10},{07,16,17,123000}})),
format_iso8601({{2001,3,10},{07,16,17,123_000}})),
?_assertEqual("2001-03-10T07:16:17.123400Z",
format_iso8601({{2001,3,10},{07,16,17,123400}})),
format_iso8601({{2001,3,10},{07,16,17,123_400}})),
?_assertEqual("2001-03-10T07:16:17.123450Z",
format_iso8601({{2001,3,10},{07,16,17,123450}})),
format_iso8601({{2001,3,10},{07,16,17,123_450}})),
?_assertEqual("2001-03-10T07:16:17.123456Z",
format_iso8601({{2001,3,10},{07,16,17,123456}})),
format_iso8601({{2001,3,10},{07,16,17,123_456}})),
?_assertEqual("2001-03-10T07:16:17.023456Z",
format_iso8601({{2001,3,10},{07,16,17,23456}})),
format_iso8601({{2001,3,10},{07,16,17,23_456}})),
?_assertEqual("2001-03-10T07:16:17.003456Z",
format_iso8601({{2001,3,10},{07,16,17,3456}})),
format_iso8601({{2001,3,10},{07,16,17,3_456}})),
?_assertEqual("2001-03-10T07:16:17.000456Z",
format_iso8601({{2001,3,10},{07,16,17,456}})),
?_assertEqual("2001-03-10T07:16:17.000056Z",
@ -1074,31 +1051,31 @@ parse_iso8601_test_() ->
parse("2001-03-10T17:16:17.000Z")),
?_assertEqual({{2001,3,10},{17,16,17,0}},
parse("2001-03-10T17:16:17.000000Z")),
?_assertEqual({{2001,3,10},{17,16,17,100000}},
?_assertEqual({{2001,3,10},{17,16,17,100_000}},
parse("2001-03-10T17:16:17.1Z")),
?_assertEqual({{2001,3,10},{17,16,17,120000}},
?_assertEqual({{2001,3,10},{17,16,17,120_000}},
parse("2001-03-10T17:16:17.12Z")),
?_assertEqual({{2001,3,10},{17,16,17,123000}},
?_assertEqual({{2001,3,10},{17,16,17,123_000}},
parse("2001-03-10T17:16:17.123Z")),
?_assertEqual({{2001,3,10},{17,16,17,123400}},
?_assertEqual({{2001,3,10},{17,16,17,123_400}},
parse("2001-03-10T17:16:17.1234Z")),
?_assertEqual({{2001,3,10},{17,16,17,123450}},
?_assertEqual({{2001,3,10},{17,16,17,123_450}},
parse("2001-03-10T17:16:17.12345Z")),
?_assertEqual({{2001,3,10},{17,16,17,123456}},
?_assertEqual({{2001,3,10},{17,16,17,123_456}},
parse("2001-03-10T17:16:17.123456Z")),
?_assertEqual({{2001,3,10},{15,16,17,100000}},
?_assertEqual({{2001,3,10},{15,16,17,100_000}},
parse("2001-03-10T16:16:17.1+01:00")),
?_assertEqual({{2001,3,10},{15,16,17,123456}},
?_assertEqual({{2001,3,10},{15,16,17,123_456}},
parse("2001-03-10T16:16:17.123456+01:00")),
?_assertEqual({{2001,3,10},{17,16,17,100000}},
?_assertEqual({{2001,3,10},{17,16,17,100_000}},
parse("2001-03-10T16:16:17.1-01:00")),
?_assertEqual({{2001,3,10},{17,16,17,123456}},
?_assertEqual({{2001,3,10},{17,16,17,123_456}},
parse("2001-03-10T16:16:17.123456-01:00")),
?_assertEqual({{2001,3,10},{17,16,17,456}},
parse("2001-03-10T17:16:17.000456Z")),
?_assertEqual({{2001,3,10},{17,16,17,123000}},
?_assertEqual({{2001,3,10},{17,16,17,123_000}},
parse("2001-03-10T17:16:17.123000Z"))
].

View file

@ -34,11 +34,7 @@
%%%===================================================================
%% This should be opaque, but that kills dialyzer so for now we export it
%% however you should not rely on the internal representation here
-ifdef(namespaced_types).
-type dictionary(_K, _V) :: dict:dict().
-else.
-type dictionary(_K, _V) :: dict().
-endif.
%%%===================================================================
%%% API

View file

@ -42,8 +42,6 @@
-type key(T) :: T.
-type value(T) :: T.
-ifdef(have_callback_support).
-callback new() -> any().
-callback has_key(key(any()), any()) -> boolean().
-callback get(key(any()), any()) -> any().
@ -55,27 +53,6 @@
-callback from_list([{key(any()), value(any())}]) -> any().
-callback keys(any()) -> [key(any())].
-else.
%% In the case where R14 or lower is being used to compile the system
%% we need to export a behaviour info
-export([behaviour_info/1]).
-spec behaviour_info(atom()) -> [{atom(), arity()}] | undefined.
behaviour_info(callbacks) ->
[{new, 0},
{has_key, 2},
{get, 2},
{add, 3},
{remove, 2},
{has_value, 2},
{size, 1},
{to_list, 1},
{from_list, 1},
{keys, 1}];
behaviour_info(_Other) ->
undefined.
-endif.
%%%===================================================================
%%% API
%%%===================================================================

View file

@ -80,7 +80,14 @@ copy(From, To) ->
copy_(From, To, [{file_info, [mode, time, owner, group]}]).
copy_(From, To, Options) ->
case file:copy(From, To) of
Linked
= case file:read_link(From) of
{ok, Linked0} -> Linked0;
{error, _} -> undefined
end,
case Linked =/= undefined orelse file:copy(From, To) of
true ->
file:make_symlink(Linked, To);
{ok, _} ->
copy_file_info(To, From, proplists:get_value(file_info, Options, []));
{error, Error} ->
@ -132,23 +139,20 @@ try_write_owner(To, #file_info{uid=OwnerId}) ->
try_write_group(To, #file_info{gid=OwnerId}) ->
file:write_file_info(To, #file_info{gid=OwnerId}).
%% @doc return an md5 checksum string or a binary. Same as unix utility of
%% same name.
%% @doc return the MD5 digest of a string or a binary,
%% named after the UNIX utility.
-spec md5sum(string() | binary()) -> string().
md5sum(Value) ->
hex(binary_to_list(erlang:md5(Value))).
bin_to_hex(crypto:hash(md5, Value)).
%% @doc return an sha1sum checksum string or a binary. Same as unix utility of
%% same name.
-ifdef(deprecated_crypto).
%% @doc return the SHA-1 digest of a string or a binary,
%% named after the UNIX utility.
-spec sha1sum(string() | binary()) -> string().
sha1sum(Value) ->
hex(binary_to_list(crypto:sha(Value))).
-else.
-spec sha1sum(string() | binary()) -> string().
sha1sum(Value) ->
hex(binary_to_list(crypto:hash(sha, Value))).
-endif.
bin_to_hex(crypto:hash(sha, Value)).
bin_to_hex(Bin) ->
hex(binary_to_list(Bin)).
%% @doc delete a file. Use the recursive option for directories.
%% <pre>
@ -219,7 +223,7 @@ real_dir_path(Path) ->
%% function of the same name.
-spec insecure_mkdtemp() -> TmpDirPath::file:name() | {error, term()}.
insecure_mkdtemp() ->
UniqueNumber = erlang:integer_to_list(erlang:trunc(random_uniform() * 1000000000000)),
UniqueNumber = erlang:integer_to_list(erlang:trunc(rand:uniform() * 1_000_000_000_000)),
TmpDirPath =
filename:join([tmp(), lists:flatten([".tmp_dir", UniqueNumber])]),
@ -245,7 +249,7 @@ mkdir_path(Path) ->
mkdir_p(Path).
%% @doc read a file from the file system. Provide UEX exeption on failure.
%% @doc read a file from the file system. Provide UEX exception on failure.
-spec read(FilePath::file:filename()) -> {ok, binary()} | {error, Reason::term()}.
read(FilePath) ->
%% Now that we are moving away from exceptions again this becomes
@ -254,7 +258,7 @@ read(FilePath) ->
file:read_file(FilePath).
%% @doc write a file to the file system. Provide UEX exeption on failure.
%% @doc write a file to the file system. Provide UEX exception on failure.
-spec write(FileName::file:filename(), Contents::string()) -> ok | {error, Reason::term()}.
write(FileName, Contents) ->
%% Now that we are moving away from exceptions again this becomes
@ -371,101 +375,3 @@ hex0(I) -> $0 + I.
sub_files(From) ->
{ok, SubFiles} = file:list_dir(From),
[filename:join(From, SubFile) || SubFile <- SubFiles].
-ifdef(rand_module).
random_uniform() ->
rand:uniform().
-else.
random_uniform() ->
random:seed(os:timestamp()),
random:uniform().
-endif.
%%%===================================================================
%%% Test Functions
%%%===================================================================
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
setup_test() ->
Dir = insecure_mkdtemp(),
mkdir_path(Dir),
?assertMatch(false, is_symlink(Dir)),
?assertMatch(true, filelib:is_dir(Dir)).
md5sum_test() ->
?assertMatch("cfcd208495d565ef66e7dff9f98764da", md5sum("0")).
sha1sum_test() ->
?assertMatch("b6589fc6ab0dc82cf12099d1c2d40ab994e8410c", sha1sum("0")).
file_test() ->
Dir = insecure_mkdtemp(),
TermFile = filename:join(Dir, "ec_file/dir/file.term"),
TermFileCopy = filename:join(Dir, "ec_file/dircopy/file.term"),
filelib:ensure_dir(TermFile),
filelib:ensure_dir(TermFileCopy),
write_term(TermFile, "term"),
?assertMatch({ok, <<"\"term\". ">>}, read(TermFile)),
copy(filename:dirname(TermFile),
filename:dirname(TermFileCopy),
[recursive]).
teardown_test() ->
Dir = insecure_mkdtemp(),
remove(Dir, [recursive]),
?assertMatch(false, filelib:is_dir(Dir)).
setup_base_and_target() ->
BaseDir = insecure_mkdtemp(),
DummyContents = <<"This should be deleted">>,
SourceDir = filename:join([BaseDir, "source"]),
ok = file:make_dir(SourceDir),
Name1 = filename:join([SourceDir, "fileone"]),
Name2 = filename:join([SourceDir, "filetwo"]),
Name3 = filename:join([SourceDir, "filethree"]),
NoName = filename:join([SourceDir, "noname"]),
ok = file:write_file(Name1, DummyContents),
ok = file:write_file(Name2, DummyContents),
ok = file:write_file(Name3, DummyContents),
ok = file:write_file(NoName, DummyContents),
{BaseDir, SourceDir, {Name1, Name2, Name3, NoName}}.
exists_test() ->
BaseDir = insecure_mkdtemp(),
SourceDir = filename:join([BaseDir, "source1"]),
NoName = filename:join([SourceDir, "noname"]),
ok = file:make_dir(SourceDir),
Name1 = filename:join([SourceDir, "fileone"]),
ok = file:write_file(Name1, <<"Testn">>),
?assertMatch(true, exists(Name1)),
?assertMatch(false, exists(NoName)).
real_path_test() ->
BaseDir = "foo",
Dir = filename:absname(filename:join(BaseDir, "source1")),
LinkDir = filename:join([BaseDir, "link"]),
ok = mkdir_p(Dir),
file:make_symlink(Dir, LinkDir),
?assertEqual(Dir, real_dir_path(LinkDir)),
?assertEqual(directory, type(Dir)),
?assertEqual(symlink, type(LinkDir)),
TermFile = filename:join(BaseDir, "test_file"),
ok = write_term(TermFile, foo),
?assertEqual(file, type(TermFile)),
?assertEqual(true, is_symlink(LinkDir)),
?assertEqual(false, is_symlink(Dir)).
find_test() ->
%% Create a directory in /tmp for the test. Clean everything afterwards
{BaseDir, _SourceDir, {Name1, Name2, Name3, _NoName}} = setup_base_and_target(),
Result = find(BaseDir, "file[a-z]+\$"),
?assertMatch(3, erlang:length(Result)),
?assertEqual(true, lists:member(Name1, Result)),
?assertEqual(true, lists:member(Name2, Result)),
?assertEqual(true, lists:member(Name3, Result)),
remove(BaseDir, [recursive]).
-endif.

View file

@ -135,79 +135,3 @@ from_list(List) when is_list(List) ->
-spec keys(gb_trees:tree(K,_V)) -> [ec_dictionary:key(K)].
keys(Data) ->
gb_trees:keys(Data).
%%%===================================================================
%%% Tests
%%%===================================================================
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
%% For me unit testing initially is about covering the obvious case. A
%% check to make sure that what you expect the tested functionality to
%% do, it actually does. As time goes on and people detect bugs you
%% add tests for those specific problems to the unit test suit.
%%
%% However, when getting started you can only test your basic
%% expectations. So here are the expectations I have for the add
%% functionality.
%%
%% 1) I can put arbitrary terms into the dictionary as keys
%% 2) I can put arbitrary terms into the dictionary as values
%% 3) When I put a value in the dictionary by a key, I can retrieve
%% that same value
%% 4) When I put a different value in the dictionary by key it does
%% not change other key value pairs.
%% 5) When I update a value the new value in available by the new key
%% 6) When a value does not exist a not found exception is created
add_test() ->
Dict0 = ec_dictionary:new(ec_gb_trees),
Key1 = foo,
Key2 = [1, 3],
Key3 = {"super"},
Key4 = <<"fabulous">>,
Key5 = {"Sona", 2, <<"Zuper">>},
Value1 = Key5,
Value2 = Key4,
Value3 = Key2,
Value4 = Key3,
Value5 = Key1,
Dict01 = ec_dictionary:add(Key1, Value1, Dict0),
Dict02 = ec_dictionary:add(Key3, Value3,
ec_dictionary:add(Key2, Value2,
Dict01)),
Dict1 =
ec_dictionary:add(Key5, Value5,
ec_dictionary:add(Key4, Value4,
Dict02)),
?assertMatch(Value1, ec_dictionary:get(Key1, Dict1)),
?assertMatch(Value2, ec_dictionary:get(Key2, Dict1)),
?assertMatch(Value3, ec_dictionary:get(Key3, Dict1)),
?assertMatch(Value4, ec_dictionary:get(Key4, Dict1)),
?assertMatch(Value5, ec_dictionary:get(Key5, Dict1)),
Dict2 = ec_dictionary:add(Key3, Value5,
ec_dictionary:add(Key2, Value4, Dict1)),
?assertMatch(Value1, ec_dictionary:get(Key1, Dict2)),
?assertMatch(Value4, ec_dictionary:get(Key2, Dict2)),
?assertMatch(Value5, ec_dictionary:get(Key3, Dict2)),
?assertMatch(Value4, ec_dictionary:get(Key4, Dict2)),
?assertMatch(Value5, ec_dictionary:get(Key5, Dict2)),
?assertThrow(not_found, ec_dictionary:get(should_blow_up, Dict2)),
?assertThrow(not_found, ec_dictionary:get("This should blow up too",
Dict2)).
-endif.

View file

@ -17,6 +17,13 @@
-export([new/0,
vsn/1]).
-ifdef(TEST).
-export([parse_tags/1,
get_patch_count/1,
collect_default_refcount/1
]).
-endif.
-export_type([t/0]).
%%%===================================================================
@ -34,7 +41,7 @@
new() ->
{}.
-spec vsn(t()) -> {ok, string()} | {error, Reason::any()}.
-spec vsn(t()|string()) -> {ok, string()} | {error, Reason::any()}.
vsn(Data) ->
{Vsn, RawRef, RawCount} = collect_default_refcount(Data),
{ok, build_vsn_string(Vsn, RawRef, RawCount)}.
@ -61,12 +68,7 @@ collect_default_refcount(Data) ->
build_vsn_string(Vsn, RawRef, RawCount) ->
%% Cleanup the tag and the Ref information. Basically leading 'v's and
%% whitespace needs to go away.
RefTag = case RawRef of
undefined ->
"";
RawRef ->
[".ref", re:replace(RawRef, "\\s", "", [global])]
end,
RefTag = [".ref", re:replace(RawRef, "\\s", "", [global])],
Count = erlang:iolist_to_binary(re:replace(RawCount, "\\s", "", [global])),
%% Create the valid [semver](http://semver.org) version from the tag
@ -80,28 +82,26 @@ build_vsn_string(Vsn, RawRef, RawCount) ->
get_patch_count(RawRef) ->
Ref = re:replace(RawRef, "\\s", "", [global]),
Cmd = io_lib:format("git rev-list --count ~s..HEAD",
Cmd = io_lib:format("git rev-list --count ~ts..HEAD",
[Ref]),
os:cmd(Cmd).
case os:cmd(Cmd) of
"fatal: " ++ _ ->
0;
Count ->
Count
end.
-spec parse_tags(t()) -> {string()|undefined, ec_semver:version_string()}.
-spec parse_tags(t()|string()) -> {string()|undefined, ec_semver:version_string()}.
parse_tags({}) ->
parse_tags("");
parse_tags(Pattern) ->
Cmd = io_lib:format("git describe --abbrev=0 --tags --match \"~s*\"", [Pattern]),
Cmd = io_lib:format("git describe --abbrev=0 --tags --match \"~ts*\"", [Pattern]),
Tag = os:cmd(Cmd),
Vsn = slice(Tag, len(Pattern)),
Vsn1 = trim(trim(Vsn, left, "v"), right, "\n"),
{Tag, Vsn1}.
-ifdef(unicode_str).
len(Str) -> string:length(Str).
trim(Str, right, Chars) -> string:trim(Str, trailing, Chars);
trim(Str, left, Chars) -> string:trim(Str, leading, Chars);
trim(Str, both, Chars) -> string:trim(Str, both, Chars).
slice(Str, Len) -> string:slice(Str, Len).
-else.
len(Str) -> string:len(Str).
trim(Str, Dir, [Chars|_]) -> string:strip(Str, Dir, Chars).
slice(Str, Len) -> string:substr(Str, Len + 1).
-endif.
case Tag of
"fatal: " ++ _ ->
{undefined, ""};
_ ->
Vsn = string:slice(Tag, string:length(Pattern)),
Vsn1 = string:trim(string:trim(Vsn, leading, "v"), trailing, "\n"),
{Tag, Vsn1}
end.

View file

@ -52,7 +52,7 @@ find(_Fun, []) ->
error.
%% @doc Fetch a value from the list. If the function returns true the
%% value is returend. If processing reaches the end of the list and
%% value is returned. If processing reaches the end of the list and
%% the function has never returned true an exception not_found is
%% thrown.
-spec fetch(fun(), list()) -> term().
@ -63,184 +63,3 @@ fetch(Fun, List) when is_list(List), is_function(Fun) ->
error ->
throw(not_found)
end.
%%%===================================================================
%%% Test Functions
%%%===================================================================
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
find1_test() ->
TestData = [1, 2, 3, 4, 5, 6],
Result = find(fun(5) ->
true;
(_) ->
false
end,
TestData),
?assertMatch({ok, 5}, Result),
Result2 = find(fun(37) ->
true;
(_) ->
false
end,
TestData),
?assertMatch(error, Result2).
find2_test() ->
TestData = ["one", "two", "three", "four", "five", "six"],
Result = find(fun("five") ->
true;
(_) ->
false
end,
TestData),
?assertMatch({ok, "five"}, Result),
Result2 = find(fun(super_duper) ->
true;
(_) ->
false
end,
TestData),
?assertMatch(error, Result2).
find3_test() ->
TestData = [{"one", 1}, {"two", 2}, {"three", 3}, {"four", 5}, {"five", 5},
{"six", 6}],
Result = find(fun({"one", 1}) ->
true;
(_) ->
false
end,
TestData),
?assertMatch({ok, {"one", 1}}, Result),
Result2 = find(fun([fo, bar, baz]) ->
true;
({"onehundred", 100}) ->
true;
(_) ->
false
end,
TestData),
?assertMatch(error, Result2).
fetch1_test() ->
TestData = [1, 2, 3, 4, 5, 6],
Result = fetch(fun(5) ->
true;
(_) ->
false
end,
TestData),
?assertMatch(5, Result),
?assertThrow(not_found,
fetch(fun(37) ->
true;
(_) ->
false
end,
TestData)).
fetch2_test() ->
TestData = ["one", "two", "three", "four", "five", "six"],
Result = fetch(fun("five") ->
true;
(_) ->
false
end,
TestData),
?assertMatch("five", Result),
?assertThrow(not_found,
fetch(fun(super_duper) ->
true;
(_) ->
false
end,
TestData)).
fetch3_test() ->
TestData = [{"one", 1}, {"two", 2}, {"three", 3}, {"four", 5}, {"five", 5},
{"six", 6}],
Result = fetch(fun({"one", 1}) ->
true;
(_) ->
false
end,
TestData),
?assertMatch({"one", 1}, Result),
?assertThrow(not_found,
fetch(fun([fo, bar, baz]) ->
true;
({"onehundred", 100}) ->
true;
(_) ->
false
end,
TestData)).
search1_test() ->
TestData = [1, 2, 3, 4, 5, 6],
Result = search(fun(5) ->
{ok, 5};
(_) ->
not_found
end,
TestData),
?assertMatch({ok, 5, 5}, Result),
Result2 = search(fun(37) ->
{ok, 37};
(_) ->
not_found
end,
TestData),
?assertMatch(not_found, Result2).
search2_test() ->
TestData = [1, 2, 3, 4, 5, 6],
Result = search(fun(1) ->
{ok, 10};
(_) ->
not_found
end,
TestData),
?assertMatch({ok, 10, 1}, Result),
Result2 = search(fun(6) ->
{ok, 37};
(_) ->
not_found
end,
TestData),
?assertMatch({ok, 37, 6}, Result2).
search3_test() ->
TestData = [1, 2, 3, 4, 5, 6],
Result = search(fun(10) ->
{ok, 10};
(_) ->
not_found
end,
TestData),
?assertMatch(not_found, Result),
Result2 = search(fun(-1) ->
{ok, 37};
(_) ->
not_found
end,
TestData),
?assertMatch(not_found, Result2).
-endif.

View file

@ -30,7 +30,7 @@
%%% most list operations parallel. It can operate on each element in
%%% parallel, for IO-bound operations, on sublists in parallel, for
%%% taking advantage of multi-core machines with CPU-bound operations,
%%% and across erlang nodes, for parallizing inside a cluster. It
%%% and across erlang nodes, for parallelizing inside a cluster. It
%%% handles errors and node failures. It can be configured, tuned, and
%%% tweaked to get optimal performance while minimizing overhead.
%%%
@ -38,7 +38,7 @@
%%% lists, returning exactly the same result, and having both a form
%%% with an identical syntax that operates on each element in parallel
%%% and a form which takes an optional "malt", a specification for how
%%% to parallize the operation.
%%% to parallelize the operation.
%%%
%%% fold is the one exception, parallel fold is different from linear
%%% fold. This module also include a simple mapreduce implementation,
@ -169,7 +169,7 @@
%%% processes. If one of them does a non-normal exit, plists receives
%%% the 'DOWN' message believing it to be from one of its own
%%% processes. The error propagation system goes into effect, which
%%% results in the error occuring in the calling process.
%%% results in the error occurring in the calling process.
%%%
-module(ec_plists).
@ -330,14 +330,14 @@ fold(Fun, Fuse, InitAcc, List, Malt) ->
end,
runmany(Fun2, Fuse, List, Malt).
%% @doc Similiar to foreach in module
%% @doc Similar to foreach in module
%% <a href="http://www.erlang.org/doc/man/lists.html">lists</a>
%% except it makes no guarantee about the order it processes list elements.
-spec foreach(fun(), list()) -> ok.
foreach(Fun, List) ->
foreach(Fun, List, 1).
%% @doc Similiar to foreach in module
%% @doc Similar to foreach in module
%% <a href="http://www.erlang.org/doc/man/lists.html">lists</a>
%% except it makes no guarantee about the order it processes list elements.
-spec foreach(fun(), list(), malt()) -> ok.
@ -432,8 +432,8 @@ sort(Fun, List) ->
%%
%% sort splits the list into sublists and sorts them, and it merges the
%% sorted lists together. These are done in parallel. Each sublist is
%% sorted in a seperate process, and each merging of results is done in a
%% seperate process. Malt defaults to 100, causing the list to be split into
%% sorted in a separate process, and each merging of results is done in a
%% separate process. Malt defaults to 100, causing the list to be split into
%% 100-element sublists.
-spec sort(fun(), list(), malt()) -> list().
sort(Fun, List, Malt) ->
@ -464,11 +464,11 @@ usort(Fun, List) ->
%%
%% usort splits the list into sublists and sorts them, and it merges the
%% sorted lists together. These are done in parallel. Each sublist is
%% sorted in a seperate process, and each merging of results is done in a
%% seperate process. Malt defaults to 100, causing the list to be split into
%% sorted in a separate process, and each merging of results is done in a
%% separate process. Malt defaults to 100, causing the list to be split into
%% 100-element sublists.
%%
%% usort removes duplicate elments while it sorts.
%% usort removes duplicate elements while it sorts.
-spec usort(fun(), list(), malt()) -> list().
usort(Fun, List, Malt) ->
Fun2 = fun (L) ->
@ -480,16 +480,9 @@ usort(Fun, List, Malt) ->
runmany(Fun2, {recursive, Fuse}, List, Malt).
%% @doc Like below, assumes default MapMalt of 1.
-ifdef(namespaced_types).
-spec mapreduce(MapFunc, list()) -> dict:dict() when
MapFunc :: fun((term()) -> DeepListOfKeyValuePairs),
DeepListOfKeyValuePairs :: [DeepListOfKeyValuePairs] | {Key::term(), Value::term()}.
-else.
-spec mapreduce(MapFunc, list()) -> dict() when
MapFunc :: fun((term()) -> DeepListOfKeyValuePairs),
DeepListOfKeyValuePairs :: [DeepListOfKeyValuePairs] | {Key::term(), Value::term()}.
-endif.
mapreduce(MapFunc, List) ->
mapreduce(MapFunc, List, 1).
@ -514,21 +507,14 @@ mapreduce(MapFunc, List, MapMalt) ->
%% reducer's final state.
%%
%% MapMalt is the malt for the mapping operation, with a default value of 1,
%% meaning each element of the list is mapped by a seperate process.
%% meaning each element of the list is mapped by a separate process.
%%
%% mapreduce requires OTP R11B, or it may leave monitoring messages in the
%% message queue.
-ifdef(namespaced_types).
-spec mapreduce(MapFunc, list(), InitState::term(), ReduceFunc, malt()) -> dict:dict() when
MapFunc :: fun((term()) -> DeepListOfKeyValuePairs),
DeepListOfKeyValuePairs :: [DeepListOfKeyValuePairs] | {Key::term(), Value::term()},
ReduceFunc :: fun((OldState::term(), Key::term(), Value::term()) -> NewState::term()).
-else.
-spec mapreduce(MapFunc, list(), InitState::term(), ReduceFunc, malt()) -> dict() when
MapFunc :: fun((term()) -> DeepListOfKeyValuePairs),
DeepListOfKeyValuePairs :: [DeepListOfKeyValuePairs] | {Key::term(), Value::term()},
ReduceFunc :: fun((OldState::term(), Key::term(), Value::term()) -> NewState::term()).
-endif.
mapreduce(MapFunc, List, InitState, ReduceFunc, MapMalt) ->
Parent = self(),
{Reducer, ReducerRef} =
@ -586,7 +572,7 @@ add_key(Dict, Key, Value) ->
end.
%% @doc Like below, but assumes a Malt of 1,
%% meaning each element of the list is processed by a seperate process.
%% meaning each element of the list is processed by a separate process.
-spec runmany(fun(), fuse(), list()) -> term().
runmany(Fun, Fuse, List) ->
runmany(Fun, Fuse, List, 1).
@ -615,7 +601,7 @@ runmany(Fun, Fuse, List) ->
%% continues fusing pairs of results until it is down to one.
%%
%% Recursive fuse is down in parallel with processing the sublists, and a
%% process is spawned to fuse each pair of results. It is a parallized
%% process is spawned to fuse each pair of results. It is a parallelized
%% algorithm. Linear fuse is done after all results of processing sublists
%% have been collected, and can only run in a single process.
%%
@ -691,7 +677,7 @@ runmany(Fun, {recursive, Fuse}, List, local, Split, []) ->
%% or {nodes, NodeList}. Degenerates recursive fuse into linear fuse.
runmany(Fun, Fuse, List, local, Split, []);
runmany(Fun, Fuse, List, Nodes, no_split, []) ->
%% by default, operate on each element seperately
%% by default, operate on each element separately
runmany(Fun, Fuse, List, Nodes, 1, []);
runmany(Fun, Fuse, List, local, Split, []) ->
List2 = splitmany(List, Split),
@ -872,24 +858,9 @@ cluster_runmany(_, _, [_Non|_Empty], []=_Nodes, []=_Running, _) ->
%% We have data, but no nodes either available or occupied
erlang:exit(allnodescrashed).
-ifdef(fun_stacktrace).
runmany_wrap(Fun, Parent) ->
try
Fun
catch
exit:siblingdied ->
ok;
exit:Reason ->
Parent ! {erlang:self(), error, Reason};
error:R ->
Parent ! {erlang:self(), error, {R, erlang:get_stacktrace()}};
throw:R ->
Parent ! {erlang:self(), error, {{nocatch, R}, erlang:get_stacktrace()}}
end.
-else.
runmany_wrap(Fun, Parent) ->
try
Fun
Fun()
catch
exit:siblingdied ->
ok;
@ -900,7 +871,6 @@ runmany_wrap(Fun, Parent) ->
throw:R:Stacktrace ->
Parent ! {erlang:self(), error, {{nocatch, R}, Stacktrace}}
end.
-endif.
delete_running(Pid, [{Pid, Node, List}|Running], Acc) ->
{Running ++ Acc, Node, List};

View file

@ -32,7 +32,7 @@
%%% representation of a dictionary, where a red-black tree is used to
%%% store the keys and values.
%%%
%%% This module implents exactly the same interface as the module
%%% This module implements exactly the same interface as the module
%%% ec_dictionary but with a defined representation. One difference is
%%% that while dict considers two keys as different if they do not
%%% match (=:=), this module considers two keys as different if and
@ -296,7 +296,7 @@ to_list(empty, List) -> List;
to_list({_, A, Xk, Xv, B}, List) ->
to_list(A, [{Xk, Xv} | to_list(B, List)]).
%% Balance a tree afer (possibly) adding a node to the left/right.
%% Balance a tree after (possibly) adding a node to the left/right.
-spec lbalance(color(), dictionary(K, V),
ec_dictionary:key(K), ec_dictionary:value(V),
dictionary(K, V)) ->

View file

@ -202,13 +202,13 @@ pes(VsnA, VsnB) ->
%%%===================================================================
%%% Friend Functions
%%%===================================================================
%% @doc helper function for the peg grammer to parse the iolist into a semver
%% @doc helper function for the peg grammar to parse the iolist into a semver
-spec internal_parse_version(iolist()) -> semver().
internal_parse_version([MMP, AlphaPart, BuildPart, _]) ->
{parse_major_minor_patch_minpatch(MMP), {parse_alpha_part(AlphaPart),
parse_alpha_part(BuildPart)}}.
%% @doc helper function for the peg grammer to parse the iolist into a major_minor_patch
%% @doc helper function for the peg grammar to parse the iolist into a major_minor_patch
-spec parse_major_minor_patch_minpatch(iolist()) -> major_minor_patch_minpatch().
parse_major_minor_patch_minpatch([MajVsn, [], [], []]) ->
strip_maj_version(MajVsn);
@ -224,7 +224,7 @@ parse_major_minor_patch_minpatch([MajVsn,
[<<".">>, MinPatch]]) ->
{strip_maj_version(MajVsn), MinVsn, PatchVsn, MinPatch}.
%% @doc helper function for the peg grammer to parse the iolist into an alpha part
%% @doc helper function for the peg grammar to parse the iolist into an alpha part
-spec parse_alpha_part(iolist()) -> [alpha_part()].
parse_alpha_part([]) ->
[];
@ -309,455 +309,3 @@ internal_pes(VsnA, {{LM, LMI, LP, LMP}, Alpha})
lt(VsnA, {{LM, LMI, LP + 1, 0}, {[], []}});
internal_pes(Vsn, LVsn) ->
gte(Vsn, LVsn).
%%%===================================================================
%%% Test Functions
%%%===================================================================
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
eql_test() ->
?assertMatch(true, eql("1.0.0-alpha",
"1.0.0-alpha")),
?assertMatch(true, eql("v1.0.0-alpha",
"1.0.0-alpha")),
?assertMatch(true, eql("1",
"1.0.0")),
?assertMatch(true, eql("v1",
"v1.0.0")),
?assertMatch(true, eql("1.0",
"1.0.0")),
?assertMatch(true, eql("1.0.0",
"1")),
?assertMatch(true, eql("1.0.0.0",
"1")),
?assertMatch(true, eql("1.0+alpha.1",
"1.0.0+alpha.1")),
?assertMatch(true, eql("1.0-alpha.1+build.1",
"1.0.0-alpha.1+build.1")),
?assertMatch(true, eql("1.0-alpha.1+build.1",
"1.0.0.0-alpha.1+build.1")),
?assertMatch(true, eql("1.0-alpha.1+build.1",
"v1.0.0.0-alpha.1+build.1")),
?assertMatch(true, eql("1.0-pre-alpha.1",
"1.0.0-pre-alpha.1")),
?assertMatch(true, eql("aa", "aa")),
?assertMatch(true, eql("AA.BB", "AA.BB")),
?assertMatch(true, eql("BBB-super", "BBB-super")),
?assertMatch(true, not eql("1.0.0",
"1.0.1")),
?assertMatch(true, not eql("1.0.0-alpha",
"1.0.1+alpha")),
?assertMatch(true, not eql("1.0.0+build.1",
"1.0.1+build.2")),
?assertMatch(true, not eql("1.0.0.0+build.1",
"1.0.0.1+build.2")),
?assertMatch(true, not eql("FFF", "BBB")),
?assertMatch(true, not eql("1", "1BBBB")).
gt_test() ->
?assertMatch(true, gt("1.0.0-alpha.1",
"1.0.0-alpha")),
?assertMatch(true, gt("1.0.0.1-alpha.1",
"1.0.0.1-alpha")),
?assertMatch(true, gt("1.0.0.4-alpha.1",
"1.0.0.2-alpha")),
?assertMatch(true, gt("1.0.0.0-alpha.1",
"1.0.0-alpha")),
?assertMatch(true, gt("1.0.0-beta.2",
"1.0.0-alpha.1")),
?assertMatch(true, gt("1.0.0-beta.11",
"1.0.0-beta.2")),
?assertMatch(true, gt("1.0.0-pre-alpha.14",
"1.0.0-pre-alpha.3")),
?assertMatch(true, gt("1.0.0-beta.11",
"1.0.0.0-beta.2")),
?assertMatch(true, gt("1.0.0-rc.1", "1.0.0-beta.11")),
?assertMatch(true, gt("1.0.0-rc.1+build.1", "1.0.0-rc.1")),
?assertMatch(true, gt("1.0.0", "1.0.0-rc.1+build.1")),
?assertMatch(true, gt("1.0.0+0.3.7", "1.0.0")),
?assertMatch(true, gt("1.3.7+build", "1.0.0+0.3.7")),
?assertMatch(true, gt("1.3.7+build.2.b8f12d7",
"1.3.7+build")),
?assertMatch(true, gt("1.3.7+build.2.b8f12d7",
"1.3.7.0+build")),
?assertMatch(true, gt("1.3.7+build.11.e0f985a",
"1.3.7+build.2.b8f12d7")),
?assertMatch(true, gt("aa.cc",
"aa.bb")),
?assertMatch(true, not gt("1.0.0-alpha",
"1.0.0-alpha.1")),
?assertMatch(true, not gt("1.0.0-alpha",
"1.0.0.0-alpha.1")),
?assertMatch(true, not gt("1.0.0-alpha.1",
"1.0.0-beta.2")),
?assertMatch(true, not gt("1.0.0-beta.2",
"1.0.0-beta.11")),
?assertMatch(true, not gt("1.0.0-beta.11",
"1.0.0-rc.1")),
?assertMatch(true, not gt("1.0.0-pre-alpha.3",
"1.0.0-pre-alpha.14")),
?assertMatch(true, not gt("1.0.0-rc.1",
"1.0.0-rc.1+build.1")),
?assertMatch(true, not gt("1.0.0-rc.1+build.1",
"1.0.0")),
?assertMatch(true, not gt("1.0.0",
"1.0.0+0.3.7")),
?assertMatch(true, not gt("1.0.0+0.3.7",
"1.3.7+build")),
?assertMatch(true, not gt("1.3.7+build",
"1.3.7+build.2.b8f12d7")),
?assertMatch(true, not gt("1.3.7+build.2.b8f12d7",
"1.3.7+build.11.e0f985a")),
?assertMatch(true, not gt("1.0.0-alpha",
"1.0.0-alpha")),
?assertMatch(true, not gt("1",
"1.0.0")),
?assertMatch(true, not gt("aa.bb",
"aa.bb")),
?assertMatch(true, not gt("aa.cc",
"aa.dd")),
?assertMatch(true, not gt("1.0",
"1.0.0")),
?assertMatch(true, not gt("1.0.0",
"1")),
?assertMatch(true, not gt("1.0+alpha.1",
"1.0.0+alpha.1")),
?assertMatch(true, not gt("1.0-alpha.1+build.1",
"1.0.0-alpha.1+build.1")).
lt_test() ->
?assertMatch(true, lt("1.0.0-alpha",
"1.0.0-alpha.1")),
?assertMatch(true, lt("1.0.0-alpha",
"1.0.0.0-alpha.1")),
?assertMatch(true, lt("1.0.0-alpha.1",
"1.0.0-beta.2")),
?assertMatch(true, lt("1.0.0-beta.2",
"1.0.0-beta.11")),
?assertMatch(true, lt("1.0.0-pre-alpha.3",
"1.0.0-pre-alpha.14")),
?assertMatch(true, lt("1.0.0-beta.11",
"1.0.0-rc.1")),
?assertMatch(true, lt("1.0.0.1-beta.11",
"1.0.0.1-rc.1")),
?assertMatch(true, lt("1.0.0-rc.1",
"1.0.0-rc.1+build.1")),
?assertMatch(true, lt("1.0.0-rc.1+build.1",
"1.0.0")),
?assertMatch(true, lt("1.0.0",
"1.0.0+0.3.7")),
?assertMatch(true, lt("1.0.0+0.3.7",
"1.3.7+build")),
?assertMatch(true, lt("1.3.7+build",
"1.3.7+build.2.b8f12d7")),
?assertMatch(true, lt("1.3.7+build.2.b8f12d7",
"1.3.7+build.11.e0f985a")),
?assertMatch(true, not lt("1.0.0-alpha",
"1.0.0-alpha")),
?assertMatch(true, not lt("1",
"1.0.0")),
?assertMatch(true, lt("1",
"1.0.0.1")),
?assertMatch(true, lt("AA.DD",
"AA.EE")),
?assertMatch(true, not lt("1.0",
"1.0.0")),
?assertMatch(true, not lt("1.0.0.0",
"1")),
?assertMatch(true, not lt("1.0+alpha.1",
"1.0.0+alpha.1")),
?assertMatch(true, not lt("AA.DD", "AA.CC")),
?assertMatch(true, not lt("1.0-alpha.1+build.1",
"1.0.0-alpha.1+build.1")),
?assertMatch(true, not lt("1.0.0-alpha.1",
"1.0.0-alpha")),
?assertMatch(true, not lt("1.0.0-beta.2",
"1.0.0-alpha.1")),
?assertMatch(true, not lt("1.0.0-beta.11",
"1.0.0-beta.2")),
?assertMatch(true, not lt("1.0.0-pre-alpha.14",
"1.0.0-pre-alpha.3")),
?assertMatch(true, not lt("1.0.0-rc.1", "1.0.0-beta.11")),
?assertMatch(true, not lt("1.0.0-rc.1+build.1", "1.0.0-rc.1")),
?assertMatch(true, not lt("1.0.0", "1.0.0-rc.1+build.1")),
?assertMatch(true, not lt("1.0.0+0.3.7", "1.0.0")),
?assertMatch(true, not lt("1.3.7+build", "1.0.0+0.3.7")),
?assertMatch(true, not lt("1.3.7+build.2.b8f12d7",
"1.3.7+build")),
?assertMatch(true, not lt("1.3.7+build.11.e0f985a",
"1.3.7+build.2.b8f12d7")).
gte_test() ->
?assertMatch(true, gte("1.0.0-alpha",
"1.0.0-alpha")),
?assertMatch(true, gte("1",
"1.0.0")),
?assertMatch(true, gte("1.0",
"1.0.0")),
?assertMatch(true, gte("1.0.0",
"1")),
?assertMatch(true, gte("1.0.0.0",
"1")),
?assertMatch(true, gte("1.0+alpha.1",
"1.0.0+alpha.1")),
?assertMatch(true, gte("1.0-alpha.1+build.1",
"1.0.0-alpha.1+build.1")),
?assertMatch(true, gte("1.0.0-alpha.1+build.1",
"1.0.0.0-alpha.1+build.1")),
?assertMatch(true, gte("1.0.0-alpha.1",
"1.0.0-alpha")),
?assertMatch(true, gte("1.0.0-pre-alpha.2",
"1.0.0-pre-alpha")),
?assertMatch(true, gte("1.0.0-beta.2",
"1.0.0-alpha.1")),
?assertMatch(true, gte("1.0.0-beta.11",
"1.0.0-beta.2")),
?assertMatch(true, gte("aa.bb", "aa.bb")),
?assertMatch(true, gte("dd", "aa")),
?assertMatch(true, gte("1.0.0-rc.1", "1.0.0-beta.11")),
?assertMatch(true, gte("1.0.0-rc.1+build.1", "1.0.0-rc.1")),
?assertMatch(true, gte("1.0.0", "1.0.0-rc.1+build.1")),
?assertMatch(true, gte("1.0.0+0.3.7", "1.0.0")),
?assertMatch(true, gte("1.3.7+build", "1.0.0+0.3.7")),
?assertMatch(true, gte("1.3.7+build.2.b8f12d7",
"1.3.7+build")),
?assertMatch(true, gte("1.3.7+build.11.e0f985a",
"1.3.7+build.2.b8f12d7")),
?assertMatch(true, not gte("1.0.0-alpha",
"1.0.0-alpha.1")),
?assertMatch(true, not gte("1.0.0-pre-alpha",
"1.0.0-pre-alpha.1")),
?assertMatch(true, not gte("CC", "DD")),
?assertMatch(true, not gte("1.0.0-alpha.1",
"1.0.0-beta.2")),
?assertMatch(true, not gte("1.0.0-beta.2",
"1.0.0-beta.11")),
?assertMatch(true, not gte("1.0.0-beta.11",
"1.0.0-rc.1")),
?assertMatch(true, not gte("1.0.0-rc.1",
"1.0.0-rc.1+build.1")),
?assertMatch(true, not gte("1.0.0-rc.1+build.1",
"1.0.0")),
?assertMatch(true, not gte("1.0.0",
"1.0.0+0.3.7")),
?assertMatch(true, not gte("1.0.0+0.3.7",
"1.3.7+build")),
?assertMatch(true, not gte("1.0.0",
"1.0.0+build.1")),
?assertMatch(true, not gte("1.3.7+build",
"1.3.7+build.2.b8f12d7")),
?assertMatch(true, not gte("1.3.7+build.2.b8f12d7",
"1.3.7+build.11.e0f985a")).
lte_test() ->
?assertMatch(true, lte("1.0.0-alpha",
"1.0.0-alpha.1")),
?assertMatch(true, lte("1.0.0-alpha.1",
"1.0.0-beta.2")),
?assertMatch(true, lte("1.0.0-beta.2",
"1.0.0-beta.11")),
?assertMatch(true, lte("1.0.0-pre-alpha.2",
"1.0.0-pre-alpha.11")),
?assertMatch(true, lte("1.0.0-beta.11",
"1.0.0-rc.1")),
?assertMatch(true, lte("1.0.0-rc.1",
"1.0.0-rc.1+build.1")),
?assertMatch(true, lte("1.0.0-rc.1+build.1",
"1.0.0")),
?assertMatch(true, lte("1.0.0",
"1.0.0+0.3.7")),
?assertMatch(true, lte("1.0.0+0.3.7",
"1.3.7+build")),
?assertMatch(true, lte("1.3.7+build",
"1.3.7+build.2.b8f12d7")),
?assertMatch(true, lte("1.3.7+build.2.b8f12d7",
"1.3.7+build.11.e0f985a")),
?assertMatch(true, lte("1.0.0-alpha",
"1.0.0-alpha")),
?assertMatch(true, lte("1",
"1.0.0")),
?assertMatch(true, lte("1.0",
"1.0.0")),
?assertMatch(true, lte("1.0.0",
"1")),
?assertMatch(true, lte("1.0+alpha.1",
"1.0.0+alpha.1")),
?assertMatch(true, lte("1.0.0.0+alpha.1",
"1.0.0+alpha.1")),
?assertMatch(true, lte("1.0-alpha.1+build.1",
"1.0.0-alpha.1+build.1")),
?assertMatch(true, lte("aa","cc")),
?assertMatch(true, lte("cc","cc")),
?assertMatch(true, not lte("1.0.0-alpha.1",
"1.0.0-alpha")),
?assertMatch(true, not lte("1.0.0-pre-alpha.2",
"1.0.0-pre-alpha")),
?assertMatch(true, not lte("cc", "aa")),
?assertMatch(true, not lte("1.0.0-beta.2",
"1.0.0-alpha.1")),
?assertMatch(true, not lte("1.0.0-beta.11",
"1.0.0-beta.2")),
?assertMatch(true, not lte("1.0.0-rc.1", "1.0.0-beta.11")),
?assertMatch(true, not lte("1.0.0-rc.1+build.1", "1.0.0-rc.1")),
?assertMatch(true, not lte("1.0.0", "1.0.0-rc.1+build.1")),
?assertMatch(true, not lte("1.0.0+0.3.7", "1.0.0")),
?assertMatch(true, not lte("1.3.7+build", "1.0.0+0.3.7")),
?assertMatch(true, not lte("1.3.7+build.2.b8f12d7",
"1.3.7+build")),
?assertMatch(true, not lte("1.3.7+build.11.e0f985a",
"1.3.7+build.2.b8f12d7")).
between_test() ->
?assertMatch(true, between("1.0.0-alpha",
"1.0.0-alpha.3",
"1.0.0-alpha.2")),
?assertMatch(true, between("1.0.0-alpha.1",
"1.0.0-beta.2",
"1.0.0-alpha.25")),
?assertMatch(true, between("1.0.0-beta.2",
"1.0.0-beta.11",
"1.0.0-beta.7")),
?assertMatch(true, between("1.0.0-pre-alpha.2",
"1.0.0-pre-alpha.11",
"1.0.0-pre-alpha.7")),
?assertMatch(true, between("1.0.0-beta.11",
"1.0.0-rc.3",
"1.0.0-rc.1")),
?assertMatch(true, between("1.0.0-rc.1",
"1.0.0-rc.1+build.3",
"1.0.0-rc.1+build.1")),
?assertMatch(true, between("1.0.0.0-rc.1",
"1.0.0-rc.1+build.3",
"1.0.0-rc.1+build.1")),
?assertMatch(true, between("1.0.0-rc.1+build.1",
"1.0.0",
"1.0.0-rc.33")),
?assertMatch(true, between("1.0.0",
"1.0.0+0.3.7",
"1.0.0+0.2")),
?assertMatch(true, between("1.0.0+0.3.7",
"1.3.7+build",
"1.2")),
?assertMatch(true, between("1.3.7+build",
"1.3.7+build.2.b8f12d7",
"1.3.7+build.1")),
?assertMatch(true, between("1.3.7+build.2.b8f12d7",
"1.3.7+build.11.e0f985a",
"1.3.7+build.10.a36faa")),
?assertMatch(true, between("1.0.0-alpha",
"1.0.0-alpha",
"1.0.0-alpha")),
?assertMatch(true, between("1",
"1.0.0",
"1.0.0")),
?assertMatch(true, between("1.0",
"1.0.0",
"1.0.0")),
?assertMatch(true, between("1.0",
"1.0.0.0",
"1.0.0.0")),
?assertMatch(true, between("1.0.0",
"1",
"1")),
?assertMatch(true, between("1.0+alpha.1",
"1.0.0+alpha.1",
"1.0.0+alpha.1")),
?assertMatch(true, between("1.0-alpha.1+build.1",
"1.0.0-alpha.1+build.1",
"1.0.0-alpha.1+build.1")),
?assertMatch(true, between("aaa",
"ddd",
"cc")),
?assertMatch(true, not between("1.0.0-alpha.1",
"1.0.0-alpha.22",
"1.0.0")),
?assertMatch(true, not between("1.0.0-pre-alpha.1",
"1.0.0-pre-alpha.22",
"1.0.0")),
?assertMatch(true, not between("1.0.0",
"1.0.0-alpha.1",
"2.0")),
?assertMatch(true, not between("1.0.0-beta.1",
"1.0.0-beta.11",
"1.0.0-alpha")),
?assertMatch(true, not between("1.0.0-beta.11", "1.0.0-rc.1",
"1.0.0-rc.22")),
?assertMatch(true, not between("aaa", "ddd", "zzz")).
pes_test() ->
?assertMatch(true, pes("1.0.0-rc.0", "1.0.0-rc.0")),
?assertMatch(true, pes("1.0.0-rc.1", "1.0.0-rc.0")),
?assertMatch(true, pes("1.0.0", "1.0.0-rc.0")),
?assertMatch(false, pes("1.0.0-rc.0", "1.0.0-rc.1")),
?assertMatch(true, pes("2.6.0", "2.6")),
?assertMatch(true, pes("2.7", "2.6")),
?assertMatch(true, pes("2.8", "2.6")),
?assertMatch(true, pes("2.9", "2.6")),
?assertMatch(true, pes("A.B", "A.A")),
?assertMatch(true, not pes("3.0.0", "2.6")),
?assertMatch(true, not pes("2.5", "2.6")),
?assertMatch(true, pes("2.6.5", "2.6.5")),
?assertMatch(true, pes("2.6.6", "2.6.5")),
?assertMatch(true, pes("2.6.7", "2.6.5")),
?assertMatch(true, pes("2.6.8", "2.6.5")),
?assertMatch(true, pes("2.6.9", "2.6.5")),
?assertMatch(true, pes("2.6.0.9", "2.6.0.5")),
?assertMatch(true, not pes("2.7", "2.6.5")),
?assertMatch(true, not pes("2.1.7", "2.1.6.5")),
?assertMatch(true, not pes("A.A", "A.B")),
?assertMatch(true, not pes("2.5", "2.6.5")).
parse_test() ->
?assertEqual({1, {[],[]}}, parse(<<"1">>)),
?assertEqual({{1,2,34},{[],[]}}, parse(<<"1.2.34">>)),
?assertEqual({<<"a">>, {[],[]}}, parse(<<"a">>)),
?assertEqual({{<<"a">>,<<"b">>}, {[],[]}}, parse(<<"a.b">>)),
?assertEqual({1, {[],[]}}, parse(<<"1">>)),
?assertEqual({{1,2}, {[],[]}}, parse(<<"1.2">>)),
?assertEqual({{1,2,2}, {[],[]}}, parse(<<"1.2.2">>)),
?assertEqual({{1,99,2}, {[],[]}}, parse(<<"1.99.2">>)),
?assertEqual({{1,99,2}, {[<<"alpha">>],[]}}, parse(<<"1.99.2-alpha">>)),
?assertEqual({{1,99,2}, {[<<"alpha">>,1], []}}, parse(<<"1.99.2-alpha.1">>)),
?assertEqual({{1,99,2}, {[<<"pre-alpha">>,1], []}}, parse(<<"1.99.2-pre-alpha.1">>)),
?assertEqual({{1,99,2}, {[], [<<"build">>, 1, <<"a36">>]}},
parse(<<"1.99.2+build.1.a36">>)),
?assertEqual({{1,99,2,44}, {[], [<<"build">>, 1, <<"a36">>]}},
parse(<<"1.99.2.44+build.1.a36">>)),
?assertEqual({{1,99,2}, {[<<"alpha">>, 1], [<<"build">>, 1, <<"a36">>]}},
parse("1.99.2-alpha.1+build.1.a36")),
?assertEqual({{1,99,2}, {[<<"pre-alpha">>, 1], [<<"build">>, 1, <<"a36">>]}},
parse("1.99.2-pre-alpha.1+build.1.a36")).
version_format_test() ->
?assertEqual(["1", [], []], format({1, {[],[]}})),
?assertEqual(["1", ".", "2", ".", "34", [], []], format({{1,2,34},{[],[]}})),
?assertEqual(<<"a">>, erlang:iolist_to_binary(format({<<"a">>, {[],[]}}))),
?assertEqual(<<"a.b">>, erlang:iolist_to_binary(format({{<<"a">>,<<"b">>}, {[],[]}}))),
?assertEqual(<<"1">>, erlang:iolist_to_binary(format({1, {[],[]}}))),
?assertEqual(<<"1.2">>, erlang:iolist_to_binary(format({{1,2}, {[],[]}}))),
?assertEqual(<<"1.2.2">>, erlang:iolist_to_binary(format({{1,2,2}, {[],[]}}))),
?assertEqual(<<"1.99.2">>, erlang:iolist_to_binary(format({{1,99,2}, {[],[]}}))),
?assertEqual(<<"1.99.2-alpha">>, erlang:iolist_to_binary(format({{1,99,2}, {[<<"alpha">>],[]}}))),
?assertEqual(<<"1.99.2-alpha.1">>, erlang:iolist_to_binary(format({{1,99,2}, {[<<"alpha">>,1], []}}))),
?assertEqual(<<"1.99.2-pre-alpha.1">>, erlang:iolist_to_binary(format({{1,99,2}, {[<<"pre-alpha">>,1], []}}))),
?assertEqual(<<"1.99.2+build.1.a36">>,
erlang:iolist_to_binary(format({{1,99,2}, {[], [<<"build">>, 1, <<"a36">>]}}))),
?assertEqual(<<"1.99.2.44+build.1.a36">>,
erlang:iolist_to_binary(format({{1,99,2,44}, {[], [<<"build">>, 1, <<"a36">>]}}))),
?assertEqual(<<"1.99.2-alpha.1+build.1.a36">>,
erlang:iolist_to_binary(format({{1,99,2}, {[<<"alpha">>, 1], [<<"build">>, 1, <<"a36">>]}}))),
?assertEqual(<<"1.99.2-pre-alpha.1+build.1.a36">>,
erlang:iolist_to_binary(format({{1,99,2}, {[<<"pre-alpha">>, 1], [<<"build">>, 1, <<"a36">>]}}))),
?assertEqual(<<"1">>, erlang:iolist_to_binary(format({1, {[],[]}}))).
-endif.

View file

@ -48,7 +48,6 @@ parse(Input) when is_binary(Input) ->
transform(_,Node,_Index) -> Node.
-file("peg_includes.hrl", 1).
-type index() :: {{line, pos_integer()}, {column, pos_integer()}}.
-type input() :: binary().
-type parse_failure() :: {fail, term()}.

View file

@ -39,6 +39,11 @@
say/1,
say/2]).
-ifdef(TEST).
-export([get_boolean/1,
get_integer/1]).
-endif.
-export_type([prompt/0,
type/0,
supported/0]).
@ -75,7 +80,7 @@ ask(Prompt) ->
ask_default(Prompt, Default) ->
ask_convert(Prompt, fun get_string/1, string, Default).
%% @doc Asks the user to respond to the prompt. Trys to return the
%% @doc Asks the user to respond to the prompt. Tries to return the
%% value in the format specified by 'Type'.
-spec ask(prompt(), type()) -> supported().
ask(Prompt, boolean) ->
@ -85,7 +90,7 @@ ask(Prompt, number) ->
ask(Prompt, string) ->
ask_convert(Prompt, fun get_string/1, string, none).
%% @doc Asks the user to respond to the prompt. Trys to return the
%% @doc Asks the user to respond to the prompt. Tries to return the
%% value in the format specified by 'Type'.
-spec ask_default(prompt(), type(), supported()) -> supported().
ask_default(Prompt, boolean, Default) ->
@ -127,7 +132,7 @@ ask_convert(Prompt, TransFun, Type, Default) ->
Default ->
[" (", io_lib:format("~p", [Default]) , ")"]
end, "> "])),
Data = trim(trim(io:get_line(NewPrompt)), both, [$\n]),
Data = string:trim(string:trim(io:get_line(NewPrompt)), both, [$\n]),
Ret = TransFun(Data),
case Ret of
no_data ->
@ -145,7 +150,7 @@ ask_convert(Prompt, TransFun, Type, Default) ->
Ret
end.
%% @doc Trys to translate the result into a boolean
%% @doc Tries to translate the result into a boolean
-spec get_boolean(string()) -> boolean().
get_boolean([]) ->
no_data;
@ -172,7 +177,7 @@ get_boolean([$N | _]) ->
get_boolean(_) ->
no_clue.
%% @doc Trys to translate the result into an integer
%% @doc Tries to translate the result into an integer
-spec get_integer(string()) -> integer().
get_integer([]) ->
no_data;
@ -196,36 +201,3 @@ get_string(String) ->
false ->
no_clue
end.
-ifdef(unicode_str).
trim(Str) -> string:trim(Str).
trim(Str, right, Chars) -> string:trim(Str, trailing, Chars);
trim(Str, left, Chars) -> string:trim(Str, leading, Chars);
trim(Str, both, Chars) -> string:trim(Str, both, Chars).
-else.
trim(Str) -> string:strip(Str).
trim(Str, Dir, [Chars|_]) -> string:strip(Str, Dir, Chars).
-endif.
%%%====================================================================
%%% tests
%%%====================================================================
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
general_test_() ->
[?_test(42 == get_integer("42")),
?_test(500211 == get_integer("500211")),
?_test(1234567890 == get_integer("1234567890")),
?_test(12345678901234567890 == get_integer("12345678901234567890")),
?_test(true == get_boolean("true")),
?_test(false == get_boolean("false")),
?_test(true == get_boolean("Ok")),
?_test(true == get_boolean("ok")),
?_test(true == get_boolean("Y")),
?_test(true == get_boolean("y")),
?_test(false == get_boolean("False")),
?_test(false == get_boolean("No")),
?_test(false == get_boolean("no"))].
-endif.

View file

@ -27,24 +27,9 @@
%% however you should not rely on the internal representation here
-type t() :: #t{}.
-ifdef(have_callback_support).
-callback new() -> any().
-callback vsn(any()) -> {ok, string()} | {error, Reason::any()}.
-else.
%% In the case where R14 or lower is being used to compile the system
%% we need to export a behaviour info
-export([behaviour_info/1]).
-spec behaviour_info(atom()) -> [{atom(), arity()}] | undefined.
behaviour_info(callbacks) ->
[{new, 0},
{vsn, 1}];
behaviour_info(_Other) ->
undefined.
-endif.
%%%===================================================================
%%% API
%%%===================================================================

View file

@ -6,6 +6,6 @@
{applications,[kernel,stdlib,cf]},
{maintainers,["Eric Merritt","Tristan Sloughter",
"Jordan Wilberding","Martin Logan"]},
{licenses,["Apache"]},
{licenses,["Apache", "MIT"]},
{links,[{"Github",
"https://github.com/erlware/erlware_commons"}]}]}.

39
test/ec_cmd_log_tests.erl Normal file
View file

@ -0,0 +1,39 @@
%%% @copyright 2024 Erlware, LLC.
-module(ec_cmd_log_tests).
-include("include/ec_cmd_log.hrl").
-include("src/ec_cmd_log.hrl").
-include_lib("eunit/include/eunit.hrl").
should_test() ->
ErrorLogState = ec_cmd_log:new(error),
?assertMatch(true, ec_cmd_log:should(ErrorLogState, ?EC_ERROR)),
?assertMatch(true, not ec_cmd_log:should(ErrorLogState, ?EC_INFO)),
?assertMatch(true, not ec_cmd_log:should(ErrorLogState, ?EC_DEBUG)),
?assertEqual(?EC_ERROR, ec_cmd_log:log_level(ErrorLogState)),
?assertEqual(error, ec_cmd_log:atom_log_level(ErrorLogState)),
InfoLogState = ec_cmd_log:new(info),
?assertMatch(true, ec_cmd_log:should(InfoLogState, ?EC_ERROR)),
?assertMatch(true, ec_cmd_log:should(InfoLogState, ?EC_INFO)),
?assertMatch(true, not ec_cmd_log:should(InfoLogState, ?EC_DEBUG)),
?assertEqual(?EC_INFO, ec_cmd_log:log_level(InfoLogState)),
?assertEqual(info, ec_cmd_log:atom_log_level(InfoLogState)),
DebugLogState = ec_cmd_log:new(debug),
?assertMatch(true, ec_cmd_log:should(DebugLogState, ?EC_ERROR)),
?assertMatch(true, ec_cmd_log:should(DebugLogState, ?EC_INFO)),
?assertMatch(true, ec_cmd_log:should(DebugLogState, ?EC_DEBUG)),
?assertEqual(?EC_DEBUG, ec_cmd_log:log_level(DebugLogState)),
?assertEqual(debug, ec_cmd_log:atom_log_level(DebugLogState)).
no_color_test() ->
LogState = ec_cmd_log:new(debug, command_line, none),
?assertEqual("test",
ec_cmd_log:colorize(LogState, ?RED, true, "test")).
color_test() ->
LogState = ec_cmd_log:new(debug, command_line, high),
?assertEqual("\e[1;31m===> test\e[0m",
ec_cmd_log:colorize(LogState, ?RED, true, "test")).

28
test/ec_cnv_tests.erl Normal file
View file

@ -0,0 +1,28 @@
%%% @copyright 2024 Erlware, LLC.
-module(ec_cnv_tests).
-include_lib("eunit/include/eunit.hrl").
to_integer_test() ->
?assertError(badarg, ec_cnv:to_integer(1.5, strict)).
to_float_test() ->
?assertError(badarg, ec_cnv:to_float(10, strict)).
to_atom_test() ->
?assertMatch(true, ec_cnv:to_atom("true")),
?assertMatch(true, ec_cnv:to_atom(<<"true">>)),
?assertMatch(false, ec_cnv:to_atom(<<"false">>)),
?assertMatch(false, ec_cnv:to_atom(false)),
?assertError(badarg, ec_cnv:to_atom("hello_foo_bar_baz")),
S = erlang:list_to_atom("1"),
?assertMatch(S, ec_cnv:to_atom(1)).
to_boolean_test()->
?assertMatch(true, ec_cnv:to_boolean(<<"true">>)),
?assertMatch(true, ec_cnv:to_boolean("true")),
?assertMatch(true, ec_cnv:to_boolean(true)),
?assertMatch(false, ec_cnv:to_boolean(<<"false">>)),
?assertMatch(false, ec_cnv:to_boolean("false")),
?assertMatch(false, ec_cnv:to_boolean(false)).

84
test/ec_file_tests.erl Normal file
View file

@ -0,0 +1,84 @@
%%% @copyright 2024 Erlware, LLC.
-module(ec_file_tests).
-include_lib("eunit/include/eunit.hrl").
setup_test() ->
Dir = ec_file:insecure_mkdtemp(),
ec_file:mkdir_path(Dir),
?assertMatch(false, ec_file:is_symlink(Dir)),
?assertMatch(true, filelib:is_dir(Dir)).
md5sum_test() ->
?assertMatch("cfcd208495d565ef66e7dff9f98764da", ec_file:md5sum("0")).
sha1sum_test() ->
?assertMatch("b6589fc6ab0dc82cf12099d1c2d40ab994e8410c", ec_file:sha1sum("0")).
file_test() ->
Dir = ec_file:insecure_mkdtemp(),
TermFile = filename:join(Dir, "ec_file/dir/file.term"),
TermFileCopy = filename:join(Dir, "ec_file/dircopy/file.term"),
filelib:ensure_dir(TermFile),
filelib:ensure_dir(TermFileCopy),
ec_file:write_term(TermFile, "term"),
?assertMatch({ok, <<"\"term\". ">>}, ec_file:read(TermFile)),
ec_file:copy(filename:dirname(TermFile),
filename:dirname(TermFileCopy),
[recursive]).
teardown_test() ->
Dir = ec_file:insecure_mkdtemp(),
ec_file:remove(Dir, [recursive]),
?assertMatch(false, filelib:is_dir(Dir)).
setup_base_and_target() ->
BaseDir = ec_file:insecure_mkdtemp(),
DummyContents = <<"This should be deleted">>,
SourceDir = filename:join([BaseDir, "source"]),
ok = file:make_dir(SourceDir),
Name1 = filename:join([SourceDir, "fileone"]),
Name2 = filename:join([SourceDir, "filetwo"]),
Name3 = filename:join([SourceDir, "filethree"]),
NoName = filename:join([SourceDir, "noname"]),
ok = file:write_file(Name1, DummyContents),
ok = file:write_file(Name2, DummyContents),
ok = file:write_file(Name3, DummyContents),
ok = file:write_file(NoName, DummyContents),
{BaseDir, SourceDir, {Name1, Name2, Name3, NoName}}.
exists_test() ->
BaseDir = ec_file:insecure_mkdtemp(),
SourceDir = filename:join([BaseDir, "source1"]),
NoName = filename:join([SourceDir, "noname"]),
ok = file:make_dir(SourceDir),
Name1 = filename:join([SourceDir, "fileone"]),
ok = file:write_file(Name1, <<"Testn">>),
?assertMatch(true, ec_file:exists(Name1)),
?assertMatch(false, ec_file:exists(NoName)).
real_path_test() ->
BaseDir = "foo",
Dir = filename:absname(filename:join(BaseDir, "source1")),
LinkDir = filename:join([BaseDir, "link"]),
ok = ec_file:mkdir_p(Dir),
file:make_symlink(Dir, LinkDir),
?assertEqual(Dir, ec_file:real_dir_path(LinkDir)),
?assertEqual(directory, ec_file:type(Dir)),
?assertEqual(symlink, ec_file:type(LinkDir)),
TermFile = filename:join(BaseDir, "test_file"),
ok = ec_file:write_term(TermFile, foo),
?assertEqual(file, ec_file:type(TermFile)),
?assertEqual(true, ec_file:is_symlink(LinkDir)),
?assertEqual(false, ec_file:is_symlink(Dir)).
find_test() ->
%% Create a directory in /tmp for the test. Clean everything afterwards
{BaseDir, _SourceDir, {Name1, Name2, Name3, _NoName}} = setup_base_and_target(),
Result = ec_file:find(BaseDir, "file[a-z]+\$"),
?assertMatch(3, erlang:length(Result)),
?assertEqual(true, lists:member(Name1, Result)),
?assertEqual(true, lists:member(Name2, Result)),
?assertEqual(true, lists:member(Name3, Result)),
ec_file:remove(BaseDir, [recursive]).

View file

@ -0,0 +1,67 @@
%%% @copyright 2024 Erlware, LLC.
-module(ec_gb_trees_tests).
-include_lib("eunit/include/eunit.hrl").
%% For me unit testing initially is about covering the obvious case. A
%% check to make sure that what you expect the tested functionality to
%% do, it actually does. As time goes on and people detect bugs you
%% add tests for those specific problems to the unit test suit.
%%
%% However, when getting started you can only test your basic
%% expectations. So here are the expectations I have for the add
%% functionality.
%%
%% 1) I can put arbitrary terms into the dictionary as keys
%% 2) I can put arbitrary terms into the dictionary as values
%% 3) When I put a value in the dictionary by a key, I can retrieve
%% that same value
%% 4) When I put a different value in the dictionary by key it does
%% not change other key value pairs.
%% 5) When I update a value the new value in available by the new key
%% 6) When a value does not exist a not found exception is created
add_test() ->
Dict0 = ec_dictionary:new(ec_gb_trees),
Key1 = foo,
Key2 = [1, 3],
Key3 = {"super"},
Key4 = <<"fabulous">>,
Key5 = {"Sona", 2, <<"Zuper">>},
Value1 = Key5,
Value2 = Key4,
Value3 = Key2,
Value4 = Key3,
Value5 = Key1,
Dict01 = ec_dictionary:add(Key1, Value1, Dict0),
Dict02 = ec_dictionary:add(Key3, Value3,
ec_dictionary:add(Key2, Value2,
Dict01)),
Dict1 =
ec_dictionary:add(Key5, Value5,
ec_dictionary:add(Key4, Value4,
Dict02)),
?assertMatch(Value1, ec_dictionary:get(Key1, Dict1)),
?assertMatch(Value2, ec_dictionary:get(Key2, Dict1)),
?assertMatch(Value3, ec_dictionary:get(Key3, Dict1)),
?assertMatch(Value4, ec_dictionary:get(Key4, Dict1)),
?assertMatch(Value5, ec_dictionary:get(Key5, Dict1)),
Dict2 = ec_dictionary:add(Key3, Value5,
ec_dictionary:add(Key2, Value4, Dict1)),
?assertMatch(Value1, ec_dictionary:get(Key1, Dict2)),
?assertMatch(Value4, ec_dictionary:get(Key2, Dict2)),
?assertMatch(Value5, ec_dictionary:get(Key3, Dict2)),
?assertMatch(Value4, ec_dictionary:get(Key4, Dict2)),
?assertMatch(Value5, ec_dictionary:get(Key5, Dict2)),
?assertThrow(not_found, ec_dictionary:get(should_blow_up, Dict2)),
?assertThrow(not_found, ec_dictionary:get("This should blow up too",
Dict2)).

13
test/ec_git_vsn_tests.erl Normal file
View file

@ -0,0 +1,13 @@
%%% @copyright 2024 Erlware, LLC.
-module(ec_git_vsn_tests).
-include_lib("eunit/include/eunit.hrl").
parse_tags_test() ->
?assertEqual({undefined, ""}, ec_git_vsn:parse_tags("a.b.c")).
get_patch_count_test() ->
?assertEqual(0, ec_git_vsn:get_patch_count("a.b.c")).
collect_default_refcount_test() ->
?assertMatch({"", _, _}, ec_git_vsn:collect_default_refcount("a.b.c")).

172
test/ec_lists_tests.erl Normal file
View file

@ -0,0 +1,172 @@
%%% @copyright 2024 Erlware, LLC.
-module(ec_lists_tests).
-include_lib("eunit/include/eunit.hrl").
find1_test() ->
TestData = [1, 2, 3, 4, 5, 6],
Result = ec_lists:find(fun(5) ->
true;
(_) ->
false
end,
TestData),
?assertMatch({ok, 5}, Result),
Result2 = ec_lists:find(fun(37) ->
true;
(_) ->
false
end,
TestData),
?assertMatch(error, Result2).
find2_test() ->
TestData = ["one", "two", "three", "four", "five", "six"],
Result = ec_lists:find(fun("five") ->
true;
(_) ->
false
end,
TestData),
?assertMatch({ok, "five"}, Result),
Result2 = ec_lists:find(fun(super_duper) ->
true;
(_) ->
false
end,
TestData),
?assertMatch(error, Result2).
find3_test() ->
TestData = [{"one", 1}, {"two", 2}, {"three", 3}, {"four", 5}, {"five", 5},
{"six", 6}],
Result = ec_lists:find(fun({"one", 1}) ->
true;
(_) ->
false
end,
TestData),
?assertMatch({ok, {"one", 1}}, Result),
Result2 = ec_lists:find(fun([fo, bar, baz]) ->
true;
({"onehundred", 100}) ->
true;
(_) ->
false
end,
TestData),
?assertMatch(error, Result2).
fetch1_test() ->
TestData = [1, 2, 3, 4, 5, 6],
Result = ec_lists:fetch(fun(5) ->
true;
(_) ->
false
end,
TestData),
?assertMatch(5, Result),
?assertThrow(not_found,
ec_lists:fetch(fun(37) ->
true;
(_) ->
false
end,
TestData)).
fetch2_test() ->
TestData = ["one", "two", "three", "four", "five", "six"],
Result = ec_lists:fetch(fun("five") ->
true;
(_) ->
false
end,
TestData),
?assertMatch("five", Result),
?assertThrow(not_found,
ec_lists:fetch(fun(super_duper) ->
true;
(_) ->
false
end,
TestData)).
fetch3_test() ->
TestData = [{"one", 1}, {"two", 2}, {"three", 3}, {"four", 5}, {"five", 5},
{"six", 6}],
Result = ec_lists:fetch(fun({"one", 1}) ->
true;
(_) ->
false
end,
TestData),
?assertMatch({"one", 1}, Result),
?assertThrow(not_found,
ec_lists:fetch(fun([fo, bar, baz]) ->
true;
({"onehundred", 100}) ->
true;
(_) ->
false
end,
TestData)).
search1_test() ->
TestData = [1, 2, 3, 4, 5, 6],
Result = ec_lists:search(fun(5) ->
{ok, 5};
(_) ->
not_found
end,
TestData),
?assertMatch({ok, 5, 5}, Result),
Result2 = ec_lists:search(fun(37) ->
{ok, 37};
(_) ->
not_found
end,
TestData),
?assertMatch(not_found, Result2).
search2_test() ->
TestData = [1, 2, 3, 4, 5, 6],
Result = ec_lists:search(fun(1) ->
{ok, 10};
(_) ->
not_found
end,
TestData),
?assertMatch({ok, 10, 1}, Result),
Result2 = ec_lists:search(fun(6) ->
{ok, 37};
(_) ->
not_found
end,
TestData),
?assertMatch({ok, 37, 6}, Result2).
search3_test() ->
TestData = [1, 2, 3, 4, 5, 6],
Result = ec_lists:search(fun(10) ->
{ok, 10};
(_) ->
not_found
end,
TestData),
?assertMatch(not_found, Result),
Result2 = ec_lists:search(fun(-1) ->
{ok, 37};
(_) ->
not_found
end,
TestData),
?assertMatch(not_found, Result2).

447
test/ec_semver_tests.erl Normal file
View file

@ -0,0 +1,447 @@
%%% @copyright 2024 Erlware, LLC.
-module(ec_semver_tests).
-include_lib("eunit/include/eunit.hrl").
eql_test() ->
?assertMatch(true, ec_semver:eql("1.0.0-alpha",
"1.0.0-alpha")),
?assertMatch(true, ec_semver:eql("v1.0.0-alpha",
"1.0.0-alpha")),
?assertMatch(true, ec_semver:eql("1",
"1.0.0")),
?assertMatch(true, ec_semver:eql("v1",
"v1.0.0")),
?assertMatch(true, ec_semver:eql("1.0",
"1.0.0")),
?assertMatch(true, ec_semver:eql("1.0.0",
"1")),
?assertMatch(true, ec_semver:eql("1.0.0.0",
"1")),
?assertMatch(true, ec_semver:eql("1.0+alpha.1",
"1.0.0+alpha.1")),
?assertMatch(true, ec_semver:eql("1.0-alpha.1+build.1",
"1.0.0-alpha.1+build.1")),
?assertMatch(true, ec_semver:eql("1.0-alpha.1+build.1",
"1.0.0.0-alpha.1+build.1")),
?assertMatch(true, ec_semver:eql("1.0-alpha.1+build.1",
"v1.0.0.0-alpha.1+build.1")),
?assertMatch(true, ec_semver:eql("1.0-pre-alpha.1",
"1.0.0-pre-alpha.1")),
?assertMatch(true, ec_semver:eql("aa", "aa")),
?assertMatch(true, ec_semver:eql("AA.BB", "AA.BB")),
?assertMatch(true, ec_semver:eql("BBB-super", "BBB-super")),
?assertMatch(true, not ec_semver:eql("1.0.0",
"1.0.1")),
?assertMatch(true, not ec_semver:eql("1.0.0-alpha",
"1.0.1+alpha")),
?assertMatch(true, not ec_semver:eql("1.0.0+build.1",
"1.0.1+build.2")),
?assertMatch(true, not ec_semver:eql("1.0.0.0+build.1",
"1.0.0.1+build.2")),
?assertMatch(true, not ec_semver:eql("FFF", "BBB")),
?assertMatch(true, not ec_semver:eql("1", "1BBBB")).
gt_test() ->
?assertMatch(true, ec_semver:gt("1.0.0-alpha.1",
"1.0.0-alpha")),
?assertMatch(true, ec_semver:gt("1.0.0.1-alpha.1",
"1.0.0.1-alpha")),
?assertMatch(true, ec_semver:gt("1.0.0.4-alpha.1",
"1.0.0.2-alpha")),
?assertMatch(true, ec_semver:gt("1.0.0.0-alpha.1",
"1.0.0-alpha")),
?assertMatch(true, ec_semver:gt("1.0.0-beta.2",
"1.0.0-alpha.1")),
?assertMatch(true, ec_semver:gt("1.0.0-beta.11",
"1.0.0-beta.2")),
?assertMatch(true, ec_semver:gt("1.0.0-pre-alpha.14",
"1.0.0-pre-alpha.3")),
?assertMatch(true, ec_semver:gt("1.0.0-beta.11",
"1.0.0.0-beta.2")),
?assertMatch(true, ec_semver:gt("1.0.0-rc.1", "1.0.0-beta.11")),
?assertMatch(true, ec_semver:gt("1.0.0-rc.1+build.1", "1.0.0-rc.1")),
?assertMatch(true, ec_semver:gt("1.0.0", "1.0.0-rc.1+build.1")),
?assertMatch(true, ec_semver:gt("1.0.0+0.3.7", "1.0.0")),
?assertMatch(true, ec_semver:gt("1.3.7+build", "1.0.0+0.3.7")),
?assertMatch(true, ec_semver:gt("1.3.7+build.2.b8f12d7",
"1.3.7+build")),
?assertMatch(true, ec_semver:gt("1.3.7+build.2.b8f12d7",
"1.3.7.0+build")),
?assertMatch(true, ec_semver:gt("1.3.7+build.11.e0f985a",
"1.3.7+build.2.b8f12d7")),
?assertMatch(true, ec_semver:gt("aa.cc",
"aa.bb")),
?assertMatch(true, not ec_semver:gt("1.0.0-alpha",
"1.0.0-alpha.1")),
?assertMatch(true, not ec_semver:gt("1.0.0-alpha",
"1.0.0.0-alpha.1")),
?assertMatch(true, not ec_semver:gt("1.0.0-alpha.1",
"1.0.0-beta.2")),
?assertMatch(true, not ec_semver:gt("1.0.0-beta.2",
"1.0.0-beta.11")),
?assertMatch(true, not ec_semver:gt("1.0.0-beta.11",
"1.0.0-rc.1")),
?assertMatch(true, not ec_semver:gt("1.0.0-pre-alpha.3",
"1.0.0-pre-alpha.14")),
?assertMatch(true, not ec_semver:gt("1.0.0-rc.1",
"1.0.0-rc.1+build.1")),
?assertMatch(true, not ec_semver:gt("1.0.0-rc.1+build.1",
"1.0.0")),
?assertMatch(true, not ec_semver:gt("1.0.0",
"1.0.0+0.3.7")),
?assertMatch(true, not ec_semver:gt("1.0.0+0.3.7",
"1.3.7+build")),
?assertMatch(true, not ec_semver:gt("1.3.7+build",
"1.3.7+build.2.b8f12d7")),
?assertMatch(true, not ec_semver:gt("1.3.7+build.2.b8f12d7",
"1.3.7+build.11.e0f985a")),
?assertMatch(true, not ec_semver:gt("1.0.0-alpha",
"1.0.0-alpha")),
?assertMatch(true, not ec_semver:gt("1",
"1.0.0")),
?assertMatch(true, not ec_semver:gt("aa.bb",
"aa.bb")),
?assertMatch(true, not ec_semver:gt("aa.cc",
"aa.dd")),
?assertMatch(true, not ec_semver:gt("1.0",
"1.0.0")),
?assertMatch(true, not ec_semver:gt("1.0.0",
"1")),
?assertMatch(true, not ec_semver:gt("1.0+alpha.1",
"1.0.0+alpha.1")),
?assertMatch(true, not ec_semver:gt("1.0-alpha.1+build.1",
"1.0.0-alpha.1+build.1")).
lt_test() ->
?assertMatch(true, ec_semver:lt("1.0.0-alpha",
"1.0.0-alpha.1")),
?assertMatch(true, ec_semver:lt("1.0.0-alpha",
"1.0.0.0-alpha.1")),
?assertMatch(true, ec_semver:lt("1.0.0-alpha.1",
"1.0.0-beta.2")),
?assertMatch(true, ec_semver:lt("1.0.0-beta.2",
"1.0.0-beta.11")),
?assertMatch(true, ec_semver:lt("1.0.0-pre-alpha.3",
"1.0.0-pre-alpha.14")),
?assertMatch(true, ec_semver:lt("1.0.0-beta.11",
"1.0.0-rc.1")),
?assertMatch(true, ec_semver:lt("1.0.0.1-beta.11",
"1.0.0.1-rc.1")),
?assertMatch(true, ec_semver:lt("1.0.0-rc.1",
"1.0.0-rc.1+build.1")),
?assertMatch(true, ec_semver:lt("1.0.0-rc.1+build.1",
"1.0.0")),
?assertMatch(true, ec_semver:lt("1.0.0",
"1.0.0+0.3.7")),
?assertMatch(true, ec_semver:lt("1.0.0+0.3.7",
"1.3.7+build")),
?assertMatch(true, ec_semver:lt("1.3.7+build",
"1.3.7+build.2.b8f12d7")),
?assertMatch(true, ec_semver:lt("1.3.7+build.2.b8f12d7",
"1.3.7+build.11.e0f985a")),
?assertMatch(true, not ec_semver:lt("1.0.0-alpha",
"1.0.0-alpha")),
?assertMatch(true, not ec_semver:lt("1",
"1.0.0")),
?assertMatch(true, ec_semver:lt("1",
"1.0.0.1")),
?assertMatch(true, ec_semver:lt("AA.DD",
"AA.EE")),
?assertMatch(true, not ec_semver:lt("1.0",
"1.0.0")),
?assertMatch(true, not ec_semver:lt("1.0.0.0",
"1")),
?assertMatch(true, not ec_semver:lt("1.0+alpha.1",
"1.0.0+alpha.1")),
?assertMatch(true, not ec_semver:lt("AA.DD", "AA.CC")),
?assertMatch(true, not ec_semver:lt("1.0-alpha.1+build.1",
"1.0.0-alpha.1+build.1")),
?assertMatch(true, not ec_semver:lt("1.0.0-alpha.1",
"1.0.0-alpha")),
?assertMatch(true, not ec_semver:lt("1.0.0-beta.2",
"1.0.0-alpha.1")),
?assertMatch(true, not ec_semver:lt("1.0.0-beta.11",
"1.0.0-beta.2")),
?assertMatch(true, not ec_semver:lt("1.0.0-pre-alpha.14",
"1.0.0-pre-alpha.3")),
?assertMatch(true, not ec_semver:lt("1.0.0-rc.1", "1.0.0-beta.11")),
?assertMatch(true, not ec_semver:lt("1.0.0-rc.1+build.1", "1.0.0-rc.1")),
?assertMatch(true, not ec_semver:lt("1.0.0", "1.0.0-rc.1+build.1")),
?assertMatch(true, not ec_semver:lt("1.0.0+0.3.7", "1.0.0")),
?assertMatch(true, not ec_semver:lt("1.3.7+build", "1.0.0+0.3.7")),
?assertMatch(true, not ec_semver:lt("1.3.7+build.2.b8f12d7",
"1.3.7+build")),
?assertMatch(true, not ec_semver:lt("1.3.7+build.11.e0f985a",
"1.3.7+build.2.b8f12d7")).
gte_test() ->
?assertMatch(true, ec_semver:gte("1.0.0-alpha",
"1.0.0-alpha")),
?assertMatch(true, ec_semver:gte("1",
"1.0.0")),
?assertMatch(true, ec_semver:gte("1.0",
"1.0.0")),
?assertMatch(true, ec_semver:gte("1.0.0",
"1")),
?assertMatch(true, ec_semver:gte("1.0.0.0",
"1")),
?assertMatch(true, ec_semver:gte("1.0+alpha.1",
"1.0.0+alpha.1")),
?assertMatch(true, ec_semver:gte("1.0-alpha.1+build.1",
"1.0.0-alpha.1+build.1")),
?assertMatch(true, ec_semver:gte("1.0.0-alpha.1+build.1",
"1.0.0.0-alpha.1+build.1")),
?assertMatch(true, ec_semver:gte("1.0.0-alpha.1",
"1.0.0-alpha")),
?assertMatch(true, ec_semver:gte("1.0.0-pre-alpha.2",
"1.0.0-pre-alpha")),
?assertMatch(true, ec_semver:gte("1.0.0-beta.2",
"1.0.0-alpha.1")),
?assertMatch(true, ec_semver:gte("1.0.0-beta.11",
"1.0.0-beta.2")),
?assertMatch(true, ec_semver:gte("aa.bb", "aa.bb")),
?assertMatch(true, ec_semver:gte("dd", "aa")),
?assertMatch(true, ec_semver:gte("1.0.0-rc.1", "1.0.0-beta.11")),
?assertMatch(true, ec_semver:gte("1.0.0-rc.1+build.1", "1.0.0-rc.1")),
?assertMatch(true, ec_semver:gte("1.0.0", "1.0.0-rc.1+build.1")),
?assertMatch(true, ec_semver:gte("1.0.0+0.3.7", "1.0.0")),
?assertMatch(true, ec_semver:gte("1.3.7+build", "1.0.0+0.3.7")),
?assertMatch(true, ec_semver:gte("1.3.7+build.2.b8f12d7",
"1.3.7+build")),
?assertMatch(true, ec_semver:gte("1.3.7+build.11.e0f985a",
"1.3.7+build.2.b8f12d7")),
?assertMatch(true, not ec_semver:gte("1.0.0-alpha",
"1.0.0-alpha.1")),
?assertMatch(true, not ec_semver:gte("1.0.0-pre-alpha",
"1.0.0-pre-alpha.1")),
?assertMatch(true, not ec_semver:gte("CC", "DD")),
?assertMatch(true, not ec_semver:gte("1.0.0-alpha.1",
"1.0.0-beta.2")),
?assertMatch(true, not ec_semver:gte("1.0.0-beta.2",
"1.0.0-beta.11")),
?assertMatch(true, not ec_semver:gte("1.0.0-beta.11",
"1.0.0-rc.1")),
?assertMatch(true, not ec_semver:gte("1.0.0-rc.1",
"1.0.0-rc.1+build.1")),
?assertMatch(true, not ec_semver:gte("1.0.0-rc.1+build.1",
"1.0.0")),
?assertMatch(true, not ec_semver:gte("1.0.0",
"1.0.0+0.3.7")),
?assertMatch(true, not ec_semver:gte("1.0.0+0.3.7",
"1.3.7+build")),
?assertMatch(true, not ec_semver:gte("1.0.0",
"1.0.0+build.1")),
?assertMatch(true, not ec_semver:gte("1.3.7+build",
"1.3.7+build.2.b8f12d7")),
?assertMatch(true, not ec_semver:gte("1.3.7+build.2.b8f12d7",
"1.3.7+build.11.e0f985a")).
lte_test() ->
?assertMatch(true, ec_semver:lte("1.0.0-alpha",
"1.0.0-alpha.1")),
?assertMatch(true, ec_semver:lte("1.0.0-alpha.1",
"1.0.0-beta.2")),
?assertMatch(true, ec_semver:lte("1.0.0-beta.2",
"1.0.0-beta.11")),
?assertMatch(true, ec_semver:lte("1.0.0-pre-alpha.2",
"1.0.0-pre-alpha.11")),
?assertMatch(true, ec_semver:lte("1.0.0-beta.11",
"1.0.0-rc.1")),
?assertMatch(true, ec_semver:lte("1.0.0-rc.1",
"1.0.0-rc.1+build.1")),
?assertMatch(true, ec_semver:lte("1.0.0-rc.1+build.1",
"1.0.0")),
?assertMatch(true, ec_semver:lte("1.0.0",
"1.0.0+0.3.7")),
?assertMatch(true, ec_semver:lte("1.0.0+0.3.7",
"1.3.7+build")),
?assertMatch(true, ec_semver:lte("1.3.7+build",
"1.3.7+build.2.b8f12d7")),
?assertMatch(true, ec_semver:lte("1.3.7+build.2.b8f12d7",
"1.3.7+build.11.e0f985a")),
?assertMatch(true, ec_semver:lte("1.0.0-alpha",
"1.0.0-alpha")),
?assertMatch(true, ec_semver:lte("1",
"1.0.0")),
?assertMatch(true, ec_semver:lte("1.0",
"1.0.0")),
?assertMatch(true, ec_semver:lte("1.0.0",
"1")),
?assertMatch(true, ec_semver:lte("1.0+alpha.1",
"1.0.0+alpha.1")),
?assertMatch(true, ec_semver:lte("1.0.0.0+alpha.1",
"1.0.0+alpha.1")),
?assertMatch(true, ec_semver:lte("1.0-alpha.1+build.1",
"1.0.0-alpha.1+build.1")),
?assertMatch(true, ec_semver:lte("aa","cc")),
?assertMatch(true, ec_semver:lte("cc","cc")),
?assertMatch(true, not ec_semver:lte("1.0.0-alpha.1",
"1.0.0-alpha")),
?assertMatch(true, not ec_semver:lte("1.0.0-pre-alpha.2",
"1.0.0-pre-alpha")),
?assertMatch(true, not ec_semver:lte("cc", "aa")),
?assertMatch(true, not ec_semver:lte("1.0.0-beta.2",
"1.0.0-alpha.1")),
?assertMatch(true, not ec_semver:lte("1.0.0-beta.11",
"1.0.0-beta.2")),
?assertMatch(true, not ec_semver:lte("1.0.0-rc.1", "1.0.0-beta.11")),
?assertMatch(true, not ec_semver:lte("1.0.0-rc.1+build.1", "1.0.0-rc.1")),
?assertMatch(true, not ec_semver:lte("1.0.0", "1.0.0-rc.1+build.1")),
?assertMatch(true, not ec_semver:lte("1.0.0+0.3.7", "1.0.0")),
?assertMatch(true, not ec_semver:lte("1.3.7+build", "1.0.0+0.3.7")),
?assertMatch(true, not ec_semver:lte("1.3.7+build.2.b8f12d7",
"1.3.7+build")),
?assertMatch(true, not ec_semver:lte("1.3.7+build.11.e0f985a",
"1.3.7+build.2.b8f12d7")).
between_test() ->
?assertMatch(true, ec_semver:between("1.0.0-alpha",
"1.0.0-alpha.3",
"1.0.0-alpha.2")),
?assertMatch(true, ec_semver:between("1.0.0-alpha.1",
"1.0.0-beta.2",
"1.0.0-alpha.25")),
?assertMatch(true, ec_semver:between("1.0.0-beta.2",
"1.0.0-beta.11",
"1.0.0-beta.7")),
?assertMatch(true, ec_semver:between("1.0.0-pre-alpha.2",
"1.0.0-pre-alpha.11",
"1.0.0-pre-alpha.7")),
?assertMatch(true, ec_semver:between("1.0.0-beta.11",
"1.0.0-rc.3",
"1.0.0-rc.1")),
?assertMatch(true, ec_semver:between("1.0.0-rc.1",
"1.0.0-rc.1+build.3",
"1.0.0-rc.1+build.1")),
?assertMatch(true, ec_semver:between("1.0.0.0-rc.1",
"1.0.0-rc.1+build.3",
"1.0.0-rc.1+build.1")),
?assertMatch(true, ec_semver:between("1.0.0-rc.1+build.1",
"1.0.0",
"1.0.0-rc.33")),
?assertMatch(true, ec_semver:between("1.0.0",
"1.0.0+0.3.7",
"1.0.0+0.2")),
?assertMatch(true, ec_semver:between("1.0.0+0.3.7",
"1.3.7+build",
"1.2")),
?assertMatch(true, ec_semver:between("1.3.7+build",
"1.3.7+build.2.b8f12d7",
"1.3.7+build.1")),
?assertMatch(true, ec_semver:between("1.3.7+build.2.b8f12d7",
"1.3.7+build.11.e0f985a",
"1.3.7+build.10.a36faa")),
?assertMatch(true, ec_semver:between("1.0.0-alpha",
"1.0.0-alpha",
"1.0.0-alpha")),
?assertMatch(true, ec_semver:between("1",
"1.0.0",
"1.0.0")),
?assertMatch(true, ec_semver:between("1.0",
"1.0.0",
"1.0.0")),
?assertMatch(true, ec_semver:between("1.0",
"1.0.0.0",
"1.0.0.0")),
?assertMatch(true, ec_semver:between("1.0.0",
"1",
"1")),
?assertMatch(true, ec_semver:between("1.0+alpha.1",
"1.0.0+alpha.1",
"1.0.0+alpha.1")),
?assertMatch(true, ec_semver:between("1.0-alpha.1+build.1",
"1.0.0-alpha.1+build.1",
"1.0.0-alpha.1+build.1")),
?assertMatch(true, ec_semver:between("aaa",
"ddd",
"cc")),
?assertMatch(true, not ec_semver:between("1.0.0-alpha.1",
"1.0.0-alpha.22",
"1.0.0")),
?assertMatch(true, not ec_semver:between("1.0.0-pre-alpha.1",
"1.0.0-pre-alpha.22",
"1.0.0")),
?assertMatch(true, not ec_semver:between("1.0.0",
"1.0.0-alpha.1",
"2.0")),
?assertMatch(true, not ec_semver:between("1.0.0-beta.1",
"1.0.0-beta.11",
"1.0.0-alpha")),
?assertMatch(true, not ec_semver:between("1.0.0-beta.11", "1.0.0-rc.1",
"1.0.0-rc.22")),
?assertMatch(true, not ec_semver:between("aaa", "ddd", "zzz")).
pes_test() ->
?assertMatch(true, ec_semver:pes("1.0.0-rc.0", "1.0.0-rc.0")),
?assertMatch(true, ec_semver:pes("1.0.0-rc.1", "1.0.0-rc.0")),
?assertMatch(true, ec_semver:pes("1.0.0", "1.0.0-rc.0")),
?assertMatch(false, ec_semver:pes("1.0.0-rc.0", "1.0.0-rc.1")),
?assertMatch(true, ec_semver:pes("2.6.0", "2.6")),
?assertMatch(true, ec_semver:pes("2.7", "2.6")),
?assertMatch(true, ec_semver:pes("2.8", "2.6")),
?assertMatch(true, ec_semver:pes("2.9", "2.6")),
?assertMatch(true, ec_semver:pes("A.B", "A.A")),
?assertMatch(true, not ec_semver:pes("3.0.0", "2.6")),
?assertMatch(true, not ec_semver:pes("2.5", "2.6")),
?assertMatch(true, ec_semver:pes("2.6.5", "2.6.5")),
?assertMatch(true, ec_semver:pes("2.6.6", "2.6.5")),
?assertMatch(true, ec_semver:pes("2.6.7", "2.6.5")),
?assertMatch(true, ec_semver:pes("2.6.8", "2.6.5")),
?assertMatch(true, ec_semver:pes("2.6.9", "2.6.5")),
?assertMatch(true, ec_semver:pes("2.6.0.9", "2.6.0.5")),
?assertMatch(true, not ec_semver:pes("2.7", "2.6.5")),
?assertMatch(true, not ec_semver:pes("2.1.7", "2.1.6.5")),
?assertMatch(true, not ec_semver:pes("A.A", "A.B")),
?assertMatch(true, not ec_semver:pes("2.5", "2.6.5")).
parse_test() ->
?assertEqual({1, {[],[]}}, ec_semver:parse(<<"1">>)),
?assertEqual({{1,2,34},{[],[]}}, ec_semver:parse(<<"1.2.34">>)),
?assertEqual({<<"a">>, {[],[]}}, ec_semver:parse(<<"a">>)),
?assertEqual({{<<"a">>,<<"b">>}, {[],[]}}, ec_semver:parse(<<"a.b">>)),
?assertEqual({1, {[],[]}}, ec_semver:parse(<<"1">>)),
?assertEqual({{1,2}, {[],[]}}, ec_semver:parse(<<"1.2">>)),
?assertEqual({{1,2,2}, {[],[]}}, ec_semver:parse(<<"1.2.2">>)),
?assertEqual({{1,99,2}, {[],[]}}, ec_semver:parse(<<"1.99.2">>)),
?assertEqual({{1,99,2}, {[<<"alpha">>],[]}}, ec_semver:parse(<<"1.99.2-alpha">>)),
?assertEqual({{1,99,2}, {[<<"alpha">>,1], []}}, ec_semver:parse(<<"1.99.2-alpha.1">>)),
?assertEqual({{1,99,2}, {[<<"pre-alpha">>,1], []}}, ec_semver:parse(<<"1.99.2-pre-alpha.1">>)),
?assertEqual({{1,99,2}, {[], [<<"build">>, 1, <<"a36">>]}},
ec_semver:parse(<<"1.99.2+build.1.a36">>)),
?assertEqual({{1,99,2,44}, {[], [<<"build">>, 1, <<"a36">>]}},
ec_semver:parse(<<"1.99.2.44+build.1.a36">>)),
?assertEqual({{1,99,2}, {[<<"alpha">>, 1], [<<"build">>, 1, <<"a36">>]}},
ec_semver:parse("1.99.2-alpha.1+build.1.a36")),
?assertEqual({{1,99,2}, {[<<"pre-alpha">>, 1], [<<"build">>, 1, <<"a36">>]}},
ec_semver:parse("1.99.2-pre-alpha.1+build.1.a36")).
version_format_test() ->
?assertEqual(["1", [], []], ec_semver:format({1, {[],[]}})),
?assertEqual(["1", ".", "2", ".", "34", [], []], ec_semver:format({{1,2,34},{[],[]}})),
?assertEqual(<<"a">>, erlang:iolist_to_binary(ec_semver:format({<<"a">>, {[],[]}}))),
?assertEqual(<<"a.b">>, erlang:iolist_to_binary(ec_semver:format({{<<"a">>,<<"b">>}, {[],[]}}))),
?assertEqual(<<"1">>, erlang:iolist_to_binary(ec_semver:format({1, {[],[]}}))),
?assertEqual(<<"1.2">>, erlang:iolist_to_binary(ec_semver:format({{1,2}, {[],[]}}))),
?assertEqual(<<"1.2.2">>, erlang:iolist_to_binary(ec_semver:format({{1,2,2}, {[],[]}}))),
?assertEqual(<<"1.99.2">>, erlang:iolist_to_binary(ec_semver:format({{1,99,2}, {[],[]}}))),
?assertEqual(<<"1.99.2-alpha">>, erlang:iolist_to_binary(ec_semver:format({{1,99,2}, {[<<"alpha">>],[]}}))),
?assertEqual(<<"1.99.2-alpha.1">>, erlang:iolist_to_binary(ec_semver:format({{1,99,2}, {[<<"alpha">>,1], []}}))),
?assertEqual(<<"1.99.2-pre-alpha.1">>, erlang:iolist_to_binary(ec_semver:format({{1,99,2}, {[<<"pre-alpha">>,1], []}}))),
?assertEqual(<<"1.99.2+build.1.a36">>,
erlang:iolist_to_binary(ec_semver:format({{1,99,2}, {[], [<<"build">>, 1, <<"a36">>]}}))),
?assertEqual(<<"1.99.2.44+build.1.a36">>,
erlang:iolist_to_binary(ec_semver:format({{1,99,2,44}, {[], [<<"build">>, 1, <<"a36">>]}}))),
?assertEqual(<<"1.99.2-alpha.1+build.1.a36">>,
erlang:iolist_to_binary(ec_semver:format({{1,99,2}, {[<<"alpha">>, 1], [<<"build">>, 1, <<"a36">>]}}))),
?assertEqual(<<"1.99.2-pre-alpha.1+build.1.a36">>,
erlang:iolist_to_binary(ec_semver:format({{1,99,2}, {[<<"pre-alpha">>, 1], [<<"build">>, 1, <<"a36">>]}}))),
?assertEqual(<<"1">>, erlang:iolist_to_binary(ec_semver:format({1, {[],[]}}))).

19
test/ec_talk_tests.erl Normal file
View file

@ -0,0 +1,19 @@
%%% @copyright 2024 Erlware, LLC.
-module(ec_talk_tests).
-include_lib("eunit/include/eunit.hrl").
general_test_() ->
[?_test(42 == ec_talk:get_integer("42")),
?_test(500_211 == ec_talk:get_integer("500211")),
?_test(1_234_567_890 == ec_talk:get_integer("1234567890")),
?_test(12_345_678_901_234_567_890 == ec_talk:get_integer("12345678901234567890")),
?_test(true == ec_talk:get_boolean("true")),
?_test(false == ec_talk:get_boolean("false")),
?_test(true == ec_talk:get_boolean("Ok")),
?_test(true == ec_talk:get_boolean("ok")),
?_test(true == ec_talk:get_boolean("Y")),
?_test(true == ec_talk:get_boolean("y")),
?_test(false == ec_talk:get_boolean("False")),
?_test(false == ec_talk:get_boolean("No")),
?_test(false == ec_talk:get_boolean("no"))].