Compare commits
96 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
4ffd5dd550 | ||
![]() |
a5712997ef | ||
![]() |
20119880fd | ||
![]() |
1a08e33b83 | ||
![]() |
47f7a5540c | ||
![]() |
2ccc40f89b | ||
![]() |
fa25b703e5 | ||
![]() |
8de367f996 | ||
![]() |
279d116dca | ||
![]() |
23b00904c8 | ||
![]() |
6cd37a4f9b | ||
![]() |
5305348899 | ||
![]() |
6d4e7d14ce | ||
![]() |
677984e961 | ||
![]() |
ca7581cbb0 | ||
![]() |
fc69576978 | ||
![]() |
d24ad72034 | ||
![]() |
a54f0623c5 | ||
![]() |
515df6b21e | ||
![]() |
1fd0a513ff | ||
![]() |
3d0006fe89 | ||
![]() |
15126e0048 | ||
![]() |
cdd9240142 | ||
![]() |
1a42c54981 | ||
![]() |
ccc1be32be | ||
![]() |
5f40d8f061 | ||
![]() |
75c6bae602 | ||
![]() |
ab7eb3874f | ||
![]() |
2636b5e21d | ||
![]() |
d6315a9541 | ||
![]() |
f9ffd1ce6b | ||
![]() |
182c30a950 | ||
![]() |
63b1798b1e | ||
![]() |
cb3983741e | ||
![]() |
f378d3ec46 | ||
![]() |
c0a02892cd | ||
![]() |
5de3c80cc2 | ||
![]() |
17e6f89078 | ||
![]() |
86a6c6ea65 | ||
![]() |
2286a6ed9b | ||
![]() |
7b7d5b559d | ||
![]() |
eca2d2129c | ||
![]() |
20d049ea4f | ||
![]() |
5118421f6f | ||
![]() |
685f08621b | ||
![]() |
952a1d2bc6 | ||
![]() |
fc69b3630c | ||
![]() |
6781f1ba6a | ||
![]() |
bbdbbf313f | ||
![]() |
19c717fb97 | ||
![]() |
6e9c1b0a22 | ||
![]() |
7e69d4949e | ||
![]() |
68e9bbcd0f | ||
![]() |
cd88825861 | ||
![]() |
5c5c264241 | ||
![]() |
6d4c471ff6 | ||
![]() |
62a985b937 | ||
![]() |
7c4911795e | ||
![]() |
ad4b944fc6 | ||
![]() |
4d5811d99b | ||
![]() |
04c0d4fc84 | ||
![]() |
378b88587c | ||
![]() |
d5183f5336 | ||
![]() |
eeb25f4b7f | ||
![]() |
8dd7378a75 | ||
![]() |
4406d56135 | ||
![]() |
e89e95de5f | ||
![]() |
791729c30a | ||
![]() |
d34da1d107 | ||
![]() |
ad559ae1f5 | ||
![]() |
916539338f | ||
![]() |
2a758c9ec7 | ||
![]() |
4c0180f157 | ||
![]() |
6f7a32487a | ||
![]() |
9cdca1c2e1 | ||
![]() |
26bcdf8030 | ||
![]() |
c47c938537 | ||
![]() |
f0347d88d7 | ||
![]() |
0318b467bc | ||
![]() |
9d67e26510 | ||
![]() |
f5e8aa6551 | ||
![]() |
4406953a87 | ||
![]() |
0a4fde35e7 | ||
![]() |
01e08a3605 | ||
![]() |
d9874feccf | ||
![]() |
32e62781bb | ||
![]() |
2256b68e4f | ||
![]() |
45d79af620 | ||
![]() |
c7f166a8a7 | ||
![]() |
2e1b59ece6 | ||
![]() |
8eef97234f | ||
![]() |
c2b7863a53 | ||
![]() |
f41b847b0c | ||
![]() |
0dc260c04c | ||
![]() |
aad7ae4241 | ||
![]() |
8aadd8b278 |
35 changed files with 1286 additions and 1387 deletions
31
.github/workflows/main.yml
vendored
Normal file
31
.github/workflows/main.yml
vendored
Normal 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
3
.gitignore
vendored
|
@ -7,9 +7,12 @@ doc/edoc-info
|
||||||
doc/erlang.png
|
doc/erlang.png
|
||||||
ebin/*
|
ebin/*
|
||||||
.*
|
.*
|
||||||
|
!.github
|
||||||
_build
|
_build
|
||||||
erl_crash.dump
|
erl_crash.dump
|
||||||
*.pyc
|
*.pyc
|
||||||
*~
|
*~
|
||||||
|
TEST-*.xml
|
||||||
|
/foo
|
||||||
|
|
||||||
src/ec_semver_parser.peg
|
src/ec_semver_parser.peg
|
||||||
|
|
22
.travis.yml
22
.travis.yml
|
@ -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
|
|
|
@ -73,7 +73,7 @@ $ git stash pop
|
||||||
```
|
```
|
||||||
|
|
||||||
You SHOULD use these commands both before working on your patch and before
|
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.
|
to deal with them.
|
||||||
|
|
||||||
You MUST create a new branch for your work. First make sure you have
|
You MUST create a new branch for your work. First make sure you have
|
||||||
|
|
30
README.md
30
README.md
|
@ -3,7 +3,9 @@ Erlware Commons
|
||||||
|
|
||||||
Current Status
|
Current Status
|
||||||
--------------
|
--------------
|
||||||
[](http://travis-ci.org/erlware/erlware_commons)
|
|
||||||
|
[](https://hex.pm/packages/erlware_commons)
|
||||||
|
[](https://github.com/erlware/erlware_commons/actions)
|
||||||
|
|
||||||
Introduction
|
Introduction
|
||||||
------------
|
------------
|
||||||
|
@ -24,6 +26,18 @@ Goals for the project
|
||||||
* Well Documented
|
* Well Documented
|
||||||
* Well Tested
|
* 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
|
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,
|
list operations parallel. It can operate on each element in parallel,
|
||||||
for IO-bound operations, on sublists in parallel, for taking advantage
|
for IO-bound operations, on sublists in parallel, for taking advantage
|
||||||
of multi-core machines with CPU-bound operations, and across erlang
|
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
|
failures. It can be configured, tuned, and tweaked to get optimal
|
||||||
performance while minimizing overhead.
|
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
|
lists, returning exactly the same result, and having both a form with
|
||||||
an identical syntax that operates on each element in parallel and a
|
an identical syntax that operates on each element in parallel and a
|
||||||
form which takes an optional "malt", a specification for how to
|
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 is the one exception, parallel fold is different from linear
|
||||||
fold. This module also include a simple mapreduce implementation, and
|
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/)
|
A complete parser for the [semver](http://semver.org/)
|
||||||
standard. Including a complete set of conforming comparison functions.
|
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
|
A set of additional list manipulation functions designed to supliment
|
||||||
the `lists` module in stdlib.
|
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
|
**signature** functionality. Java has Interfaces, SML has
|
||||||
Signatures. Erlang, though, doesn't currently support this model, at
|
Signatures. Erlang, though, doesn't currently support this model, at
|
||||||
least not directly. There are a few ways you can approximate it. We
|
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*
|
to serve as examples and provide a good set of *dictionary*
|
||||||
signatures. More information about signatures can be found at
|
signatures. More information about signatures can be found at
|
||||||
[signature](https://github.com/erlware/erlware_commons/blob/master/doc/signatures.md).
|
[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
|
erlang's dicts as a base. The function documentation for ec_dictionary
|
||||||
applies here as well.
|
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
|
This provides an implementation of the ec_dictionary signature using
|
||||||
erlang's gb_trees as a base. The function documentation for
|
erlang's gb_trees as a base. The function documentation for
|
||||||
ec_dictionary applies here as well.
|
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
|
This provides an implementation of the ec_dictionary signature using
|
||||||
erlang's orddict as a base. The function documentation for
|
erlang's orddict as a base. The function documentation for
|
||||||
ec_dictionary applies here as well.
|
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
|
This provides an implementation of the ec_dictionary signature using
|
||||||
Robert Virding's rbdict module as a base. The function documentation
|
Robert Virding's rbdict module as a base. The function documentation
|
||||||
|
|
|
@ -2,10 +2,10 @@ Signatures
|
||||||
==========
|
==========
|
||||||
|
|
||||||
It often occurs in coding that we need a library, a set of
|
It often occurs in coding that we need a library, a set of
|
||||||
functionaly. Often there are several algorithms that could provide
|
functionalities. Often there are several algorithms that could provide
|
||||||
this functionality. However, the code that uses it, either doesn't
|
each of these functionalities. However, the code that uses it, either doesn't
|
||||||
care about the individual algorithm or wishes to delegate choosing
|
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
|
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
|
a key (other things as well but primarily this). There are may ways to
|
||||||
implement a dictionary. Just a few are:
|
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)
|
* [Skip Lists](http://en.wikipedia.org/wiki/Skip_list)
|
||||||
* Many, many more ....
|
* Many, many more ....
|
||||||
|
|
||||||
Each of these approaches has there own performance characteristics,
|
Each of these approaches has their own performance characteristics,
|
||||||
memory footprints etc. For example, a table of size n with open
|
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
|
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
|
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)
|
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
|
comparisons for lookup. While for skip lists the performance
|
||||||
characteristics are about as good as that of randomly-built binary
|
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,
|
depends very much on memory available, insert/read characteristics,
|
||||||
etc. So delegating the choice to a single point in your code is a very
|
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
|
Other languages, have built in support for this
|
||||||
functionality. [Java](http://en.wikipedia.org/wiki/Java_(programming_language))
|
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
|
pass the Module name to the calling functions along with the data that
|
||||||
it is going to be called on.
|
it is going to be called on.
|
||||||
|
|
||||||
:::erlang
|
```erlang
|
||||||
add(ModuleToUse, Key, Value, DictData) ->
|
add(ModuleToUse, Key, Value, DictData) ->
|
||||||
ModuleToUse:add(Key, Value, DictData).
|
ModuleToUse:add(Key, Value, DictData).
|
||||||
|
```
|
||||||
|
|
||||||
This works, and you can vary how you want to pass the data. For
|
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,
|
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
|
you could pass in `{ModuleToUse, DictData}` and that would make it a
|
||||||
bit cleaner.
|
bit cleaner.
|
||||||
:::erlang
|
|
||||||
add(Key, Value, {ModuleToUse, DictData}) ->
|
```erlang
|
||||||
ModuleToUse:add(Key, Value, DictData).
|
add(Key, Value, {ModuleToUse, DictData}) ->
|
||||||
|
ModuleToUse:add(Key, Value, DictData).
|
||||||
|
```
|
||||||
|
|
||||||
Either way, there are a few problems with this approach. One of the
|
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
|
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
|
[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
|
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
|
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
|
`ModuleToUse` and you would never get any warnings, just an exception
|
||||||
at run time.
|
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
|
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
|
and keeping all the nice locality we get by using an explicit module
|
||||||
name.
|
name.
|
||||||
|
|
||||||
So what we actually want to do is something mole like this:
|
So what we actually want to do is something mole like this:
|
||||||
|
|
||||||
:::erlang
|
```erlang
|
||||||
add(Key, Value, DictData) ->
|
add(Key, Value, DictData) ->
|
||||||
dictionary:add(Key, Value, DictData).
|
dictionary:add(Key, Value, DictData).
|
||||||
|
```
|
||||||
|
|
||||||
Doing this we retain the locality. We can easily look up the
|
Doing this we retain the locality. We can easily look up the
|
||||||
`dictionary` Module. We immediately have a good idea what a
|
`dictionary` Module. We immediately have a good idea what a
|
||||||
|
@ -90,54 +94,56 @@ reasons, this is a much better approach to the problem. This is what
|
||||||
Signatures
|
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
|
The first thing we need to do is to define
|
||||||
a [Behaviour](http://metajack.im/2008/10/29/custom-behaviors-in-erlang/)
|
a [Behaviour](http://metajack.im/2008/10/29/custom-behaviors-in-erlang/)
|
||||||
for our functionality. To continue our example we will define a
|
for our functionality. To continue our example we will define a
|
||||||
Behaviour for dictionaries. That Behaviour looks like this:
|
Behaviour for dictionaries. That Behaviour looks like this:
|
||||||
|
|
||||||
:::erlang
|
```erlang
|
||||||
-module(ec_dictionary).
|
-module(ec_dictionary).
|
||||||
|
|
||||||
-export([behaviour_info/1]).
|
-export([behaviour_info/1]).
|
||||||
|
|
||||||
behaviour_info(callbacks) ->
|
behaviour_info(callbacks) ->
|
||||||
[{new, 0},
|
[{new, 0},
|
||||||
{has_key, 2},
|
{has_key, 2},
|
||||||
{get, 2},
|
{get, 2},
|
||||||
{add, 3},
|
{add, 3},
|
||||||
{remove, 2},
|
{remove, 2},
|
||||||
{has_value, 2},
|
{has_value, 2},
|
||||||
{size, 1},
|
{size, 1},
|
||||||
{to_list, 1},
|
{to_list, 1},
|
||||||
{from_list, 1},
|
{from_list, 1},
|
||||||
{keys, 1}];
|
{keys, 1}];
|
||||||
behaviour_info(_) ->
|
behaviour_info(_) ->
|
||||||
undefined.
|
undefined.
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
So we have our Behaviour now. Unfortunately, this doesn't give us much
|
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
|
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
|
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
|
a bit of functionality. We do that by actually implementing our own
|
||||||
behaviour, starting with `new/1`.
|
behaviour, starting with `new/1`.
|
||||||
|
|
||||||
:::erlang
|
```erlang
|
||||||
%% @doc create a new dictionary object from the specified module. The
|
%% @doc create a new dictionary object from the specified module. The
|
||||||
%% module should implement the dictionary behaviour.
|
%% module should implement the dictionary behaviour.
|
||||||
%%
|
%%
|
||||||
%% @param ModuleName The module name.
|
%% @param ModuleName The module name.
|
||||||
-spec new(module()) -> dictionary(_K, _V).
|
-spec new(module()) -> dictionary(_K, _V).
|
||||||
new(ModuleName) when is_atom(ModuleName) ->
|
new(ModuleName) when is_atom(ModuleName) ->
|
||||||
#dict_t{callback = ModuleName, data = ModuleName:new()}.
|
#dict_t{callback = ModuleName, data = ModuleName:new()}.
|
||||||
|
```
|
||||||
|
|
||||||
This code creates a new dictionary for us. Or to be more specific it
|
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
|
actually creates a new dictionary Signature record, that will be used
|
||||||
subsequently in other calls. This might look a bit familiar from our
|
subsequently in other calls. This might look a bit familiar from our
|
||||||
previous less optimal approach. We have both the module name and the
|
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
|
`ModuleName` to create the initial data. We then construct the record
|
||||||
and return that record to the caller and we have a new
|
and return that record to the caller and we have a new
|
||||||
dictionary. What about the other functions, the ones that don't create
|
dictionary. What about the other functions, the ones that don't create
|
||||||
|
@ -148,16 +154,17 @@ dictionary and another that just retrieves data.
|
||||||
The first we will look at is the one that updates the dictionary by
|
The first we will look at is the one that updates the dictionary by
|
||||||
adding a value.
|
adding a value.
|
||||||
|
|
||||||
:::erlang
|
```erlang
|
||||||
%% @doc add a new value to the existing dictionary. Return a new
|
%% @doc add a new value to the existing dictionary. Return a new
|
||||||
%% dictionary containing the value.
|
%% dictionary containing the value.
|
||||||
%%
|
%%
|
||||||
%% @param Dict the dictionary object to add too
|
%% @param Dict the dictionary object to add too
|
||||||
%% @param Key the key to add
|
%% @param Key the key to add
|
||||||
%% @param Value the value to add
|
%% @param Value the value to add
|
||||||
-spec add(key(K), value(V), dictionary(K, V)) -> dictionary(K, V).
|
-spec add(key(K), value(V), dictionary(K, V)) -> dictionary(K, V).
|
||||||
add(Key, Value, #dict_t{callback = Mod, data = Data} = Dict) ->
|
add(Key, Value, #dict_t{callback = Mod, data = Data} = Dict) ->
|
||||||
Dict#dict_t{data = Mod:add(Key, Value, Data)}.
|
Dict#dict_t{data = Mod:add(Key, Value, Data)}.
|
||||||
|
```
|
||||||
|
|
||||||
There are two key things here.
|
There are two key things here.
|
||||||
|
|
||||||
|
@ -173,16 +180,17 @@ implementation to do the work itself.
|
||||||
Now lets do a data retrieval function. In this case, the `get` function
|
Now lets do a data retrieval function. In this case, the `get` function
|
||||||
of the dictionary Signature.
|
of the dictionary Signature.
|
||||||
|
|
||||||
:::erlang
|
```erlang
|
||||||
%% @doc given a key return that key from the dictionary. If the key is
|
%% @doc given a key return that key from the dictionary. If the key is
|
||||||
%% not found throw a 'not_found' exception.
|
%% not found throw a 'not_found' exception.
|
||||||
%%
|
%%
|
||||||
%% @param Dict The dictionary object to return the value from
|
%% @param Dict The dictionary object to return the value from
|
||||||
%% @param Key The key requested
|
%% @param Key The key requested
|
||||||
%% @throws not_found when the key does not exist
|
%% @throws not_found when the key does not exist
|
||||||
-spec get(key(K), dictionary(K, V)) -> value(V).
|
-spec get(key(K), dictionary(K, V)) -> value(V).
|
||||||
get(Key, #dict_t{callback = Mod, data = Data}) ->
|
get(Key, #dict_t{callback = Mod, data = Data}) ->
|
||||||
Mod:get(Key, Data).
|
Mod:get(Key, Data).
|
||||||
|
```
|
||||||
|
|
||||||
In this case, you can see a very similar approach to deconstructing
|
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
|
the dict record. We still need to pull out the callback module and the
|
||||||
|
@ -197,7 +205,7 @@ implementation in
|
||||||
Using Signatures
|
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
|
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
|
have some questions about what kind of performance burden this places
|
||||||
on the code. At the very least we have an additional function call
|
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.
|
this is all costing us.
|
||||||
|
|
||||||
In general, there are two kinds of concrete implementations for
|
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.
|
wrapper.
|
||||||
|
|
||||||
### Native Signature Implementations
|
### Native Signature Implementations
|
||||||
|
@ -223,32 +231,33 @@ implements the ec_dictionary module directly.
|
||||||
|
|
||||||
A Signature Wrapper is a module that wraps another module. Its
|
A Signature Wrapper is a module that wraps another module. Its
|
||||||
purpose is to help a preexisting module implement the Behaviour
|
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
|
is the
|
||||||
[erlware_commons/ec_dict](https://github.com/ericbmerritt/erlware_commons/blob/types/src/ec_dict.erl)
|
[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
|
functionality is provided by the
|
||||||
[stdlib/dict](http://www.erlang.org/doc/man/dict.html) module
|
[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
|
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
|
`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
|
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.
|
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)) ->
|
-spec get(ec_dictionary:key(K), Object::dictionary(K, V)) ->
|
||||||
ec_dictionary:value(V).
|
ec_dictionary:value(V).
|
||||||
get(Key, Data) ->
|
get(Key, Data) ->
|
||||||
case dict:find(Key, Data) of
|
case dict:find(Key, Data) of
|
||||||
{ok, Value} ->
|
{ok, Value} ->
|
||||||
Value;
|
Value;
|
||||||
error ->
|
error ->
|
||||||
throw(not_found)
|
throw(not_found)
|
||||||
end.
|
end.
|
||||||
|
```
|
||||||
|
|
||||||
So the ec_dict module's purpose for existence is to help the
|
So the `ec_dict` module's purpose for existence is to help the
|
||||||
preexisting dict module implement the Behaviour defined by the
|
preexisting `dict` module implement the Behaviour defined by the
|
||||||
Signature.
|
Signature.
|
||||||
|
|
||||||
|
|
||||||
|
@ -258,24 +267,25 @@ the mix and that adds a bit of additional overhead.
|
||||||
|
|
||||||
### Creating the Timing Module
|
### 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.
|
Implementations and Signature Wrappers.
|
||||||
|
|
||||||
Lets get started by looking at some helper functions. We want
|
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 are will
|
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
|
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
|
want to test. The first we want to time is the Signature Wrapper, so
|
||||||
`dict` vs `ec_dict` called as a Signature.
|
`dict` vs `ec_dict` called as a Signature.
|
||||||
|
|
||||||
:::erlang
|
```erlang
|
||||||
create_dict() ->
|
create_dict() ->
|
||||||
lists:foldl(fun(El, Dict) ->
|
lists:foldl(fun(El, Dict) ->
|
||||||
dict:store(El, El, Dict)
|
dict:store(El, El, Dict)
|
||||||
end, dict:new(),
|
end, dict:new(),
|
||||||
lists:seq(1,100)).
|
lists:seq(1,100)).
|
||||||
|
```
|
||||||
|
|
||||||
The only thing we do here is create a sequence of numbers 1 to 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
|
worried about replicating real data in the dictionary. We care about
|
||||||
timing the function call overhead of Signatures, not the performance
|
timing the function call overhead of Signatures, not the performance
|
||||||
of the dictionaries themselves.
|
of the dictionaries themselves.
|
||||||
|
@ -283,58 +293,61 @@ of the dictionaries themselves.
|
||||||
We need to create a similar function for our Signature based
|
We need to create a similar function for our Signature based
|
||||||
dictionary `ec_dict`.
|
dictionary `ec_dict`.
|
||||||
|
|
||||||
:::erlang
|
```erlang
|
||||||
create_dictionary(Type) ->
|
create_dictionary(Type) ->
|
||||||
lists:foldl(fun(El, Dict) ->
|
lists:foldl(fun(El, Dict) ->
|
||||||
ec_dictionary:add(El, El, Dict)
|
ec_dictionary:add(El, El, Dict)
|
||||||
end,
|
end,
|
||||||
ec_dictionary:new(Type),
|
ec_dictionary:new(Type),
|
||||||
lists:seq(1,100)).
|
lists:seq(1,100)).
|
||||||
|
```
|
||||||
|
|
||||||
Here we actually create everything using the Signature. So we don't
|
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
|
need one function for each type. We can have one function that can
|
||||||
create anything that implements the Signature. That is the magic of
|
create anything that implements the Signature. That is the magic of
|
||||||
Signatures. Otherwise, this does the exact same thing as the dict
|
Signatures. Otherwise, this does the exact same thing as the dictionary
|
||||||
`create_dict/1`.
|
given by `create_dict/0`.
|
||||||
|
|
||||||
We are going to use two function calls in our timing. One that updates
|
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
|
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.
|
the `add` function.
|
||||||
|
|
||||||
:::erlang
|
```erlang
|
||||||
time_direct_vs_signature_dict() ->
|
time_direct_vs_signature_dict() ->
|
||||||
io:format("Timing dict~n"),
|
io:format("Timing dict~n"),
|
||||||
Dict = create_dict(),
|
Dict = create_dict(),
|
||||||
test_avg(fun() ->
|
test_avg(fun() ->
|
||||||
dict:size(dict:store(some_key, some_value, Dict))
|
dict:size(dict:store(some_key, some_value, Dict))
|
||||||
end,
|
end,
|
||||||
1000000),
|
1000000),
|
||||||
io:format("Timing ec_dict implementation of ec_dictionary~n"),
|
io:format("Timing ec_dict implementation of ec_dictionary~n"),
|
||||||
time_dict_type(ec_dict).
|
time_dict_type(ec_dict).
|
||||||
|
```
|
||||||
|
|
||||||
The `test_avg` function runs the provided function the number of times
|
The `test_avg` function runs the provided function the number of times
|
||||||
specified in the second argument and collects timing information. We
|
specified in the second argument and collects timing information. We
|
||||||
are going to run these one million times to get a good average (its
|
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 that in the anonymous
|
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
|
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
|
the test. However, because we are in the wonderful world of Signatures
|
||||||
we don't have to hard code the calls for the Signature
|
we don't have to hard code the calls for the Signature
|
||||||
implementations. Lets take a look at the `time_dict_type` function.
|
implementations. Lets take a look at the `time_dict_type` function.
|
||||||
|
|
||||||
|
|
||||||
:::erlang
|
```erlang
|
||||||
time_dict_type(Type) ->
|
time_dict_type(Type) ->
|
||||||
io:format("Testing ~p~n", [Type]),
|
io:format("Testing ~p~n", [Type]),
|
||||||
Dict = create_dictionary(Type),
|
Dict = create_dictionary(Type),
|
||||||
test_avg(fun() ->
|
test_avg(fun() ->
|
||||||
ec_dictionary:size(ec_dictionary:add(some_key, some_value, Dict))
|
ec_dictionary:size(ec_dictionary:add(some_key, some_value, Dict))
|
||||||
end,
|
end,
|
||||||
1000000).
|
1000000).
|
||||||
|
```
|
||||||
|
|
||||||
As you can see we take the type as an argument (we need it for `dict`
|
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
|
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
|
is never specified, we only ever call ec_dictionary, so this test will
|
||||||
work for anything that implements that Signature.
|
work for anything that implements that Signature.
|
||||||
|
|
||||||
|
@ -343,25 +356,26 @@ work for anything that implements that Signature.
|
||||||
So we have our tests, what was the result. Well on my laptop this is
|
So we have our tests, what was the result. Well on my laptop this is
|
||||||
what it looked like.
|
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]
|
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)
|
Eshell V5.8.2 (abort with ^G)
|
||||||
|
|
||||||
1> ec_timing:time_direct_vs_signature_dict().
|
1> ec_timing:time_direct_vs_signature_dict().
|
||||||
Timing dict
|
Timing dict
|
||||||
Range: 2 - 5621 mics
|
Range: 2 - 5621 mics
|
||||||
Median: 3 mics
|
Median: 3 mics
|
||||||
Average: 3 mics
|
Average: 3 mics
|
||||||
Timing ec_dict implementation of ec_dictionary
|
Timing ec_dict implementation of ec_dictionary
|
||||||
Testing ec_dict
|
Testing ec_dict
|
||||||
Range: 3 - 6097 mics
|
Range: 3 - 6097 mics
|
||||||
Median: 3 mics
|
Median: 3 mics
|
||||||
Average: 4 mics
|
Average: 4 mics
|
||||||
2>
|
2>
|
||||||
|
```
|
||||||
|
|
||||||
So for the direct dict call, we average about 3 mics per call, while
|
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
|
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
|
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
|
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
|
lesser. In any case, we can see that there is some cost associated
|
||||||
|
@ -373,30 +387,32 @@ Signature, but it is not a Signature Wrapper. It is a native
|
||||||
implementation of the Signature. To use `ec_rbdict` directly we have
|
implementation of the Signature. To use `ec_rbdict` directly we have
|
||||||
to create a creation helper just like we did for dict.
|
to create a creation helper just like we did for dict.
|
||||||
|
|
||||||
:::erlang
|
```erlang
|
||||||
create_rbdict() ->
|
create_rbdict() ->
|
||||||
lists:foldl(fun(El, Dict) ->
|
lists:foldl(fun(El, Dict) ->
|
||||||
ec_rbdict:add(El, El, Dict)
|
ec_rbdict:add(El, El, Dict)
|
||||||
end, ec_rbdict:new(),
|
end, ec_rbdict:new(),
|
||||||
lists:seq(1,100)).
|
lists:seq(1,100)).
|
||||||
|
```
|
||||||
|
|
||||||
This is exactly the same as `create_dict` with the exception that dict
|
This is exactly the same as `create_dict` with the exception that dict
|
||||||
is replaced by `ec_rbdict`.
|
is replaced by `ec_rbdict`.
|
||||||
|
|
||||||
The timing function itself looks very similar as well. Again notice
|
The timing function itself looks very similar as well. Again notice
|
||||||
that we have to hard code the concrete name for the concrete
|
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() ->
|
time_direct_vs_signature_rbdict() ->
|
||||||
io:format("Timing rbdict~n"),
|
io:format("Timing rbdict~n"),
|
||||||
Dict = create_rbdict(),
|
Dict = create_rbdict(),
|
||||||
test_avg(fun() ->
|
test_avg(fun() ->
|
||||||
ec_rbdict:size(ec_rbdict:add(some_key, some_value, Dict))
|
ec_rbdict:size(ec_rbdict:add(some_key, some_value, Dict))
|
||||||
end,
|
end,
|
||||||
1000000),
|
1000000),
|
||||||
io:format("Timing ec_dict implementation of ec_dictionary~n"),
|
io:format("Timing ec_dict implementation of ec_dictionary~n"),
|
||||||
time_dict_type(ec_rbdict).
|
time_dict_type(ec_rbdict).
|
||||||
|
```
|
||||||
|
|
||||||
And there we have our test. What do the results look like?
|
And there we have our test. What do the results look like?
|
||||||
|
|
||||||
|
@ -406,34 +422,35 @@ 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
|
dictionary Signature itself. Keep that in mind as we look at the
|
||||||
results.
|
results.
|
||||||
|
|
||||||
:::sh
|
```sh
|
||||||
Erlang R14B01 (erts-5.8.2) [source] [64-bit] [smp:4:4] [rq:4] [async-threads:0] [hipe] [kernel-poll:false]
|
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)
|
Eshell V5.8.2 (abort with ^G)
|
||||||
|
|
||||||
1> ec_timing:time_direct_vs_signature_rbdict().
|
1> ec_timing:time_direct_vs_signature_rbdict().
|
||||||
Timing rbdict
|
Timing rbdict
|
||||||
Range: 6 - 15070 mics
|
Range: 6 - 15070 mics
|
||||||
Median: 7 mics
|
Median: 7 mics
|
||||||
Average: 7 mics
|
Average: 7 mics
|
||||||
Timing ec_dict implementation of ec_dictionary
|
Timing ec_dict implementation of ec_dictionary
|
||||||
Testing ec_rbdict
|
Testing ec_rbdict
|
||||||
Range: 6 - 6013 mics
|
Range: 6 - 6013 mics
|
||||||
Median: 7 mics
|
Median: 7 mics
|
||||||
Average: 7 mics
|
Average: 7 mics
|
||||||
2>
|
2>
|
||||||
|
```
|
||||||
|
|
||||||
So no difference it time. Well the reality is that there is a
|
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
|
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
|
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.
|
enough not to worry about at the very least.
|
||||||
|
|
||||||
Conclusion
|
Conclusion
|
||||||
----------
|
----------
|
||||||
|
|
||||||
Signatures are a viable, useful approach to the problem of interfaces
|
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
|
implementation, and greatly increase the flexibility of the a library
|
||||||
while retaining testability and locality.
|
while retaining testability and locality.
|
||||||
|
|
||||||
|
@ -456,7 +473,7 @@ Signature Wrapper
|
||||||
|
|
||||||
### Code Referenced
|
### Code Referenced
|
||||||
|
|
||||||
* [ec_dictionary Implementation] (https://github.com/ericbmerritt/erlware_commons/blob/types/src/ec_dictionary.erl)
|
* [ec_dictionary Implementation](https://github.com/ericbmerritt/erlware_commons/blob/types/src/ec_dictionary.erl)
|
||||||
* [ec_dict Signature Wrapper] (https://github.com/ericbmerritt/erlware_commons/blob/types/src/ec_dict.erl)
|
* [ec_dict Signature Wrapper](https://github.com/ericbmerritt/erlware_commons/blob/types/src/ec_dict.erl)
|
||||||
* [ec_rbdict Native Signature Implementation] (https://github.com/ericbmerritt/erlware_commons/blob/types/src/ec_rbdict.erl)
|
* [ec_rbdict Native Signature Implementation](https://github.com/ericbmerritt/erlware_commons/blob/types/src/ec_rbdict.erl)
|
||||||
* [ec_timing Signature Use Example and Timing Collector] (https://github.com/ericbmerritt/erlware_commons/blob/types/examples/ec_timing.erl)
|
* [ec_timing Signature Use Example and Timing Collector](https://github.com/ericbmerritt/erlware_commons/blob/types/examples/ec_timing.erl)
|
||||||
|
|
11
rebar.config
11
rebar.config
|
@ -8,16 +8,7 @@
|
||||||
{erl_first_files, ["ec_dictionary", "ec_vsn"]}.
|
{erl_first_files, ["ec_dictionary", "ec_vsn"]}.
|
||||||
|
|
||||||
%% Compiler Options ============================================================
|
%% Compiler Options ============================================================
|
||||||
{erl_opts,
|
{erl_opts, [debug_info, warnings_as_errors]}.
|
||||||
[{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]}.
|
|
||||||
|
|
||||||
%% EUnit =======================================================================
|
%% EUnit =======================================================================
|
||||||
{eunit_opts, [verbose,
|
{eunit_opts, [verbose,
|
||||||
|
|
|
@ -1,17 +1,7 @@
|
||||||
IsRebar3 = case application:get_key(rebar, vsn) of
|
NoDialWarns = {dialyzer, [{warnings, [no_unknown]}]},
|
||||||
{ok, Vsn} ->
|
OTPRelease = erlang:list_to_integer(erlang:system_info(otp_release)),
|
||||||
[MajorVersion|_] = string:tokens(Vsn, "."),
|
|
||||||
(list_to_integer(MajorVersion) >= 3);
|
|
||||||
undefined ->
|
|
||||||
false
|
|
||||||
end,
|
|
||||||
|
|
||||||
Rebar2Deps = [
|
case OTPRelease<26 of
|
||||||
{cf, ".*", {git, "https://github.com/project-fifo/cf", {tag, "0.2.2"}}}
|
true -> CONFIG;
|
||||||
],
|
false -> lists:keystore(dialyzer, 1, CONFIG, NoDialWarns)
|
||||||
|
|
||||||
case IsRebar3 of
|
|
||||||
true -> CONFIG;
|
|
||||||
false ->
|
|
||||||
lists:keyreplace(deps, 1, CONFIG, {deps, Rebar2Deps})
|
|
||||||
end.
|
end.
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
{"1.1.0",
|
{"1.2.0",
|
||||||
[{<<"cf">>,{pkg,<<"cf">>,<<"0.3.1">>},0}]}.
|
[{<<"cf">>,{pkg,<<"cf">>,<<"0.3.1">>},0}]}.
|
||||||
[
|
[
|
||||||
{pkg_hash,[
|
{pkg_hash,[
|
||||||
{<<"cf">>, <<"5CB902239476E141EA70A740340233782D363A31EEA8AD37049561542E6CD641">>}]}
|
{<<"cf">>, <<"5CB902239476E141EA70A740340233782D363A31EEA8AD37049561542E6CD641">>}]},
|
||||||
|
{pkg_hash_ext,[
|
||||||
|
{<<"cf">>, <<"315E8D447D3A4B02BCDBFA397AD03BBB988A6E0AA6F44D3ADD0F4E3C3BF97672">>}]}
|
||||||
].
|
].
|
||||||
|
|
|
@ -19,9 +19,12 @@
|
||||||
%%% @copyright (C) 2012 Erlware, LLC.
|
%%% @copyright (C) 2012 Erlware, LLC.
|
||||||
%%%
|
%%%
|
||||||
%%% @doc This provides simple output functions for command line apps. You should
|
%%% @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).
|
-module(ec_cmd_log).
|
||||||
|
|
||||||
|
%% Avoid clashing with `error/3` BIF added in Erlang/OTP 24
|
||||||
|
-compile({no_auto_import,[error/3]}).
|
||||||
|
|
||||||
-export([new/1,
|
-export([new/1,
|
||||||
new/2,
|
new/2,
|
||||||
new/3,
|
new/3,
|
||||||
|
@ -37,22 +40,17 @@
|
||||||
warn/3,
|
warn/3,
|
||||||
log_level/1,
|
log_level/1,
|
||||||
atom_log_level/1,
|
atom_log_level/1,
|
||||||
|
colorize/4,
|
||||||
format/1]).
|
format/1]).
|
||||||
|
|
||||||
-include("ec_cmd_log.hrl").
|
-include("include/ec_cmd_log.hrl").
|
||||||
|
-include("src/ec_cmd_log.hrl").
|
||||||
-define(RED, $r).
|
|
||||||
-define(GREEN, $g).
|
|
||||||
-define(YELLOW, $y).
|
|
||||||
-define(BLUE, $b).
|
|
||||||
-define(MAGENTA, $m).
|
|
||||||
-define(CYAN, $c).
|
|
||||||
|
|
||||||
-define(PREFIX, "===> ").
|
-define(PREFIX, "===> ").
|
||||||
|
|
||||||
-record(state_t, {log_level=0 :: int_log_level(),
|
-record(state_t, {log_level=0 :: int_log_level(),
|
||||||
caller=api :: caller(),
|
caller=api :: caller(),
|
||||||
intensity=low :: none | low | high}).
|
intensity=low :: intensity()}).
|
||||||
|
|
||||||
%%============================================================================
|
%%============================================================================
|
||||||
%% types
|
%% types
|
||||||
|
@ -123,10 +121,10 @@ debug(LogState, Fun)
|
||||||
colorize(LogState, ?CYAN, false, Fun())
|
colorize(LogState, ?CYAN, false, Fun())
|
||||||
end);
|
end);
|
||||||
debug(LogState, String) ->
|
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
|
%% @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.
|
-spec debug(t(), string(), [any()]) -> ok.
|
||||||
debug(LogState, FormatString, Args) ->
|
debug(LogState, FormatString, Args) ->
|
||||||
log(LogState, ?EC_DEBUG, colorize(LogState, ?CYAN, false, FormatString), Args).
|
log(LogState, ?EC_DEBUG, colorize(LogState, ?CYAN, false, FormatString), Args).
|
||||||
|
@ -140,10 +138,10 @@ info(LogState, Fun)
|
||||||
colorize(LogState, ?GREEN, false, Fun())
|
colorize(LogState, ?GREEN, false, Fun())
|
||||||
end);
|
end);
|
||||||
info(LogState, String) ->
|
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
|
%% @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.
|
-spec info(t(), string(), [any()]) -> ok.
|
||||||
info(LogState, FormatString, Args) ->
|
info(LogState, FormatString, Args) ->
|
||||||
log(LogState, ?EC_INFO, colorize(LogState, ?GREEN, false, FormatString), Args).
|
log(LogState, ?EC_INFO, colorize(LogState, ?GREEN, false, FormatString), Args).
|
||||||
|
@ -157,10 +155,10 @@ error(LogState, Fun)
|
||||||
colorize(LogState, ?RED, false, Fun())
|
colorize(LogState, ?RED, false, Fun())
|
||||||
end);
|
end);
|
||||||
error(LogState, String) ->
|
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
|
%% @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.
|
-spec error(t(), string(), [any()]) -> ok.
|
||||||
error(LogState, FormatString, Args) ->
|
error(LogState, FormatString, Args) ->
|
||||||
log(LogState, ?EC_ERROR, colorize(LogState, ?RED, false, FormatString), Args).
|
log(LogState, ?EC_ERROR, colorize(LogState, ?RED, false, FormatString), Args).
|
||||||
|
@ -172,10 +170,10 @@ warn(LogState, Fun)
|
||||||
when erlang:is_function(Fun) ->
|
when erlang:is_function(Fun) ->
|
||||||
log(LogState, ?EC_WARN, fun() -> colorize(LogState, ?MAGENTA, false, Fun()) end);
|
log(LogState, ?EC_WARN, fun() -> colorize(LogState, ?MAGENTA, false, Fun()) end);
|
||||||
warn(LogState, String) ->
|
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
|
%% @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.
|
-spec warn(t(), string(), [any()]) -> ok.
|
||||||
warn(LogState, FormatString, Args) ->
|
warn(LogState, FormatString, Args) ->
|
||||||
log(LogState, ?EC_WARN, colorize(LogState, ?MAGENTA, false, 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.
|
-spec log(t(), int_log_level(), log_fun()) -> ok.
|
||||||
log(#state_t{log_level=DetailLogLevel}, LogLevel, Fun)
|
log(#state_t{log_level=DetailLogLevel}, LogLevel, Fun)
|
||||||
when DetailLogLevel >= LogLevel ->
|
when DetailLogLevel >= LogLevel ->
|
||||||
io:format("~s~n", [Fun()]);
|
io:format("~ts~n", [Fun()]);
|
||||||
log(_, _, _) ->
|
log(_, _, _) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
|
@ -240,61 +238,20 @@ format(Log) ->
|
||||||
|
|
||||||
colorize(#state_t{intensity=none}, _, _, Msg) ->
|
colorize(#state_t{intensity=none}, _, _, Msg) ->
|
||||||
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
|
%% (bold color) we don't need to modify the color
|
||||||
colorize(State, Color, true, Msg) when ?VALID_COLOR(Color),
|
colorize(State, Color, true, Msg) when ?VALID_COLOR(Color),
|
||||||
Color >= $A, Color =< $Z ->
|
Color >= $A, Color =< $Z ->
|
||||||
colorize(State, Color, false, Msg);
|
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.
|
%% bold but have a non bold color.
|
||||||
colorize(State, Color, true, Msg) when ?VALID_COLOR(Color) ->
|
colorize(State, Color, true, Msg) when ?VALID_COLOR(Color) ->
|
||||||
colorize(State, Color - 32, false, Msg);
|
colorize(State, Color - 32, false, Msg);
|
||||||
colorize(#state_t{caller=command_line, intensity = high},
|
colorize(#state_t{caller=command_line, intensity = high},
|
||||||
Color, false, Msg) when ?VALID_COLOR(Color) ->
|
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},
|
colorize(#state_t{caller=command_line, intensity = low},
|
||||||
Color, false, Msg) when ?VALID_COLOR(Color) ->
|
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) ->
|
colorize(_LogState, _Color, _Bold, Msg) ->
|
||||||
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
7
src/ec_cmd_log.hrl
Normal 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).
|
|
@ -212,36 +212,3 @@ to_atom(X)
|
||||||
erlang:list_to_existing_atom(X);
|
erlang:list_to_existing_atom(X);
|
||||||
to_atom(X) ->
|
to_atom(X) ->
|
||||||
to_atom(to_list(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.
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ beam_to_erl_source(BeamFName, ErlFName) ->
|
||||||
Src =
|
Src =
|
||||||
erl_prettypr:format(erl_syntax:form_list(tl(Forms))),
|
erl_prettypr:format(erl_syntax:form_list(tl(Forms))),
|
||||||
{ok, Fd} = file:open(ErlFName, [write]),
|
{ok, Fd} = file:open(ErlFName, [write]),
|
||||||
io:fwrite(Fd, "~s~n", [Src]),
|
io:fwrite(Fd, "~ts~n", [Src]),
|
||||||
file:close(Fd);
|
file:close(Fd);
|
||||||
Error ->
|
Error ->
|
||||||
Error
|
Error
|
||||||
|
|
113
src/ec_date.erl
113
src/ec_date.erl
|
@ -44,9 +44,9 @@
|
||||||
-define( is_month(X), ( (is_integer(X) andalso X =< 12) orelse ?is_hinted_month(X) ) ).
|
-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( 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(GREGORIAN_SECONDS_1970, 62_167_219_200).
|
||||||
-define(ISO_8601_DATETIME_FORMAT, "Y-m-dTG:i:sZ").
|
-define(ISO_8601_DATETIME_FORMAT, "Y-m-dTH:i:sZ").
|
||||||
-define(ISO_8601_DATETIME_WITH_MS_FORMAT, "Y-m-dTG:i:s.fZ").
|
-define(ISO_8601_DATETIME_WITH_MS_FORMAT, "Y-m-dTH:i:s.fZ").
|
||||||
|
|
||||||
-type year() :: non_neg_integer().
|
-type year() :: non_neg_integer().
|
||||||
-type month() :: 1..12 | {?MONTH_TAG, 1..12}.
|
-type month() :: 1..12 | {?MONTH_TAG, 1..12}.
|
||||||
|
@ -54,7 +54,7 @@
|
||||||
-type hour() :: 0..23.
|
-type hour() :: 0..23.
|
||||||
-type minute() :: 0..59.
|
-type minute() :: 0..59.
|
||||||
-type second() :: 0..59.
|
-type second() :: 0..59.
|
||||||
-type microsecond() :: 0..999999.
|
-type microsecond() :: 0..999_999.
|
||||||
|
|
||||||
-type daynum() :: 1..7.
|
-type daynum() :: 1..7.
|
||||||
-type date() :: {year(),month(),day()}.
|
-type date() :: {year(),month(),day()}.
|
||||||
|
@ -101,7 +101,7 @@ parse(Date, Now) ->
|
||||||
do_parse(Date, Now, []).
|
do_parse(Date, Now, []).
|
||||||
|
|
||||||
do_parse(Date, Now, Opts) ->
|
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} ->
|
{error, bad_date} ->
|
||||||
erlang:throw({?MODULE, {bad_date, Date}});
|
erlang:throw({?MODULE, {bad_date, Date}});
|
||||||
{D1, T1} = {{Y, M, D}, {H, M1, S}}
|
{D1, T1} = {{Y, M, D}, {H, M1, S}}
|
||||||
|
@ -138,11 +138,11 @@ nparse(Date) ->
|
||||||
{DateS, {H, M, S, Ms} } ->
|
{DateS, {H, M, S, Ms} } ->
|
||||||
GSeconds = calendar:datetime_to_gregorian_seconds({DateS, {H, M, S} }),
|
GSeconds = calendar:datetime_to_gregorian_seconds({DateS, {H, M, S} }),
|
||||||
ESeconds = GSeconds - ?GREGORIAN_SECONDS_1970,
|
ESeconds = GSeconds - ?GREGORIAN_SECONDS_1970,
|
||||||
{ESeconds div 1000000, ESeconds rem 1000000, Ms};
|
{ESeconds div 1_000_000, ESeconds rem 1_000_000, Ms};
|
||||||
DateTime ->
|
DateTime ->
|
||||||
GSeconds = calendar:datetime_to_gregorian_seconds(DateTime),
|
GSeconds = calendar:datetime_to_gregorian_seconds(DateTime),
|
||||||
ESeconds = GSeconds - ?GREGORIAN_SECONDS_1970,
|
ESeconds = GSeconds - ?GREGORIAN_SECONDS_1970,
|
||||||
{ESeconds div 1000000, ESeconds rem 1000000, 0}
|
{ESeconds div 1_000_000, ESeconds rem 1_000_000, 0}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%%
|
%%
|
||||||
|
@ -151,7 +151,7 @@ nparse(Date) ->
|
||||||
|
|
||||||
parse([Year, X, Month, X, Day, Hour, $:, Min, $:, Sec, $., Micros, $Z ], _Now, _Opts)
|
parse([Year, X, Month, X, Day, Hour, $:, Min, $:, Sec, $., Micros, $Z ], _Now, _Opts)
|
||||||
when ?is_world_sep(X)
|
when ?is_world_sep(X)
|
||||||
andalso (Micros >= 0 andalso Micros < 1000000)
|
andalso (Micros >= 0 andalso Micros < 1_000_000)
|
||||||
andalso Year > 31 ->
|
andalso Year > 31 ->
|
||||||
{{Year, Month, Day}, {hour(Hour, []), Min, Sec}, {Micros}};
|
{{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)
|
parse([Year, X, Month, X, Day, Hour, $:, Min, $:, Sec, $., Micros, $+, Off | _Rest ], _Now, _Opts)
|
||||||
when (?is_us_sep(X) orelse ?is_world_sep(X))
|
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 ->
|
andalso Year > 31 ->
|
||||||
{{Year, Month, Day}, {hour(Hour, []) - Off, Min, Sec}, {Micros}};
|
{{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)
|
parse([Year, X, Month, X, Day, Hour, $:, Min, $:, Sec, $., Micros, $-, Off | _Rest ], _Now, _Opts)
|
||||||
when (?is_us_sep(X) orelse ?is_world_sep(X))
|
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 ->
|
andalso Year > 31 ->
|
||||||
{{Year, Month, Day}, {hour(Hour, []) + Off, Min, Sec}, {Micros}};
|
{{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) ->
|
andalso ?is_year(Year) ->
|
||||||
{{Year, Month, Day}, {hour(Hour, PAM), Min, Sec}, {Ms}};
|
{{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
|
%% Date/Times Dec 1st, 2012 6:25 PM
|
||||||
parse([Month,Day,Year,Hour,$:,Min,$:,Sec | PAM], _Now, _Opts)
|
parse([Month,Day,Year,Hour,$:,Min,$:,Sec | PAM], _Now, _Opts)
|
||||||
when ?is_meridian(PAM) andalso ?is_hinted_month(Month) andalso ?is_day(Day) ->
|
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) ->
|
when ?is_meridian(PAM) andalso ?is_hinted_month(Month) andalso ?is_day(Day) ->
|
||||||
{{Year, Month, Day}, {hour(Hour, PAM), 0, 0}};
|
{{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)
|
%% Date/Times Fri Nov 21 14:55:26 +0000 2014 (Twitter format)
|
||||||
parse([Month, Day, Hour,$:,Min,$:,Sec, Year], _Now, _Opts)
|
parse([Month, Day, Hour,$:,Min,$:,Sec, Year], _Now, _Opts)
|
||||||
when ?is_hinted_month(Month), ?is_day(Day), ?is_year(Year) ->
|
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) ->
|
when ?is_num(N1), ?is_num(N2), ?is_num(N3), ?is_num(N4) ->
|
||||||
tokenise(Rest, [ ltoi([N1, N2, N3, N4]) * 100, $. | Acc]);
|
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([$., 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([$., 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([$., 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)
|
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) ->
|
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([$g|T], {_,{H,_,_}}=Dt, Acc) ->
|
||||||
format(T, Dt, [itol(H)|Acc]);
|
format(T, Dt, [itol(H)|Acc]);
|
||||||
format([$G|T], {_,{H,_,_}}=Dt, 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([$h|T], {_,{H,_,_}}=Dt, Acc) when H > 12 ->
|
||||||
format(T, Dt, [pad2(H-12)|Acc]);
|
format(T, Dt, [pad2(H-12)|Acc]);
|
||||||
format([$h|T], {_,{H,_,_}}=Dt, Acc) ->
|
format([$h|T], {_,{H,_,_}}=Dt, Acc) ->
|
||||||
|
@ -728,12 +709,6 @@ pad6(X) when is_integer(X) ->
|
||||||
ltoi(X) ->
|
ltoi(X) ->
|
||||||
list_to_integer(X).
|
list_to_integer(X).
|
||||||
|
|
||||||
-ifdef(unicode_str).
|
|
||||||
uppercase(Str) -> string:uppercase(Str).
|
|
||||||
-else.
|
|
||||||
uppercase(Str) -> string:to_upper(Str).
|
|
||||||
-endif.
|
|
||||||
|
|
||||||
%%%===================================================================
|
%%%===================================================================
|
||||||
%%% Tests
|
%%% Tests
|
||||||
%%%===================================================================
|
%%%===================================================================
|
||||||
|
@ -743,7 +718,7 @@ uppercase(Str) -> string:to_upper(Str).
|
||||||
|
|
||||||
|
|
||||||
-define(DATE, {{2001,3,10},{17,16,17}}).
|
-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_NOON, {{2001,3,10},{12,0,0}}).
|
||||||
-define(DATE_MIDNIGHT, {{2001,3,10},{0,0,0}}).
|
-define(DATE_MIDNIGHT, {{2001,3,10},{0,0,0}}).
|
||||||
-define(ISO, "o \\WW").
|
-define(ISO, "o \\WW").
|
||||||
|
@ -762,6 +737,8 @@ basic_format_test_() ->
|
||||||
?_assertEqual(format("H:i:s",?DATE), "17:16:17"),
|
?_assertEqual(format("H:i:s",?DATE), "17:16:17"),
|
||||||
?_assertEqual(format("z",?DATE), "68"),
|
?_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",?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_NOON), "12PM"),
|
?_assertEqual(format("gA",?DATE_NOON), "12PM"),
|
||||||
?_assertEqual(format("ga",?DATE_MIDNIGHT), "12am"),
|
?_assertEqual(format("ga",?DATE_MIDNIGHT), "12am"),
|
||||||
|
@ -978,7 +955,7 @@ ms_test_() ->
|
||||||
Now=os:timestamp(),
|
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,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),
|
?_assertEqual(format("H:m:s.f \\m \\i\\s \\m\\o\\n\\t\\h",?DATEMS),
|
||||||
"17:03:17.123456 m is month"),
|
"17:03:17.123456 m is month"),
|
||||||
?_assertEqual(format("Y-m-d\\TH:i:s.f",?DATEMS),
|
?_assertEqual(format("Y-m-d\\TH:i:s.f",?DATEMS),
|
||||||
|
@ -1017,21 +994,21 @@ format_iso8601_test_() ->
|
||||||
?_assertEqual("2001-03-10T17:16:17.000000Z",
|
?_assertEqual("2001-03-10T17:16:17.000000Z",
|
||||||
format_iso8601({{2001,3,10},{17,16,17,0}})),
|
format_iso8601({{2001,3,10},{17,16,17,0}})),
|
||||||
?_assertEqual("2001-03-10T17:16:17.100000Z",
|
?_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",
|
?_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",
|
?_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",
|
?_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",
|
?_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",
|
?_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",
|
?_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",
|
?_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",
|
?_assertEqual("2001-03-10T17:16:17.000456Z",
|
||||||
format_iso8601({{2001,3,10},{17,16,17,456}})),
|
format_iso8601({{2001,3,10},{17,16,17,456}})),
|
||||||
?_assertEqual("2001-03-10T17:16:17.000056Z",
|
?_assertEqual("2001-03-10T17:16:17.000056Z",
|
||||||
|
@ -1043,21 +1020,21 @@ format_iso8601_test_() ->
|
||||||
?_assertEqual("2001-03-10T07:16:17.000000Z",
|
?_assertEqual("2001-03-10T07:16:17.000000Z",
|
||||||
format_iso8601({{2001,3,10},{07,16,17,0}})),
|
format_iso8601({{2001,3,10},{07,16,17,0}})),
|
||||||
?_assertEqual("2001-03-10T07:16:17.100000Z",
|
?_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",
|
?_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",
|
?_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",
|
?_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",
|
?_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",
|
?_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",
|
?_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",
|
?_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",
|
?_assertEqual("2001-03-10T07:16:17.000456Z",
|
||||||
format_iso8601({{2001,3,10},{07,16,17,456}})),
|
format_iso8601({{2001,3,10},{07,16,17,456}})),
|
||||||
?_assertEqual("2001-03-10T07:16:17.000056Z",
|
?_assertEqual("2001-03-10T07:16:17.000056Z",
|
||||||
|
@ -1074,31 +1051,31 @@ parse_iso8601_test_() ->
|
||||||
parse("2001-03-10T17:16:17.000Z")),
|
parse("2001-03-10T17:16:17.000Z")),
|
||||||
?_assertEqual({{2001,3,10},{17,16,17,0}},
|
?_assertEqual({{2001,3,10},{17,16,17,0}},
|
||||||
parse("2001-03-10T17:16:17.000000Z")),
|
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")),
|
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")),
|
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")),
|
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")),
|
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")),
|
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")),
|
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")),
|
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")),
|
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")),
|
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")),
|
parse("2001-03-10T16:16:17.123456-01:00")),
|
||||||
|
|
||||||
?_assertEqual({{2001,3,10},{17,16,17,456}},
|
?_assertEqual({{2001,3,10},{17,16,17,456}},
|
||||||
parse("2001-03-10T17:16:17.000456Z")),
|
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"))
|
parse("2001-03-10T17:16:17.123000Z"))
|
||||||
].
|
].
|
||||||
|
|
||||||
|
|
|
@ -34,11 +34,7 @@
|
||||||
%%%===================================================================
|
%%%===================================================================
|
||||||
%% This should be opaque, but that kills dialyzer so for now we export it
|
%% This should be opaque, but that kills dialyzer so for now we export it
|
||||||
%% however you should not rely on the internal representation here
|
%% however you should not rely on the internal representation here
|
||||||
-ifdef(namespaced_types).
|
|
||||||
-type dictionary(_K, _V) :: dict:dict().
|
-type dictionary(_K, _V) :: dict:dict().
|
||||||
-else.
|
|
||||||
-type dictionary(_K, _V) :: dict().
|
|
||||||
-endif.
|
|
||||||
|
|
||||||
%%%===================================================================
|
%%%===================================================================
|
||||||
%%% API
|
%%% API
|
||||||
|
|
|
@ -42,8 +42,6 @@
|
||||||
-type key(T) :: T.
|
-type key(T) :: T.
|
||||||
-type value(T) :: T.
|
-type value(T) :: T.
|
||||||
|
|
||||||
-ifdef(have_callback_support).
|
|
||||||
|
|
||||||
-callback new() -> any().
|
-callback new() -> any().
|
||||||
-callback has_key(key(any()), any()) -> boolean().
|
-callback has_key(key(any()), any()) -> boolean().
|
||||||
-callback get(key(any()), any()) -> any().
|
-callback get(key(any()), any()) -> any().
|
||||||
|
@ -55,27 +53,6 @@
|
||||||
-callback from_list([{key(any()), value(any())}]) -> any().
|
-callback from_list([{key(any()), value(any())}]) -> any().
|
||||||
-callback keys(any()) -> [key(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
|
%%% API
|
||||||
%%%===================================================================
|
%%%===================================================================
|
||||||
|
|
136
src/ec_file.erl
136
src/ec_file.erl
|
@ -80,7 +80,14 @@ copy(From, To) ->
|
||||||
copy_(From, To, [{file_info, [mode, time, owner, group]}]).
|
copy_(From, To, [{file_info, [mode, time, owner, group]}]).
|
||||||
|
|
||||||
copy_(From, To, Options) ->
|
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, _} ->
|
{ok, _} ->
|
||||||
copy_file_info(To, From, proplists:get_value(file_info, Options, []));
|
copy_file_info(To, From, proplists:get_value(file_info, Options, []));
|
||||||
{error, Error} ->
|
{error, Error} ->
|
||||||
|
@ -132,23 +139,20 @@ try_write_owner(To, #file_info{uid=OwnerId}) ->
|
||||||
try_write_group(To, #file_info{gid=OwnerId}) ->
|
try_write_group(To, #file_info{gid=OwnerId}) ->
|
||||||
file:write_file_info(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
|
%% @doc return the MD5 digest of a string or a binary,
|
||||||
%% same name.
|
%% named after the UNIX utility.
|
||||||
-spec md5sum(string() | binary()) -> string().
|
-spec md5sum(string() | binary()) -> string().
|
||||||
md5sum(Value) ->
|
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
|
%% @doc return the SHA-1 digest of a string or a binary,
|
||||||
%% same name.
|
%% named after the UNIX utility.
|
||||||
-ifdef(deprecated_crypto).
|
|
||||||
-spec sha1sum(string() | binary()) -> string().
|
-spec sha1sum(string() | binary()) -> string().
|
||||||
sha1sum(Value) ->
|
sha1sum(Value) ->
|
||||||
hex(binary_to_list(crypto:sha(Value))).
|
bin_to_hex(crypto:hash(sha, Value)).
|
||||||
-else.
|
|
||||||
-spec sha1sum(string() | binary()) -> string().
|
bin_to_hex(Bin) ->
|
||||||
sha1sum(Value) ->
|
hex(binary_to_list(Bin)).
|
||||||
hex(binary_to_list(crypto:hash(sha, Value))).
|
|
||||||
-endif.
|
|
||||||
|
|
||||||
%% @doc delete a file. Use the recursive option for directories.
|
%% @doc delete a file. Use the recursive option for directories.
|
||||||
%% <pre>
|
%% <pre>
|
||||||
|
@ -167,7 +171,7 @@ remove(Path, Options) ->
|
||||||
remove(Path) ->
|
remove(Path) ->
|
||||||
remove(Path, []).
|
remove(Path, []).
|
||||||
|
|
||||||
%% @doc indicates witha boolean if the path supplied refers to symlink.
|
%% @doc indicates with a boolean if the path supplied refers to symlink.
|
||||||
-spec is_symlink(file:name()) -> boolean().
|
-spec is_symlink(file:name()) -> boolean().
|
||||||
is_symlink(Path) ->
|
is_symlink(Path) ->
|
||||||
case file:read_link_info(Path) of
|
case file:read_link_info(Path) of
|
||||||
|
@ -219,7 +223,7 @@ real_dir_path(Path) ->
|
||||||
%% function of the same name.
|
%% function of the same name.
|
||||||
-spec insecure_mkdtemp() -> TmpDirPath::file:name() | {error, term()}.
|
-spec insecure_mkdtemp() -> TmpDirPath::file:name() | {error, term()}.
|
||||||
insecure_mkdtemp() ->
|
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 =
|
TmpDirPath =
|
||||||
filename:join([tmp(), lists:flatten([".tmp_dir", UniqueNumber])]),
|
filename:join([tmp(), lists:flatten([".tmp_dir", UniqueNumber])]),
|
||||||
|
|
||||||
|
@ -245,7 +249,7 @@ mkdir_path(Path) ->
|
||||||
mkdir_p(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()}.
|
-spec read(FilePath::file:filename()) -> {ok, binary()} | {error, Reason::term()}.
|
||||||
read(FilePath) ->
|
read(FilePath) ->
|
||||||
%% Now that we are moving away from exceptions again this becomes
|
%% Now that we are moving away from exceptions again this becomes
|
||||||
|
@ -254,7 +258,7 @@ read(FilePath) ->
|
||||||
file:read_file(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()}.
|
-spec write(FileName::file:filename(), Contents::string()) -> ok | {error, Reason::term()}.
|
||||||
write(FileName, Contents) ->
|
write(FileName, Contents) ->
|
||||||
%% Now that we are moving away from exceptions again this becomes
|
%% Now that we are moving away from exceptions again this becomes
|
||||||
|
@ -371,101 +375,3 @@ hex0(I) -> $0 + I.
|
||||||
sub_files(From) ->
|
sub_files(From) ->
|
||||||
{ok, SubFiles} = file:list_dir(From),
|
{ok, SubFiles} = file:list_dir(From),
|
||||||
[filename:join(From, SubFile) || SubFile <- SubFiles].
|
[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.
|
|
||||||
|
|
|
@ -135,79 +135,3 @@ from_list(List) when is_list(List) ->
|
||||||
-spec keys(gb_trees:tree(K,_V)) -> [ec_dictionary:key(K)].
|
-spec keys(gb_trees:tree(K,_V)) -> [ec_dictionary:key(K)].
|
||||||
keys(Data) ->
|
keys(Data) ->
|
||||||
gb_trees: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.
|
|
||||||
|
|
|
@ -17,6 +17,13 @@
|
||||||
-export([new/0,
|
-export([new/0,
|
||||||
vsn/1]).
|
vsn/1]).
|
||||||
|
|
||||||
|
-ifdef(TEST).
|
||||||
|
-export([parse_tags/1,
|
||||||
|
get_patch_count/1,
|
||||||
|
collect_default_refcount/1
|
||||||
|
]).
|
||||||
|
-endif.
|
||||||
|
|
||||||
-export_type([t/0]).
|
-export_type([t/0]).
|
||||||
|
|
||||||
%%%===================================================================
|
%%%===================================================================
|
||||||
|
@ -34,7 +41,7 @@
|
||||||
new() ->
|
new() ->
|
||||||
{}.
|
{}.
|
||||||
|
|
||||||
-spec vsn(t()) -> {ok, string()} | {error, Reason::any()}.
|
-spec vsn(t()|string()) -> {ok, string()} | {error, Reason::any()}.
|
||||||
vsn(Data) ->
|
vsn(Data) ->
|
||||||
{Vsn, RawRef, RawCount} = collect_default_refcount(Data),
|
{Vsn, RawRef, RawCount} = collect_default_refcount(Data),
|
||||||
{ok, build_vsn_string(Vsn, RawRef, RawCount)}.
|
{ok, build_vsn_string(Vsn, RawRef, RawCount)}.
|
||||||
|
@ -61,12 +68,7 @@ collect_default_refcount(Data) ->
|
||||||
build_vsn_string(Vsn, RawRef, RawCount) ->
|
build_vsn_string(Vsn, RawRef, RawCount) ->
|
||||||
%% Cleanup the tag and the Ref information. Basically leading 'v's and
|
%% Cleanup the tag and the Ref information. Basically leading 'v's and
|
||||||
%% whitespace needs to go away.
|
%% whitespace needs to go away.
|
||||||
RefTag = case RawRef of
|
RefTag = [".ref", re:replace(RawRef, "\\s", "", [global])],
|
||||||
undefined ->
|
|
||||||
"";
|
|
||||||
RawRef ->
|
|
||||||
[".ref", re:replace(RawRef, "\\s", "", [global])]
|
|
||||||
end,
|
|
||||||
Count = erlang:iolist_to_binary(re:replace(RawCount, "\\s", "", [global])),
|
Count = erlang:iolist_to_binary(re:replace(RawCount, "\\s", "", [global])),
|
||||||
|
|
||||||
%% Create the valid [semver](http://semver.org) version from the tag
|
%% 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) ->
|
get_patch_count(RawRef) ->
|
||||||
Ref = re:replace(RawRef, "\\s", "", [global]),
|
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]),
|
[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("");
|
parse_tags("");
|
||||||
parse_tags(Pattern) ->
|
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),
|
Tag = os:cmd(Cmd),
|
||||||
Vsn = slice(Tag, len(Pattern)),
|
case Tag of
|
||||||
Vsn1 = trim(trim(Vsn, left, "v"), right, "\n"),
|
"fatal: " ++ _ ->
|
||||||
{Tag, Vsn1}.
|
{undefined, ""};
|
||||||
|
_ ->
|
||||||
-ifdef(unicode_str).
|
Vsn = string:slice(Tag, string:length(Pattern)),
|
||||||
len(Str) -> string:length(Str).
|
Vsn1 = string:trim(string:trim(Vsn, leading, "v"), trailing, "\n"),
|
||||||
trim(Str, right, Chars) -> string:trim(Str, trailing, Chars);
|
{Tag, Vsn1}
|
||||||
trim(Str, left, Chars) -> string:trim(Str, leading, Chars);
|
end.
|
||||||
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.
|
|
||||||
|
|
183
src/ec_lists.erl
183
src/ec_lists.erl
|
@ -52,7 +52,7 @@ find(_Fun, []) ->
|
||||||
error.
|
error.
|
||||||
|
|
||||||
%% @doc Fetch a value from the list. If the function returns true the
|
%% @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
|
%% the function has never returned true an exception not_found is
|
||||||
%% thrown.
|
%% thrown.
|
||||||
-spec fetch(fun(), list()) -> term().
|
-spec fetch(fun(), list()) -> term().
|
||||||
|
@ -63,184 +63,3 @@ fetch(Fun, List) when is_list(List), is_function(Fun) ->
|
||||||
error ->
|
error ->
|
||||||
throw(not_found)
|
throw(not_found)
|
||||||
end.
|
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.
|
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
%%% most list operations parallel. It can operate on each element in
|
%%% most list operations parallel. It can operate on each element in
|
||||||
%%% parallel, for IO-bound operations, on sublists in parallel, for
|
%%% parallel, for IO-bound operations, on sublists in parallel, for
|
||||||
%%% taking advantage of multi-core machines with CPU-bound operations,
|
%%% 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
|
%%% handles errors and node failures. It can be configured, tuned, and
|
||||||
%%% tweaked to get optimal performance while minimizing overhead.
|
%%% tweaked to get optimal performance while minimizing overhead.
|
||||||
%%%
|
%%%
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
%%% lists, returning exactly the same result, and having both a form
|
%%% lists, returning exactly the same result, and having both a form
|
||||||
%%% with an identical syntax that operates on each element in parallel
|
%%% with an identical syntax that operates on each element in parallel
|
||||||
%%% and a form which takes an optional "malt", a specification for how
|
%%% 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 is the one exception, parallel fold is different from linear
|
||||||
%%% fold. This module also include a simple mapreduce implementation,
|
%%% 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
|
%%% processes. If one of them does a non-normal exit, plists receives
|
||||||
%%% the 'DOWN' message believing it to be from one of its own
|
%%% the 'DOWN' message believing it to be from one of its own
|
||||||
%%% processes. The error propagation system goes into effect, which
|
%%% 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).
|
-module(ec_plists).
|
||||||
|
|
||||||
|
@ -330,14 +330,14 @@ fold(Fun, Fuse, InitAcc, List, Malt) ->
|
||||||
end,
|
end,
|
||||||
runmany(Fun2, Fuse, List, Malt).
|
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>
|
%% <a href="http://www.erlang.org/doc/man/lists.html">lists</a>
|
||||||
%% except it makes no guarantee about the order it processes list elements.
|
%% except it makes no guarantee about the order it processes list elements.
|
||||||
-spec foreach(fun(), list()) -> ok.
|
-spec foreach(fun(), list()) -> ok.
|
||||||
foreach(Fun, List) ->
|
foreach(Fun, List) ->
|
||||||
foreach(Fun, List, 1).
|
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>
|
%% <a href="http://www.erlang.org/doc/man/lists.html">lists</a>
|
||||||
%% except it makes no guarantee about the order it processes list elements.
|
%% except it makes no guarantee about the order it processes list elements.
|
||||||
-spec foreach(fun(), list(), malt()) -> ok.
|
-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
|
%% 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 lists together. These are done in parallel. Each sublist is
|
||||||
%% sorted in a seperate process, and each merging of results is done in a
|
%% sorted in a separate process, and each merging of results is done in a
|
||||||
%% seperate process. Malt defaults to 100, causing the list to be split into
|
%% separate process. Malt defaults to 100, causing the list to be split into
|
||||||
%% 100-element sublists.
|
%% 100-element sublists.
|
||||||
-spec sort(fun(), list(), malt()) -> list().
|
-spec sort(fun(), list(), malt()) -> list().
|
||||||
sort(Fun, List, Malt) ->
|
sort(Fun, List, Malt) ->
|
||||||
|
@ -464,11 +464,11 @@ usort(Fun, List) ->
|
||||||
%%
|
%%
|
||||||
%% usort splits the list into sublists and sorts them, and it merges the
|
%% 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 lists together. These are done in parallel. Each sublist is
|
||||||
%% sorted in a seperate process, and each merging of results is done in a
|
%% sorted in a separate process, and each merging of results is done in a
|
||||||
%% seperate process. Malt defaults to 100, causing the list to be split into
|
%% separate process. Malt defaults to 100, causing the list to be split into
|
||||||
%% 100-element sublists.
|
%% 100-element sublists.
|
||||||
%%
|
%%
|
||||||
%% usort removes duplicate elments while it sorts.
|
%% usort removes duplicate elements while it sorts.
|
||||||
-spec usort(fun(), list(), malt()) -> list().
|
-spec usort(fun(), list(), malt()) -> list().
|
||||||
usort(Fun, List, Malt) ->
|
usort(Fun, List, Malt) ->
|
||||||
Fun2 = fun (L) ->
|
Fun2 = fun (L) ->
|
||||||
|
@ -480,16 +480,9 @@ usort(Fun, List, Malt) ->
|
||||||
runmany(Fun2, {recursive, Fuse}, List, Malt).
|
runmany(Fun2, {recursive, Fuse}, List, Malt).
|
||||||
|
|
||||||
%% @doc Like below, assumes default MapMalt of 1.
|
%% @doc Like below, assumes default MapMalt of 1.
|
||||||
-ifdef(namespaced_types).
|
|
||||||
-spec mapreduce(MapFunc, list()) -> dict:dict() when
|
-spec mapreduce(MapFunc, list()) -> dict:dict() when
|
||||||
MapFunc :: fun((term()) -> DeepListOfKeyValuePairs),
|
MapFunc :: fun((term()) -> DeepListOfKeyValuePairs),
|
||||||
DeepListOfKeyValuePairs :: [DeepListOfKeyValuePairs] | {Key::term(), Value::term()}.
|
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) ->
|
||||||
mapreduce(MapFunc, List, 1).
|
mapreduce(MapFunc, List, 1).
|
||||||
|
@ -514,21 +507,14 @@ mapreduce(MapFunc, List, MapMalt) ->
|
||||||
%% reducer's final state.
|
%% reducer's final state.
|
||||||
%%
|
%%
|
||||||
%% MapMalt is the malt for the mapping operation, with a default value of 1,
|
%% 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
|
%% mapreduce requires OTP R11B, or it may leave monitoring messages in the
|
||||||
%% message queue.
|
%% message queue.
|
||||||
-ifdef(namespaced_types).
|
|
||||||
-spec mapreduce(MapFunc, list(), InitState::term(), ReduceFunc, malt()) -> dict:dict() when
|
-spec mapreduce(MapFunc, list(), InitState::term(), ReduceFunc, malt()) -> dict:dict() when
|
||||||
MapFunc :: fun((term()) -> DeepListOfKeyValuePairs),
|
MapFunc :: fun((term()) -> DeepListOfKeyValuePairs),
|
||||||
DeepListOfKeyValuePairs :: [DeepListOfKeyValuePairs] | {Key::term(), Value::term()},
|
DeepListOfKeyValuePairs :: [DeepListOfKeyValuePairs] | {Key::term(), Value::term()},
|
||||||
ReduceFunc :: fun((OldState::term(), Key::term(), Value::term()) -> NewState::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) ->
|
mapreduce(MapFunc, List, InitState, ReduceFunc, MapMalt) ->
|
||||||
Parent = self(),
|
Parent = self(),
|
||||||
{Reducer, ReducerRef} =
|
{Reducer, ReducerRef} =
|
||||||
|
@ -586,7 +572,7 @@ add_key(Dict, Key, Value) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% @doc Like below, but assumes a Malt of 1,
|
%% @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().
|
-spec runmany(fun(), fuse(), list()) -> term().
|
||||||
runmany(Fun, Fuse, List) ->
|
runmany(Fun, Fuse, List) ->
|
||||||
runmany(Fun, Fuse, List, 1).
|
runmany(Fun, Fuse, List, 1).
|
||||||
|
@ -615,7 +601,7 @@ runmany(Fun, Fuse, List) ->
|
||||||
%% continues fusing pairs of results until it is down to one.
|
%% continues fusing pairs of results until it is down to one.
|
||||||
%%
|
%%
|
||||||
%% Recursive fuse is down in parallel with processing the sublists, and a
|
%% 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
|
%% algorithm. Linear fuse is done after all results of processing sublists
|
||||||
%% have been collected, and can only run in a single process.
|
%% 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.
|
%% or {nodes, NodeList}. Degenerates recursive fuse into linear fuse.
|
||||||
runmany(Fun, Fuse, List, local, Split, []);
|
runmany(Fun, Fuse, List, local, Split, []);
|
||||||
runmany(Fun, Fuse, List, Nodes, no_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, Nodes, 1, []);
|
||||||
runmany(Fun, Fuse, List, local, Split, []) ->
|
runmany(Fun, Fuse, List, local, Split, []) ->
|
||||||
List2 = splitmany(List, 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
|
%% We have data, but no nodes either available or occupied
|
||||||
erlang:exit(allnodescrashed).
|
erlang:exit(allnodescrashed).
|
||||||
|
|
||||||
-ifdef(fun_stacktrace).
|
|
||||||
runmany_wrap(Fun, Parent) ->
|
runmany_wrap(Fun, Parent) ->
|
||||||
try
|
try
|
||||||
Fun
|
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
|
|
||||||
catch
|
catch
|
||||||
exit:siblingdied ->
|
exit:siblingdied ->
|
||||||
ok;
|
ok;
|
||||||
|
@ -900,7 +871,6 @@ runmany_wrap(Fun, Parent) ->
|
||||||
throw:R:Stacktrace ->
|
throw:R:Stacktrace ->
|
||||||
Parent ! {erlang:self(), error, {{nocatch, R}, Stacktrace}}
|
Parent ! {erlang:self(), error, {{nocatch, R}, Stacktrace}}
|
||||||
end.
|
end.
|
||||||
-endif.
|
|
||||||
|
|
||||||
delete_running(Pid, [{Pid, Node, List}|Running], Acc) ->
|
delete_running(Pid, [{Pid, Node, List}|Running], Acc) ->
|
||||||
{Running ++ Acc, Node, List};
|
{Running ++ Acc, Node, List};
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
%%% representation of a dictionary, where a red-black tree is used to
|
%%% representation of a dictionary, where a red-black tree is used to
|
||||||
%%% store the keys and values.
|
%%% 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
|
%%% ec_dictionary but with a defined representation. One difference is
|
||||||
%%% that while dict considers two keys as different if they do not
|
%%% that while dict considers two keys as different if they do not
|
||||||
%%% match (=:=), this module considers two keys as different if and
|
%%% 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, B}, List) ->
|
||||||
to_list(A, [{Xk, Xv} | to_list(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),
|
-spec lbalance(color(), dictionary(K, V),
|
||||||
ec_dictionary:key(K), ec_dictionary:value(V),
|
ec_dictionary:key(K), ec_dictionary:value(V),
|
||||||
dictionary(K, V)) ->
|
dictionary(K, V)) ->
|
||||||
|
|
|
@ -202,13 +202,13 @@ pes(VsnA, VsnB) ->
|
||||||
%%%===================================================================
|
%%%===================================================================
|
||||||
%%% Friend Functions
|
%%% 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().
|
-spec internal_parse_version(iolist()) -> semver().
|
||||||
internal_parse_version([MMP, AlphaPart, BuildPart, _]) ->
|
internal_parse_version([MMP, AlphaPart, BuildPart, _]) ->
|
||||||
{parse_major_minor_patch_minpatch(MMP), {parse_alpha_part(AlphaPart),
|
{parse_major_minor_patch_minpatch(MMP), {parse_alpha_part(AlphaPart),
|
||||||
parse_alpha_part(BuildPart)}}.
|
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().
|
-spec parse_major_minor_patch_minpatch(iolist()) -> major_minor_patch_minpatch().
|
||||||
parse_major_minor_patch_minpatch([MajVsn, [], [], []]) ->
|
parse_major_minor_patch_minpatch([MajVsn, [], [], []]) ->
|
||||||
strip_maj_version(MajVsn);
|
strip_maj_version(MajVsn);
|
||||||
|
@ -224,7 +224,7 @@ parse_major_minor_patch_minpatch([MajVsn,
|
||||||
[<<".">>, MinPatch]]) ->
|
[<<".">>, MinPatch]]) ->
|
||||||
{strip_maj_version(MajVsn), MinVsn, PatchVsn, 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()].
|
-spec parse_alpha_part(iolist()) -> [alpha_part()].
|
||||||
parse_alpha_part([]) ->
|
parse_alpha_part([]) ->
|
||||||
[];
|
[];
|
||||||
|
@ -309,455 +309,3 @@ internal_pes(VsnA, {{LM, LMI, LP, LMP}, Alpha})
|
||||||
lt(VsnA, {{LM, LMI, LP + 1, 0}, {[], []}});
|
lt(VsnA, {{LM, LMI, LP + 1, 0}, {[], []}});
|
||||||
internal_pes(Vsn, LVsn) ->
|
internal_pes(Vsn, LVsn) ->
|
||||||
gte(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.
|
|
||||||
|
|
|
@ -48,7 +48,6 @@ parse(Input) when is_binary(Input) ->
|
||||||
|
|
||||||
|
|
||||||
transform(_,Node,_Index) -> Node.
|
transform(_,Node,_Index) -> Node.
|
||||||
-file("peg_includes.hrl", 1).
|
|
||||||
-type index() :: {{line, pos_integer()}, {column, pos_integer()}}.
|
-type index() :: {{line, pos_integer()}, {column, pos_integer()}}.
|
||||||
-type input() :: binary().
|
-type input() :: binary().
|
||||||
-type parse_failure() :: {fail, term()}.
|
-type parse_failure() :: {fail, term()}.
|
||||||
|
|
|
@ -39,6 +39,11 @@
|
||||||
say/1,
|
say/1,
|
||||||
say/2]).
|
say/2]).
|
||||||
|
|
||||||
|
-ifdef(TEST).
|
||||||
|
-export([get_boolean/1,
|
||||||
|
get_integer/1]).
|
||||||
|
-endif.
|
||||||
|
|
||||||
-export_type([prompt/0,
|
-export_type([prompt/0,
|
||||||
type/0,
|
type/0,
|
||||||
supported/0]).
|
supported/0]).
|
||||||
|
@ -75,7 +80,7 @@ ask(Prompt) ->
|
||||||
ask_default(Prompt, Default) ->
|
ask_default(Prompt, Default) ->
|
||||||
ask_convert(Prompt, fun get_string/1, string, 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'.
|
%% value in the format specified by 'Type'.
|
||||||
-spec ask(prompt(), type()) -> supported().
|
-spec ask(prompt(), type()) -> supported().
|
||||||
ask(Prompt, boolean) ->
|
ask(Prompt, boolean) ->
|
||||||
|
@ -85,7 +90,7 @@ ask(Prompt, number) ->
|
||||||
ask(Prompt, string) ->
|
ask(Prompt, string) ->
|
||||||
ask_convert(Prompt, fun get_string/1, string, none).
|
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'.
|
%% value in the format specified by 'Type'.
|
||||||
-spec ask_default(prompt(), type(), supported()) -> supported().
|
-spec ask_default(prompt(), type(), supported()) -> supported().
|
||||||
ask_default(Prompt, boolean, Default) ->
|
ask_default(Prompt, boolean, Default) ->
|
||||||
|
@ -127,7 +132,7 @@ ask_convert(Prompt, TransFun, Type, Default) ->
|
||||||
Default ->
|
Default ->
|
||||||
[" (", io_lib:format("~p", [Default]) , ")"]
|
[" (", io_lib:format("~p", [Default]) , ")"]
|
||||||
end, "> "])),
|
end, "> "])),
|
||||||
Data = trim(trim(io:get_line(NewPrompt)), both, [$\n]),
|
Data = string:trim(string:trim(io:get_line(NewPrompt)), both, [$\n]),
|
||||||
Ret = TransFun(Data),
|
Ret = TransFun(Data),
|
||||||
case Ret of
|
case Ret of
|
||||||
no_data ->
|
no_data ->
|
||||||
|
@ -145,7 +150,7 @@ ask_convert(Prompt, TransFun, Type, Default) ->
|
||||||
Ret
|
Ret
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% @doc Trys to translate the result into a boolean
|
%% @doc Tries to translate the result into a boolean
|
||||||
-spec get_boolean(string()) -> boolean().
|
-spec get_boolean(string()) -> boolean().
|
||||||
get_boolean([]) ->
|
get_boolean([]) ->
|
||||||
no_data;
|
no_data;
|
||||||
|
@ -172,7 +177,7 @@ get_boolean([$N | _]) ->
|
||||||
get_boolean(_) ->
|
get_boolean(_) ->
|
||||||
no_clue.
|
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().
|
-spec get_integer(string()) -> integer().
|
||||||
get_integer([]) ->
|
get_integer([]) ->
|
||||||
no_data;
|
no_data;
|
||||||
|
@ -196,36 +201,3 @@ get_string(String) ->
|
||||||
false ->
|
false ->
|
||||||
no_clue
|
no_clue
|
||||||
end.
|
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.
|
|
||||||
|
|
|
@ -27,24 +27,9 @@
|
||||||
%% however you should not rely on the internal representation here
|
%% however you should not rely on the internal representation here
|
||||||
-type t() :: #t{}.
|
-type t() :: #t{}.
|
||||||
|
|
||||||
-ifdef(have_callback_support).
|
|
||||||
|
|
||||||
-callback new() -> any().
|
-callback new() -> any().
|
||||||
-callback vsn(any()) -> {ok, string()} | {error, Reason::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
|
%%% API
|
||||||
%%%===================================================================
|
%%%===================================================================
|
||||||
|
|
|
@ -6,6 +6,6 @@
|
||||||
{applications,[kernel,stdlib,cf]},
|
{applications,[kernel,stdlib,cf]},
|
||||||
{maintainers,["Eric Merritt","Tristan Sloughter",
|
{maintainers,["Eric Merritt","Tristan Sloughter",
|
||||||
"Jordan Wilberding","Martin Logan"]},
|
"Jordan Wilberding","Martin Logan"]},
|
||||||
{licenses,["Apache"]},
|
{licenses,["Apache", "MIT"]},
|
||||||
{links,[{"Github",
|
{links,[{"Github",
|
||||||
"https://github.com/erlware/erlware_commons"}]}]}.
|
"https://github.com/erlware/erlware_commons"}]}]}.
|
||||||
|
|
39
test/ec_cmd_log_tests.erl
Normal file
39
test/ec_cmd_log_tests.erl
Normal 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
28
test/ec_cnv_tests.erl
Normal 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
84
test/ec_file_tests.erl
Normal 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]).
|
67
test/ec_gb_trees_tests.erl
Normal file
67
test/ec_gb_trees_tests.erl
Normal 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
13
test/ec_git_vsn_tests.erl
Normal 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
172
test/ec_lists_tests.erl
Normal 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
447
test/ec_semver_tests.erl
Normal 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
19
test/ec_talk_tests.erl
Normal 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"))].
|
Loading…
Add table
Add a link
Reference in a new issue