diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..9611b10 --- /dev/null +++ b/.github/workflows/main.yml @@ -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 diff --git a/.gitignore b/.gitignore index 929000d..d306af0 100644 --- a/.gitignore +++ b/.gitignore @@ -7,9 +7,12 @@ doc/edoc-info doc/erlang.png ebin/* .* +!.github _build erl_crash.dump *.pyc *~ +TEST-*.xml +/foo src/ec_semver_parser.peg diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 1de9b24..0000000 --- a/.travis.yml +++ /dev/null @@ -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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e63ac46..cca6505 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -73,7 +73,7 @@ $ git stash pop ``` You SHOULD use these commands both before working on your patch and before -submitting the pull request. If conflicts arise it is your responsability +submitting the pull request. If conflicts arise it is your responsibility to deal with them. You MUST create a new branch for your work. First make sure you have diff --git a/README.md b/README.md index d4e53eb..3e9f193 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,9 @@ Erlware Commons Current Status -------------- -[![Build Status](https://secure.travis-ci.org/erlware/erlware_commons.png)](http://travis-ci.org/erlware/erlware_commons) + +[![Hex.pm](https://img.shields.io/hexpm/v/erlware_commons)](https://hex.pm/packages/erlware_commons) +[![Tests](https://github.com/erlware/erlware_commons/workflows/EUnit/badge.svg)](https://github.com/erlware/erlware_commons/actions) Introduction ------------ @@ -24,6 +26,18 @@ Goals for the project * Well Documented * Well Tested +Licenses +-------- + +This project contains elements licensed with Apache License, Version 2.0, +as well as elements licensed with The MIT License. + +You'll find license-related information in the header of specific files, +where warranted. + +In cases where no such information is present refer to +[COPYING](COPYING). + Currently Available Modules/Systems ------------------------------------ @@ -56,7 +70,7 @@ href="http://www.erlang.org/doc/man/lists.html">lists, making most list operations parallel. It can operate on each element in parallel, for IO-bound operations, on sublists in parallel, for taking advantage of multi-core machines with CPU-bound operations, and across erlang -nodes, for parallizing inside a cluster. It handles errors and node +nodes, for parallelizing inside a cluster. It handles errors and node failures. It can be configured, tuned, and tweaked to get optimal performance while minimizing overhead. @@ -64,7 +78,7 @@ Almost all the functions are identical to equivalent functions in lists, returning exactly the same result, and having both a form with an identical syntax that operates on each element in parallel and a form which takes an optional "malt", a specification for how to -parallize the operation. +parallelize the operation. fold is the one exception, parallel fold is different from linear fold. This module also include a simple mapreduce implementation, and @@ -76,7 +90,7 @@ runmany, which is as a generalization of parallel list operations. A complete parser for the [semver](http://semver.org/) standard. Including a complete set of conforming comparison functions. -### [ec_lists](https://github.com/ericbmerritt/erlware_commons/blob/master/src/ec_lists.erl) +### [ec_lists](https://github.com/erlware/erlware_commons/blob/master/src/ec_lists.erl) A set of additional list manipulation functions designed to supliment the `lists` module in stdlib. @@ -93,7 +107,7 @@ Other languages, have built in support for **Interface** or **signature** functionality. Java has Interfaces, SML has Signatures. Erlang, though, doesn't currently support this model, at least not directly. There are a few ways you can approximate it. We -have defined a mechnism called *signatures* and several modules that +have defined a mechanism called *signatures* and several modules that to serve as examples and provide a good set of *dictionary* signatures. More information about signatures can be found at [signature](https://github.com/erlware/erlware_commons/blob/master/doc/signatures.md). @@ -110,19 +124,19 @@ This provides an implementation of the ec_dictionary signature using erlang's dicts as a base. The function documentation for ec_dictionary applies here as well. -### [ec_gb_trees](https://github.com/ericbmerritt/erlware_commons/blob/master/src/ec_gb_trees.erl) +### [ec_gb_trees](https://github.com/erlware/erlware_commons/blob/master/src/ec_gb_trees.erl) This provides an implementation of the ec_dictionary signature using erlang's gb_trees as a base. The function documentation for ec_dictionary applies here as well. -### [ec_orddict](https://github.com/ericbmerritt/erlware_commons/blob/master/src/ec_orddict.erl) +### [ec_orddict](https://github.com/erlware/erlware_commons/blob/master/src/ec_orddict.erl) This provides an implementation of the ec_dictionary signature using erlang's orddict as a base. The function documentation for ec_dictionary applies here as well. -### [ec_rbdict](https://github.com/ericbmerritt/erlware_commons/blob/master/src/ec_rbdict.erl) +### [ec_rbdict](https://github.com/erlware/erlware_commons/blob/master/src/ec_rbdict.erl) This provides an implementation of the ec_dictionary signature using Robert Virding's rbdict module as a base. The function documentation diff --git a/doc/signatures.md b/doc/signatures.md index cf95960..cc5fd32 100644 --- a/doc/signatures.md +++ b/doc/signatures.md @@ -2,10 +2,10 @@ Signatures ========== It often occurs in coding that we need a library, a set of -functionaly. Often there are several algorithms that could provide -this functionality. However, the code that uses it, either doesn't +functionalities. Often there are several algorithms that could provide +each of these functionalities. However, the code that uses it, either doesn't care about the individual algorithm or wishes to delegate choosing -that algorithm to some higher level. Lets take the concrete example of +that algorithm to some higher level. Let's take the concrete example of dictionaries. A dictionary provides the ability to access a value via a key (other things as well but primarily this). There are may ways to implement a dictionary. Just a few are: @@ -16,17 +16,17 @@ implement a dictionary. Just a few are: * [Skip Lists](http://en.wikipedia.org/wiki/Skip_list) * Many, many more .... -Each of these approaches has there own performance characteristics, -memory footprints etc. For example, a table of size n with open -addressing has no collisions and holds up to n elements, with a single -comparison for successful lookup, and a table of size n with chaining -and k keys has the minimum max(0, k-n) collisions and O(1 + k/n) +Each of these approaches has their own performance characteristics, +memory footprints, etc. For example, a table of size $n$ with open +addressing has no collisions and holds up to $n$ elements, with a single +comparison for successful lookup, and a table of size $n$ with chaining +and $k$ keys has the minimum $\max(0, k-n)$ collisions and $\mathcal{O}(1 + k/n)$ comparisons for lookup. While for skip lists the performance characteristics are about as good as that of randomly-built binary -search trees - namely (O log n). So the choice of which to select +search trees - namely $\mathcal{O}(\log n)$. So the choice of which to select depends very much on memory available, insert/read characteristics, etc. So delegating the choice to a single point in your code is a very -good idea. Unfortunately, in Erlang thats ot so easy to do at the moment. +good idea. Unfortunately, in Erlang that's so easy to do at the moment. Other languages, have built in support for this functionality. [Java](http://en.wikipedia.org/wiki/Java_(programming_language)) @@ -39,17 +39,20 @@ directly. There are a few ways you can approximate it. One way is to pass the Module name to the calling functions along with the data that it is going to be called on. - :::erlang - add(ModuleToUse, Key, Value, DictData) -> - ModuleToUse:add(Key, Value, DictData). +```erlang +add(ModuleToUse, Key, Value, DictData) -> + ModuleToUse:add(Key, Value, DictData). +``` This works, and you can vary how you want to pass the data. For example, you could easily use a tuple to contain the data. That is, you could pass in `{ModuleToUse, DictData}` and that would make it a bit cleaner. - :::erlang - add(Key, Value, {ModuleToUse, DictData}) -> - ModuleToUse:add(Key, Value, DictData). + +```erlang +add(Key, Value, {ModuleToUse, DictData}) -> + ModuleToUse:add(Key, Value, DictData). +``` Either way, there are a few problems with this approach. One of the biggest is that you lose code locality, by looking at this bit of code @@ -63,21 +66,22 @@ mistakes that you might have made. Tools like [Dialyzer](http://www.erlang.org/doc/man/dialyzer.html) have just as hard a time figuring out the what `ModuleToUse` is pointing to as you do. So they can't give you warnings about potential problems. In fact -someone could inadvertantly pass an unexpected function name as +someone could inadvertently pass an unexpected function name as `ModuleToUse` and you would never get any warnings, just an exception at run time. -Fortunately, Erlang is a pretty flexable language so we can use a +Fortunately, Erlang is a pretty flexible language so we can use a similar approach with a few adjustments to give us the best of both -worlds. Both the flexibiltiy of ignoreing a specific implementation +worlds. Both the flexibility of ignoring a specific implementation and keeping all the nice locality we get by using an explicit module name. So what we actually want to do is something mole like this: - :::erlang - add(Key, Value, DictData) -> - dictionary:add(Key, Value, DictData). +```erlang +add(Key, Value, DictData) -> + dictionary:add(Key, Value, DictData). +``` Doing this we retain the locality. We can easily look up the `dictionary` Module. We immediately have a good idea what a @@ -90,54 +94,56 @@ reasons, this is a much better approach to the problem. This is what Signatures ---------- -How do we actually do this in Erlang now that Erlang is missing what Java, SML and friends has built in? +How do we actually do this in Erlang now that Erlang is missing what Java, SML and friends have built in? The first thing we need to do is to define a [Behaviour](http://metajack.im/2008/10/29/custom-behaviors-in-erlang/) for our functionality. To continue our example we will define a Behaviour for dictionaries. That Behaviour looks like this: - :::erlang - -module(ec_dictionary). +```erlang +-module(ec_dictionary). - -export([behaviour_info/1]). +-export([behaviour_info/1]). - 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(_) -> - 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(_) -> + undefined. +``` So we have our Behaviour now. Unfortunately, this doesn't give us much yet. It will make sure that any dictionaries we write will have all -the functions they need to have, but it wont help use actually use the +the functions they need to have, but it won't help us actually use the dictionaries in an abstract way in our code. To do that we need to add a bit of functionality. We do that by actually implementing our own behaviour, starting with `new/1`. - :::erlang - %% @doc create a new dictionary object from the specified module. The - %% module should implement the dictionary behaviour. - %% - %% @param ModuleName The module name. - -spec new(module()) -> dictionary(_K, _V). - new(ModuleName) when is_atom(ModuleName) -> - #dict_t{callback = ModuleName, data = ModuleName:new()}. +```erlang +%% @doc create a new dictionary object from the specified module. The +%% module should implement the dictionary behaviour. +%% +%% @param ModuleName The module name. +-spec new(module()) -> dictionary(_K, _V). +new(ModuleName) when is_atom(ModuleName) -> + #dict_t{callback = ModuleName, data = ModuleName:new()}. +``` This code creates a new dictionary for us. Or to be more specific it actually creates a new dictionary Signature record, that will be used subsequently in other calls. This might look a bit familiar from our previous less optimal approach. We have both the module name and the -data. here in the record. We call the module name named in +data in the record. We call the module name named in `ModuleName` to create the initial data. We then construct the record and return that record to the caller and we have a new dictionary. What about the other functions, the ones that don't create @@ -148,16 +154,17 @@ dictionary and another that just retrieves data. The first we will look at is the one that updates the dictionary by adding a value. - :::erlang - %% @doc add a new value to the existing dictionary. Return a new - %% dictionary containing the value. - %% - %% @param Dict the dictionary object to add too - %% @param Key the key to add - %% @param Value the value to add - -spec add(key(K), value(V), dictionary(K, V)) -> dictionary(K, V). - add(Key, Value, #dict_t{callback = Mod, data = Data} = Dict) -> - Dict#dict_t{data = Mod:add(Key, Value, Data)}. +```erlang +%% @doc add a new value to the existing dictionary. Return a new +%% dictionary containing the value. +%% +%% @param Dict the dictionary object to add too +%% @param Key the key to add +%% @param Value the value to add +-spec add(key(K), value(V), dictionary(K, V)) -> dictionary(K, V). +add(Key, Value, #dict_t{callback = Mod, data = Data} = Dict) -> + Dict#dict_t{data = Mod:add(Key, Value, Data)}. +``` There are two key things here. @@ -173,16 +180,17 @@ implementation to do the work itself. Now lets do a data retrieval function. In this case, the `get` function of the dictionary Signature. - :::erlang - %% @doc given a key return that key from the dictionary. If the key is - %% not found throw a 'not_found' exception. - %% - %% @param Dict The dictionary object to return the value from - %% @param Key The key requested - %% @throws not_found when the key does not exist - -spec get(key(K), dictionary(K, V)) -> value(V). - get(Key, #dict_t{callback = Mod, data = Data}) -> - Mod:get(Key, Data). +```erlang +%% @doc given a key return that key from the dictionary. If the key is +%% not found throw a 'not_found' exception. +%% +%% @param Dict The dictionary object to return the value from +%% @param Key The key requested +%% @throws not_found when the key does not exist +-spec get(key(K), dictionary(K, V)) -> value(V). +get(Key, #dict_t{callback = Mod, data = Data}) -> + Mod:get(Key, Data). +``` In this case, you can see a very similar approach to deconstructing the dict record. We still need to pull out the callback module and the @@ -197,7 +205,7 @@ implementation in Using Signatures ---------------- -Its a good idea to work through an example so we have a bit better +It's a good idea to work through an example so we have a bit better idea of how to use these Signatures. If you are like me, you probably have some questions about what kind of performance burden this places on the code. At the very least we have an additional function call @@ -206,7 +214,7 @@ lets write a little timing test, so we can get a good idea of how much this is all costing us. In general, there are two kinds of concrete implementations for -Signatures. The first is a native implementations, the second is a +Signatures. The first is a native implementation, the second is a wrapper. ### Native Signature Implementations @@ -223,32 +231,33 @@ implements the ec_dictionary module directly. A Signature Wrapper is a module that wraps another module. Its purpose is to help a preexisting module implement the Behaviour -defined by a Signature. A good example if this in our current example +defined by a Signature. A good example of this in our current example is the [erlware_commons/ec_dict](https://github.com/ericbmerritt/erlware_commons/blob/types/src/ec_dict.erl) -module. It implements the ec_dictionary Behaviour, but all the +module. It implements the `ec_dictionary` Behaviour, but all the functionality is provided by the [stdlib/dict](http://www.erlang.org/doc/man/dict.html) module -itself. Lets take a look at one example to see how this is done. +itself. Let's take a look at one example to see how this is done. We will take a look at one of the functions we have already seen. The -`get` function an ec_dictionary `get` doesn't have quite the same -semantics as any of the functions in the dict module. So a bit of -translation needs to be done. We do that in the ec_dict module `get` function. +`get` function in `ec_dictionary` doesn't have quite the same +semantics as any of the functions in the `dict` module. So a bit of +translation needs to be done. We do that in the `ec_dict:get/2` function. - :::erlang - -spec get(ec_dictionary:key(K), Object::dictionary(K, V)) -> - ec_dictionary:value(V). - get(Key, Data) -> - case dict:find(Key, Data) of - {ok, Value} -> - Value; - error -> - throw(not_found) - end. +```erlang +-spec get(ec_dictionary:key(K), Object::dictionary(K, V)) -> + ec_dictionary:value(V). +get(Key, Data) -> + case dict:find(Key, Data) of + {ok, Value} -> + Value; + error -> + throw(not_found) + end. +``` -So the ec_dict module's purpose for existence is to help the -preexisting dict module implement the Behaviour defined by the +So the `ec_dict` module's purpose for existence is to help the +preexisting `dict` module implement the Behaviour defined by the Signature. @@ -258,24 +267,25 @@ the mix and that adds a bit of additional overhead. ### Creating the Timing Module -We are going to creating timings for both Native Signature +We are going to be creating timings for both Native Signature Implementations and Signature Wrappers. -Lets get started by looking at some helper functions. We want -dictionaries to have a bit of data in them. So to that end we are will +Let's get started by looking at some helper functions. We want +dictionaries to have a bit of data in them. So to that end we will create a couple of functions that create dictionaries for each type we want to test. The first we want to time is the Signature Wrapper, so `dict` vs `ec_dict` called as a Signature. - :::erlang - create_dict() -> +```erlang +create_dict() -> lists:foldl(fun(El, Dict) -> - dict:store(El, El, Dict) - end, dict:new(), - lists:seq(1,100)). + dict:store(El, El, Dict) + end, dict:new(), + lists:seq(1,100)). +``` The only thing we do here is create a sequence of numbers 1 to 100, -and then add each of those to the dict as an entry. We aren't too +and then add each of those to the `dict` as an entry. We aren't too worried about replicating real data in the dictionary. We care about timing the function call overhead of Signatures, not the performance of the dictionaries themselves. @@ -283,58 +293,61 @@ of the dictionaries themselves. We need to create a similar function for our Signature based dictionary `ec_dict`. - :::erlang - create_dictionary(Type) -> +```erlang +create_dictionary(Type) -> lists:foldl(fun(El, Dict) -> - ec_dictionary:add(El, El, Dict) - end, - ec_dictionary:new(Type), - lists:seq(1,100)). + ec_dictionary:add(El, El, Dict) + end, + ec_dictionary:new(Type), + lists:seq(1,100)). +``` Here we actually create everything using the Signature. So we don't need one function for each type. We can have one function that can create anything that implements the Signature. That is the magic of -Signatures. Otherwise, this does the exact same thing as the dict -`create_dict/1`. +Signatures. Otherwise, this does the exact same thing as the dictionary +given by `create_dict/0`. We are going to use two function calls in our timing. One that updates data and one that returns data, just to get good coverage. For our -dictionaries that we are going to use the `size` function as well as +dictionaries we are going to use the `size` function as well as the `add` function. - :::erlang - time_direct_vs_signature_dict() -> - io:format("Timing dict~n"), - Dict = create_dict(), - test_avg(fun() -> - dict:size(dict:store(some_key, some_value, Dict)) - end, - 1000000), - io:format("Timing ec_dict implementation of ec_dictionary~n"), - time_dict_type(ec_dict). +```erlang +time_direct_vs_signature_dict() -> + io:format("Timing dict~n"), + Dict = create_dict(), + test_avg(fun() -> + dict:size(dict:store(some_key, some_value, Dict)) + end, + 1000000), + io:format("Timing ec_dict implementation of ec_dictionary~n"), + time_dict_type(ec_dict). +``` The `test_avg` function runs the provided function the number of times specified in the second argument and collects timing information. We -are going to run these one million times to get a good average (its -fast so it doesn't take long). You can see that in the anonymous +are going to run these one million times to get a good average (it's +fast so it doesn't take long). You can see in the anonymous function that we directly call `dict:size/1` and `dict:store/3` to perform the test. However, because we are in the wonderful world of Signatures we don't have to hard code the calls for the Signature implementations. Lets take a look at the `time_dict_type` function. - :::erlang - time_dict_type(Type) -> - io:format("Testing ~p~n", [Type]), - Dict = create_dictionary(Type), - test_avg(fun() -> - ec_dictionary:size(ec_dictionary:add(some_key, some_value, Dict)) - end, - 1000000). +```erlang +time_dict_type(Type) -> + io:format("Testing ~p~n", [Type]), + Dict = create_dictionary(Type), + test_avg(fun() -> + ec_dictionary:size(ec_dictionary:add(some_key, some_value, Dict)) + end, + 1000000). +``` As you can see we take the type as an argument (we need it for `dict` creation) and call our create function. Then we run the same timings -that we did for ec dict. In this case though, the type of dictionary +that we did for `ec_dict`. In this case though, the type of dictionary is never specified, we only ever call ec_dictionary, so this test will work for anything that implements that Signature. @@ -343,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 what it looked like. - :::sh - Erlang R14B01 (erts-5.8.2) [source] [64-bit] [smp:4:4] [rq:4] [async-threads:0] [hipe] [kernel-poll:false] +```sh +Erlang R14B01 (erts-5.8.2) [source] [64-bit] [smp:4:4] [rq:4] [async-threads:0] [hipe] [kernel-poll:false] - Eshell V5.8.2 (abort with ^G) +Eshell V5.8.2 (abort with ^G) - 1> ec_timing:time_direct_vs_signature_dict(). - Timing dict - Range: 2 - 5621 mics - Median: 3 mics - Average: 3 mics - Timing ec_dict implementation of ec_dictionary - Testing ec_dict - Range: 3 - 6097 mics - Median: 3 mics - Average: 4 mics - 2> +1> ec_timing:time_direct_vs_signature_dict(). +Timing dict +Range: 2 - 5621 mics +Median: 3 mics +Average: 3 mics +Timing ec_dict implementation of ec_dictionary +Testing ec_dict +Range: 3 - 6097 mics +Median: 3 mics +Average: 4 mics +2> +``` -So for the direct dict call, we average about 3 mics per call, while -for the Signature Wrapper we average around 4. Thats a 25% cost for +So for the direct `dict` call, we average about 3 mics per call, while +for the Signature Wrapper we average around 4. That's a 25% cost for Signature Wrappers in this example, for a very small number of calls. Depending on what you are doing that is going to be greater or lesser. In any case, we can see that there is some cost associated @@ -373,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 to create a creation helper just like we did for dict. - :::erlang - create_rbdict() -> +```erlang +create_rbdict() -> lists:foldl(fun(El, Dict) -> - ec_rbdict:add(El, El, Dict) - end, ec_rbdict:new(), - lists:seq(1,100)). + ec_rbdict:add(El, El, Dict) + end, ec_rbdict:new(), + lists:seq(1,100)). +``` This is exactly the same as `create_dict` with the exception that dict is replaced by `ec_rbdict`. The timing function itself looks very similar as well. Again notice that we have to hard code the concrete name for the concrete -implementation, but we don't for the ec_dictionary test. +implementation, but we don't for the `ec_dictionary` test. - :::erlang - time_direct_vs_signature_rbdict() -> - io:format("Timing rbdict~n"), - Dict = create_rbdict(), - test_avg(fun() -> - ec_rbdict:size(ec_rbdict:add(some_key, some_value, Dict)) - end, - 1000000), - io:format("Timing ec_dict implementation of ec_dictionary~n"), - time_dict_type(ec_rbdict). +```erlang +time_direct_vs_signature_rbdict() -> + io:format("Timing rbdict~n"), + Dict = create_rbdict(), + test_avg(fun() -> + ec_rbdict:size(ec_rbdict:add(some_key, some_value, Dict)) + end, + 1000000), + io:format("Timing ec_dict implementation of ec_dictionary~n"), + time_dict_type(ec_rbdict). +``` And there we have our test. What do the results look like? @@ -406,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 results. - :::sh - Erlang R14B01 (erts-5.8.2) [source] [64-bit] [smp:4:4] [rq:4] [async-threads:0] [hipe] [kernel-poll:false] +```sh +Erlang R14B01 (erts-5.8.2) [source] [64-bit] [smp:4:4] [rq:4] [async-threads:0] [hipe] [kernel-poll:false] - Eshell V5.8.2 (abort with ^G) +Eshell V5.8.2 (abort with ^G) - 1> ec_timing:time_direct_vs_signature_rbdict(). - Timing rbdict - Range: 6 - 15070 mics - Median: 7 mics - Average: 7 mics - Timing ec_dict implementation of ec_dictionary - Testing ec_rbdict - Range: 6 - 6013 mics - Median: 7 mics - Average: 7 mics - 2> +1> ec_timing:time_direct_vs_signature_rbdict(). +Timing rbdict +Range: 6 - 15070 mics +Median: 7 mics +Average: 7 mics +Timing ec_dict implementation of ec_dictionary +Testing ec_rbdict +Range: 6 - 6013 mics +Median: 7 mics +Average: 7 mics +2> +``` So no difference it time. Well the reality is that there is a difference in timing, there must be, but we don't have enough resolution in the timing system to be able to figure out what that -difference is. Essentially that means its really, really small - or small +difference is. Essentially that means it's really, really small - or small enough not to worry about at the very least. Conclusion ---------- Signatures are a viable, useful approach to the problem of interfaces -in Erlang. The have little or no over head depending on the type of +in Erlang. They have little or no overhead depending on the type of implementation, and greatly increase the flexibility of the a library while retaining testability and locality. @@ -456,7 +473,7 @@ Signature Wrapper ### Code Referenced -* [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_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_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_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) diff --git a/rebar.config b/rebar.config index e269132..017e3fe 100644 --- a/rebar.config +++ b/rebar.config @@ -8,16 +8,7 @@ {erl_first_files, ["ec_dictionary", "ec_vsn"]}. %% Compiler Options ============================================================ -{erl_opts, - [{platform_define, "^[0-9]+", namespaced_types}, - {platform_define, "^[0-9]+", have_callback_support}, - {platform_define, "^R1[4|5]", deprecated_crypto}, - {platform_define, "^1[8|9]", rand_module}, - {platform_define, "^2", rand_module}, - {platform_define, "^2", unicode_str}, - {platform_define, "^(R|1|20)", fun_stacktrace}, - debug_info, - warnings_as_errors]}. +{erl_opts, [debug_info, warnings_as_errors]}. %% EUnit ======================================================================= {eunit_opts, [verbose, diff --git a/rebar.config.script b/rebar.config.script index 9c1ed91..cc054a8 100644 --- a/rebar.config.script +++ b/rebar.config.script @@ -1,17 +1,7 @@ -IsRebar3 = case application:get_key(rebar, vsn) of - {ok, Vsn} -> - [MajorVersion|_] = string:tokens(Vsn, "."), - (list_to_integer(MajorVersion) >= 3); - undefined -> - false - end, +NoDialWarns = {dialyzer, [{warnings, [no_unknown]}]}, +OTPRelease = erlang:list_to_integer(erlang:system_info(otp_release)), -Rebar2Deps = [ - {cf, ".*", {git, "https://github.com/project-fifo/cf", {tag, "0.2.2"}}} - ], - -case IsRebar3 of - true -> CONFIG; - false -> - lists:keyreplace(deps, 1, CONFIG, {deps, Rebar2Deps}) +case OTPRelease<26 of + true -> CONFIG; + false -> lists:keystore(dialyzer, 1, CONFIG, NoDialWarns) end. diff --git a/rebar.lock b/rebar.lock index f39d00d..7873d25 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,6 +1,8 @@ -{"1.1.0", +{"1.2.0", [{<<"cf">>,{pkg,<<"cf">>,<<"0.3.1">>},0}]}. [ {pkg_hash,[ - {<<"cf">>, <<"5CB902239476E141EA70A740340233782D363A31EEA8AD37049561542E6CD641">>}]} + {<<"cf">>, <<"5CB902239476E141EA70A740340233782D363A31EEA8AD37049561542E6CD641">>}]}, +{pkg_hash_ext,[ + {<<"cf">>, <<"315E8D447D3A4B02BCDBFA397AD03BBB988A6E0AA6F44D3ADD0F4E3C3BF97672">>}]} ]. diff --git a/src/ec_cmd_log.erl b/src/ec_cmd_log.erl index b61bbd0..a1f2713 100644 --- a/src/ec_cmd_log.erl +++ b/src/ec_cmd_log.erl @@ -19,9 +19,12 @@ %%% @copyright (C) 2012 Erlware, LLC. %%% %%% @doc This provides simple output functions for command line apps. You should -%%% use this to talk to the users if you are wrting code for the system +%%% use this to talk to the users if you are writing code for the system -module(ec_cmd_log). +%% Avoid clashing with `error/3` BIF added in Erlang/OTP 24 +-compile({no_auto_import,[error/3]}). + -export([new/1, new/2, new/3, @@ -37,22 +40,17 @@ warn/3, log_level/1, atom_log_level/1, + colorize/4, format/1]). --include("ec_cmd_log.hrl"). - --define(RED, $r). --define(GREEN, $g). --define(YELLOW, $y). --define(BLUE, $b). --define(MAGENTA, $m). --define(CYAN, $c). +-include("include/ec_cmd_log.hrl"). +-include("src/ec_cmd_log.hrl"). -define(PREFIX, "===> "). -record(state_t, {log_level=0 :: int_log_level(), caller=api :: caller(), - intensity=low :: none | low | high}). + intensity=low :: intensity()}). %%============================================================================ %% types @@ -123,10 +121,10 @@ debug(LogState, Fun) colorize(LogState, ?CYAN, false, Fun()) end); debug(LogState, String) -> - debug(LogState, "~s~n", [String]). + debug(LogState, "~ts~n", [String]). %% @doc log at the debug level given the current log state with a format string -%% and argements @see io:format/2 +%% and arguments @see io:format/2 -spec debug(t(), string(), [any()]) -> ok. debug(LogState, FormatString, Args) -> log(LogState, ?EC_DEBUG, colorize(LogState, ?CYAN, false, FormatString), Args). @@ -140,10 +138,10 @@ info(LogState, Fun) colorize(LogState, ?GREEN, false, Fun()) end); info(LogState, String) -> - info(LogState, "~s~n", [String]). + info(LogState, "~ts~n", [String]). %% @doc log at the info level given the current log state with a format string -%% and argements @see io:format/2 +%% and arguments @see io:format/2 -spec info(t(), string(), [any()]) -> ok. info(LogState, FormatString, Args) -> log(LogState, ?EC_INFO, colorize(LogState, ?GREEN, false, FormatString), Args). @@ -157,10 +155,10 @@ error(LogState, Fun) colorize(LogState, ?RED, false, Fun()) end); error(LogState, String) -> - error(LogState, "~s~n", [String]). + error(LogState, "~ts~n", [String]). %% @doc log at the error level given the current log state with a format string -%% and argements @see io:format/2 +%% and arguments @see io:format/2 -spec error(t(), string(), [any()]) -> ok. error(LogState, FormatString, Args) -> log(LogState, ?EC_ERROR, colorize(LogState, ?RED, false, FormatString), Args). @@ -172,10 +170,10 @@ warn(LogState, Fun) when erlang:is_function(Fun) -> log(LogState, ?EC_WARN, fun() -> colorize(LogState, ?MAGENTA, false, Fun()) end); warn(LogState, String) -> - warn(LogState, "~s~n", [String]). + warn(LogState, "~ts~n", [String]). %% @doc log at the warn level given the current log state with a format string -%% and argements @see io:format/2 +%% and arguments @see io:format/2 -spec warn(t(), string(), [any()]) -> ok. warn(LogState, FormatString, Args) -> log(LogState, ?EC_WARN, colorize(LogState, ?MAGENTA, false, FormatString), Args). @@ -184,7 +182,7 @@ warn(LogState, FormatString, Args) -> -spec log(t(), int_log_level(), log_fun()) -> ok. log(#state_t{log_level=DetailLogLevel}, LogLevel, Fun) when DetailLogLevel >= LogLevel -> - io:format("~s~n", [Fun()]); + io:format("~ts~n", [Fun()]); log(_, _, _) -> ok. @@ -240,61 +238,20 @@ format(Log) -> colorize(#state_t{intensity=none}, _, _, Msg) -> Msg; -%% When it is suposed to be bold and we already have a uppercase +%% When it is supposed to be bold and we already have a uppercase %% (bold color) we don't need to modify the color colorize(State, Color, true, Msg) when ?VALID_COLOR(Color), Color >= $A, Color =< $Z -> colorize(State, Color, false, Msg); -%% We're sneaky we can substract 32 to get the uppercase character if we want +%% We're sneaky we can subtract 32 to get the uppercase character if we want %% bold but have a non bold color. colorize(State, Color, true, Msg) when ?VALID_COLOR(Color) -> colorize(State, Color - 32, false, Msg); colorize(#state_t{caller=command_line, intensity = high}, Color, false, Msg) when ?VALID_COLOR(Color) -> - lists:flatten(cf:format("~!" ++ [Color] ++"~s~s", [?PREFIX, Msg])); + lists:flatten(cf:format("~!" ++ [Color] ++"~ts~ts", [?PREFIX, Msg])); colorize(#state_t{caller=command_line, intensity = low}, Color, false, Msg) when ?VALID_COLOR(Color) -> - lists:flatten(cf:format("~!" ++ [Color] ++"~s~!!~s", [?PREFIX, Msg])); + lists:flatten(cf:format("~!" ++ [Color] ++"~ts~!!~ts", [?PREFIX, Msg])); colorize(_LogState, _Color, _Bold, Msg) -> Msg. - -%%%=================================================================== -%%% Test Functions -%%%=================================================================== - --ifdef(TEST). --include_lib("eunit/include/eunit.hrl"). - -should_test() -> - ErrorLogState = new(error), - ?assertMatch(true, should(ErrorLogState, ?EC_ERROR)), - ?assertMatch(true, not should(ErrorLogState, ?EC_INFO)), - ?assertMatch(true, not should(ErrorLogState, ?EC_DEBUG)), - ?assertEqual(?EC_ERROR, log_level(ErrorLogState)), - ?assertEqual(error, atom_log_level(ErrorLogState)), - - InfoLogState = new(info), - ?assertMatch(true, should(InfoLogState, ?EC_ERROR)), - ?assertMatch(true, should(InfoLogState, ?EC_INFO)), - ?assertMatch(true, not should(InfoLogState, ?EC_DEBUG)), - ?assertEqual(?EC_INFO, log_level(InfoLogState)), - ?assertEqual(info, atom_log_level(InfoLogState)), - - DebugLogState = new(debug), - ?assertMatch(true, should(DebugLogState, ?EC_ERROR)), - ?assertMatch(true, should(DebugLogState, ?EC_INFO)), - ?assertMatch(true, should(DebugLogState, ?EC_DEBUG)), - ?assertEqual(?EC_DEBUG, log_level(DebugLogState)), - ?assertEqual(debug, atom_log_level(DebugLogState)). - - -no_color_test() -> - LogState = new(debug, command_line, none), - ?assertEqual("test", - colorize(LogState, ?RED, true, "test")). - -color_test() -> - LogState = new(debug, command_line, high), - ?assertEqual("\e[1;31m===> test\e[0m", - colorize(LogState, ?RED, true, "test")). --endif. diff --git a/src/ec_cmd_log.hrl b/src/ec_cmd_log.hrl new file mode 100644 index 0000000..428fd74 --- /dev/null +++ b/src/ec_cmd_log.hrl @@ -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). diff --git a/src/ec_cnv.erl b/src/ec_cnv.erl index 72700fb..bc3b3f3 100644 --- a/src/ec_cnv.erl +++ b/src/ec_cnv.erl @@ -212,36 +212,3 @@ to_atom(X) erlang:list_to_existing_atom(X); to_atom(X) -> to_atom(to_list(X)). - -%%%=================================================================== -%%% Tests -%%%=================================================================== - --ifdef(TEST). --include_lib("eunit/include/eunit.hrl"). - -to_integer_test() -> - ?assertError(badarg, to_integer(1.5, strict)). - -to_float_test() -> - ?assertError(badarg, to_float(10, strict)). - -to_atom_test() -> - ?assertMatch(true, to_atom("true")), - ?assertMatch(true, to_atom(<<"true">>)), - ?assertMatch(false, to_atom(<<"false">>)), - ?assertMatch(false, to_atom(false)), - ?assertError(badarg, to_atom("hello_foo_bar_baz")), - - S = erlang:list_to_atom("1"), - ?assertMatch(S, to_atom(1)). - -to_boolean_test()-> - ?assertMatch(true, to_boolean(<<"true">>)), - ?assertMatch(true, to_boolean("true")), - ?assertMatch(true, to_boolean(true)), - ?assertMatch(false, to_boolean(<<"false">>)), - ?assertMatch(false, to_boolean("false")), - ?assertMatch(false, to_boolean(false)). - --endif. diff --git a/src/ec_compile.erl b/src/ec_compile.erl index 6c15520..7199610 100644 --- a/src/ec_compile.erl +++ b/src/ec_compile.erl @@ -38,7 +38,7 @@ beam_to_erl_source(BeamFName, ErlFName) -> Src = erl_prettypr:format(erl_syntax:form_list(tl(Forms))), {ok, Fd} = file:open(ErlFName, [write]), - io:fwrite(Fd, "~s~n", [Src]), + io:fwrite(Fd, "~ts~n", [Src]), file:close(Fd); Error -> Error diff --git a/src/ec_date.erl b/src/ec_date.erl index 97bcea3..747b246 100644 --- a/src/ec_date.erl +++ b/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_tz_offset(H1,H2,M1,M2), (?is_num(H1) andalso ?is_num(H2) andalso ?is_num(M1) andalso ?is_num(M2)) ). --define(GREGORIAN_SECONDS_1970, 62167219200). --define(ISO_8601_DATETIME_FORMAT, "Y-m-dTG:i:sZ"). --define(ISO_8601_DATETIME_WITH_MS_FORMAT, "Y-m-dTG:i:s.fZ"). +-define(GREGORIAN_SECONDS_1970, 62_167_219_200). +-define(ISO_8601_DATETIME_FORMAT, "Y-m-dTH:i:sZ"). +-define(ISO_8601_DATETIME_WITH_MS_FORMAT, "Y-m-dTH:i:s.fZ"). -type year() :: non_neg_integer(). -type month() :: 1..12 | {?MONTH_TAG, 1..12}. @@ -54,7 +54,7 @@ -type hour() :: 0..23. -type minute() :: 0..59. -type second() :: 0..59. --type microsecond() :: 0..999999. +-type microsecond() :: 0..999_999. -type daynum() :: 1..7. -type date() :: {year(),month(),day()}. @@ -101,7 +101,7 @@ parse(Date, Now) -> do_parse(Date, Now, []). do_parse(Date, Now, Opts) -> - case filter_hints(parse(tokenise(uppercase(Date), []), Now, Opts)) of + case filter_hints(parse(tokenise(string:uppercase(Date), []), Now, Opts)) of {error, bad_date} -> erlang:throw({?MODULE, {bad_date, Date}}); {D1, T1} = {{Y, M, D}, {H, M1, S}} @@ -138,11 +138,11 @@ nparse(Date) -> {DateS, {H, M, S, Ms} } -> GSeconds = calendar:datetime_to_gregorian_seconds({DateS, {H, M, S} }), ESeconds = GSeconds - ?GREGORIAN_SECONDS_1970, - {ESeconds div 1000000, ESeconds rem 1000000, Ms}; + {ESeconds div 1_000_000, ESeconds rem 1_000_000, Ms}; DateTime -> GSeconds = calendar:datetime_to_gregorian_seconds(DateTime), ESeconds = GSeconds - ?GREGORIAN_SECONDS_1970, - {ESeconds div 1000000, ESeconds rem 1000000, 0} + {ESeconds div 1_000_000, ESeconds rem 1_000_000, 0} end. %% @@ -151,7 +151,7 @@ nparse(Date) -> parse([Year, X, Month, X, Day, Hour, $:, Min, $:, Sec, $., Micros, $Z ], _Now, _Opts) when ?is_world_sep(X) - andalso (Micros >= 0 andalso Micros < 1000000) + andalso (Micros >= 0 andalso Micros < 1_000_000) andalso Year > 31 -> {{Year, Month, Day}, {hour(Hour, []), Min, Sec}, {Micros}}; @@ -162,7 +162,7 @@ parse([Year, X, Month, X, Day, Hour, $:, Min, $:, Sec, $Z ], _Now, _Opts) parse([Year, X, Month, X, Day, Hour, $:, Min, $:, Sec, $., Micros, $+, Off | _Rest ], _Now, _Opts) when (?is_us_sep(X) orelse ?is_world_sep(X)) - andalso (Micros >= 0 andalso Micros < 1000000) + andalso (Micros >= 0 andalso Micros < 1_000_000) andalso Year > 31 -> {{Year, Month, Day}, {hour(Hour, []) - Off, Min, Sec}, {Micros}}; @@ -173,7 +173,7 @@ parse([Year, X, Month, X, Day, Hour, $:, Min, $:, Sec, $+, Off | _Rest ], _Now, parse([Year, X, Month, X, Day, Hour, $:, Min, $:, Sec, $., Micros, $-, Off | _Rest ], _Now, _Opts) when (?is_us_sep(X) orelse ?is_world_sep(X)) - andalso (Micros >= 0 andalso Micros < 1000000) + andalso (Micros >= 0 andalso Micros < 1_000_000) andalso Year > 31 -> {{Year, Month, Day}, {hour(Hour, []) + Off, Min, Sec}, {Micros}}; @@ -197,17 +197,6 @@ parse([Day,X,Month,X,Year,Hour,$:,Min,$:,Sec,$., Ms | PAM], _Now, _Opts) andalso ?is_year(Year) -> {{Year, Month, Day}, {hour(Hour, PAM), Min, Sec}, {Ms}}; -parse([Year,X,Month,X,Day,Hour,$:,Min,$:,Sec,$., Ms], _Now, _Opts) - when (?is_us_sep(X) orelse ?is_world_sep(X)) - andalso ?is_year(Year) -> - {{Year, Month, Day}, {hour(Hour,[]), Min, Sec}, {Ms}}; -parse([Month,X,Day,X,Year,Hour,$:,Min,$:,Sec,$., Ms], _Now, _Opts) - when ?is_us_sep(X) andalso ?is_month(Month) -> - {{Year, Month, Day}, {hour(Hour, []), Min, Sec}, {Ms}}; -parse([Day,X,Month,X,Year,Hour,$:,Min,$:,Sec,$., Ms ], _Now, _Opts) - when ?is_world_sep(X) andalso ?is_month(Month) -> - {{Year, Month, Day}, {hour(Hour, []), Min, Sec}, {Ms}}; - %% Date/Times Dec 1st, 2012 6:25 PM parse([Month,Day,Year,Hour,$:,Min,$:,Sec | PAM], _Now, _Opts) when ?is_meridian(PAM) andalso ?is_hinted_month(Month) andalso ?is_day(Day) -> @@ -219,14 +208,6 @@ parse([Month,Day,Year,Hour | PAM], _Now, _Opts) when ?is_meridian(PAM) andalso ?is_hinted_month(Month) andalso ?is_day(Day) -> {{Year, Month, Day}, {hour(Hour, PAM), 0, 0}}; -%% Date/Times Dec 1st, 2012 18:25:15 (no AM/PM) -parse([Month,Day,Year,Hour,$:,Min,$:,Sec], _Now, _Opts) - when ?is_hinted_month(Month) andalso ?is_day(Day) -> - {{Year, Month, Day}, {hour(Hour, []), Min, Sec}}; -parse([Month,Day,Year,Hour,$:,Min], _Now, _Opts) - when ?is_hinted_month(Month) andalso ?is_day(Day) -> - {{Year, Month, Day}, {hour(Hour, []), Min, 0}}; - %% Date/Times Fri Nov 21 14:55:26 +0000 2014 (Twitter format) parse([Month, Day, Hour,$:,Min,$:,Sec, Year], _Now, _Opts) when ?is_hinted_month(Month), ?is_day(Day), ?is_year(Year) -> @@ -335,11 +316,11 @@ tokenise([$., N1, N2, N3, N4 | Rest], Acc) when ?is_num(N1), ?is_num(N2), ?is_num(N3), ?is_num(N4) -> tokenise(Rest, [ ltoi([N1, N2, N3, N4]) * 100, $. | Acc]); tokenise([$., N1, N2, N3 | Rest], Acc) when ?is_num(N1), ?is_num(N2), ?is_num(N3) -> - tokenise(Rest, [ ltoi([N1, N2, N3]) * 1000, $. | Acc]); + tokenise(Rest, [ ltoi([N1, N2, N3]) * 1_000, $. | Acc]); tokenise([$., N1, N2 | Rest], Acc) when ?is_num(N1), ?is_num(N2) -> - tokenise(Rest, [ ltoi([N1, N2]) * 10000, $. | Acc]); + tokenise(Rest, [ ltoi([N1, N2]) * 10_000, $. | Acc]); tokenise([$., N1 | Rest], Acc) when ?is_num(N1) -> - tokenise(Rest, [ ltoi([N1]) * 100000, $. | Acc]); + tokenise(Rest, [ ltoi([N1]) * 100_000, $. | Acc]); tokenise([N1, N2, N3, N4, N5, N6 | Rest], Acc) when ?is_num(N1), ?is_num(N2), ?is_num(N3), ?is_num(N4), ?is_num(N5), ?is_num(N6) -> @@ -522,7 +503,7 @@ format([$g|T], {_,{H,_,_}}=Dt, Acc) when H > 12 -> format([$g|T], {_,{H,_,_}}=Dt, Acc) -> format(T, Dt, [itol(H)|Acc]); format([$G|T], {_,{H,_,_}}=Dt, Acc) -> - format(T, Dt, [pad2(H)|Acc]); + format(T, Dt, [itol(H)|Acc]); format([$h|T], {_,{H,_,_}}=Dt, Acc) when H > 12 -> format(T, Dt, [pad2(H-12)|Acc]); format([$h|T], {_,{H,_,_}}=Dt, Acc) -> @@ -728,12 +709,6 @@ pad6(X) when is_integer(X) -> ltoi(X) -> list_to_integer(X). --ifdef(unicode_str). -uppercase(Str) -> string:uppercase(Str). --else. -uppercase(Str) -> string:to_upper(Str). --endif. - %%%=================================================================== %%% Tests %%%=================================================================== @@ -743,7 +718,7 @@ uppercase(Str) -> string:to_upper(Str). -define(DATE, {{2001,3,10},{17,16,17}}). --define(DATEMS, {{2001,3,10},{17,16,17,123456}}). +-define(DATEMS, {{2001,3,10},{17,16,17,123_456}}). -define(DATE_NOON, {{2001,3,10},{12,0,0}}). -define(DATE_MIDNIGHT, {{2001,3,10},{0,0,0}}). -define(ISO, "o \\WW"). @@ -762,6 +737,8 @@ basic_format_test_() -> ?_assertEqual(format("H:i:s",?DATE), "17:16:17"), ?_assertEqual(format("z",?DATE), "68"), ?_assertEqual(format("D M j G:i:s Y",?DATE), "Sat Mar 10 17:16:17 2001"), + ?_assertEqual(format("D M j G:i:s Y", {{2001,3,10},{5,16,17}}), "Sat Mar 10 5:16:17 2001"), + ?_assertEqual(format("D M j H:i:s Y", {{2001,3,10},{5,16,17}}), "Sat Mar 10 05:16:17 2001"), ?_assertEqual(format("ga",?DATE_NOON), "12pm"), ?_assertEqual(format("gA",?DATE_NOON), "12PM"), ?_assertEqual(format("ga",?DATE_MIDNIGHT), "12am"), @@ -978,7 +955,7 @@ ms_test_() -> Now=os:timestamp(), [ ?_assertEqual({{2012,12,12}, {12,12,12,1234}}, parse("2012-12-12T12:12:12.001234")), - ?_assertEqual({{2012,12,12}, {12,12,12,123000}}, parse("2012-12-12T12:12:12.123")), + ?_assertEqual({{2012,12,12}, {12,12,12,123_000}}, parse("2012-12-12T12:12:12.123")), ?_assertEqual(format("H:m:s.f \\m \\i\\s \\m\\o\\n\\t\\h",?DATEMS), "17:03:17.123456 m is month"), ?_assertEqual(format("Y-m-d\\TH:i:s.f",?DATEMS), @@ -1017,21 +994,21 @@ format_iso8601_test_() -> ?_assertEqual("2001-03-10T17:16:17.000000Z", format_iso8601({{2001,3,10},{17,16,17,0}})), ?_assertEqual("2001-03-10T17:16:17.100000Z", - format_iso8601({{2001,3,10},{17,16,17,100000}})), + format_iso8601({{2001,3,10},{17,16,17,100_000}})), ?_assertEqual("2001-03-10T17:16:17.120000Z", - format_iso8601({{2001,3,10},{17,16,17,120000}})), + format_iso8601({{2001,3,10},{17,16,17,120_000}})), ?_assertEqual("2001-03-10T17:16:17.123000Z", - format_iso8601({{2001,3,10},{17,16,17,123000}})), + format_iso8601({{2001,3,10},{17,16,17,123_000}})), ?_assertEqual("2001-03-10T17:16:17.123400Z", - format_iso8601({{2001,3,10},{17,16,17,123400}})), + format_iso8601({{2001,3,10},{17,16,17,123_400}})), ?_assertEqual("2001-03-10T17:16:17.123450Z", - format_iso8601({{2001,3,10},{17,16,17,123450}})), + format_iso8601({{2001,3,10},{17,16,17,123_450}})), ?_assertEqual("2001-03-10T17:16:17.123456Z", - format_iso8601({{2001,3,10},{17,16,17,123456}})), + format_iso8601({{2001,3,10},{17,16,17,123_456}})), ?_assertEqual("2001-03-10T17:16:17.023456Z", - format_iso8601({{2001,3,10},{17,16,17,23456}})), + format_iso8601({{2001,3,10},{17,16,17,23_456}})), ?_assertEqual("2001-03-10T17:16:17.003456Z", - format_iso8601({{2001,3,10},{17,16,17,3456}})), + format_iso8601({{2001,3,10},{17,16,17,3_456}})), ?_assertEqual("2001-03-10T17:16:17.000456Z", format_iso8601({{2001,3,10},{17,16,17,456}})), ?_assertEqual("2001-03-10T17:16:17.000056Z", @@ -1043,21 +1020,21 @@ format_iso8601_test_() -> ?_assertEqual("2001-03-10T07:16:17.000000Z", format_iso8601({{2001,3,10},{07,16,17,0}})), ?_assertEqual("2001-03-10T07:16:17.100000Z", - format_iso8601({{2001,3,10},{07,16,17,100000}})), + format_iso8601({{2001,3,10},{07,16,17,100_000}})), ?_assertEqual("2001-03-10T07:16:17.120000Z", - format_iso8601({{2001,3,10},{07,16,17,120000}})), + format_iso8601({{2001,3,10},{07,16,17,120_000}})), ?_assertEqual("2001-03-10T07:16:17.123000Z", - format_iso8601({{2001,3,10},{07,16,17,123000}})), + format_iso8601({{2001,3,10},{07,16,17,123_000}})), ?_assertEqual("2001-03-10T07:16:17.123400Z", - format_iso8601({{2001,3,10},{07,16,17,123400}})), + format_iso8601({{2001,3,10},{07,16,17,123_400}})), ?_assertEqual("2001-03-10T07:16:17.123450Z", - format_iso8601({{2001,3,10},{07,16,17,123450}})), + format_iso8601({{2001,3,10},{07,16,17,123_450}})), ?_assertEqual("2001-03-10T07:16:17.123456Z", - format_iso8601({{2001,3,10},{07,16,17,123456}})), + format_iso8601({{2001,3,10},{07,16,17,123_456}})), ?_assertEqual("2001-03-10T07:16:17.023456Z", - format_iso8601({{2001,3,10},{07,16,17,23456}})), + format_iso8601({{2001,3,10},{07,16,17,23_456}})), ?_assertEqual("2001-03-10T07:16:17.003456Z", - format_iso8601({{2001,3,10},{07,16,17,3456}})), + format_iso8601({{2001,3,10},{07,16,17,3_456}})), ?_assertEqual("2001-03-10T07:16:17.000456Z", format_iso8601({{2001,3,10},{07,16,17,456}})), ?_assertEqual("2001-03-10T07:16:17.000056Z", @@ -1074,31 +1051,31 @@ parse_iso8601_test_() -> parse("2001-03-10T17:16:17.000Z")), ?_assertEqual({{2001,3,10},{17,16,17,0}}, parse("2001-03-10T17:16:17.000000Z")), - ?_assertEqual({{2001,3,10},{17,16,17,100000}}, + ?_assertEqual({{2001,3,10},{17,16,17,100_000}}, parse("2001-03-10T17:16:17.1Z")), - ?_assertEqual({{2001,3,10},{17,16,17,120000}}, + ?_assertEqual({{2001,3,10},{17,16,17,120_000}}, parse("2001-03-10T17:16:17.12Z")), - ?_assertEqual({{2001,3,10},{17,16,17,123000}}, + ?_assertEqual({{2001,3,10},{17,16,17,123_000}}, parse("2001-03-10T17:16:17.123Z")), - ?_assertEqual({{2001,3,10},{17,16,17,123400}}, + ?_assertEqual({{2001,3,10},{17,16,17,123_400}}, parse("2001-03-10T17:16:17.1234Z")), - ?_assertEqual({{2001,3,10},{17,16,17,123450}}, + ?_assertEqual({{2001,3,10},{17,16,17,123_450}}, parse("2001-03-10T17:16:17.12345Z")), - ?_assertEqual({{2001,3,10},{17,16,17,123456}}, + ?_assertEqual({{2001,3,10},{17,16,17,123_456}}, parse("2001-03-10T17:16:17.123456Z")), - ?_assertEqual({{2001,3,10},{15,16,17,100000}}, + ?_assertEqual({{2001,3,10},{15,16,17,100_000}}, parse("2001-03-10T16:16:17.1+01:00")), - ?_assertEqual({{2001,3,10},{15,16,17,123456}}, + ?_assertEqual({{2001,3,10},{15,16,17,123_456}}, parse("2001-03-10T16:16:17.123456+01:00")), - ?_assertEqual({{2001,3,10},{17,16,17,100000}}, + ?_assertEqual({{2001,3,10},{17,16,17,100_000}}, parse("2001-03-10T16:16:17.1-01:00")), - ?_assertEqual({{2001,3,10},{17,16,17,123456}}, + ?_assertEqual({{2001,3,10},{17,16,17,123_456}}, parse("2001-03-10T16:16:17.123456-01:00")), ?_assertEqual({{2001,3,10},{17,16,17,456}}, parse("2001-03-10T17:16:17.000456Z")), - ?_assertEqual({{2001,3,10},{17,16,17,123000}}, + ?_assertEqual({{2001,3,10},{17,16,17,123_000}}, parse("2001-03-10T17:16:17.123000Z")) ]. diff --git a/src/ec_dict.erl b/src/ec_dict.erl index 0c0b998..3e9418e 100644 --- a/src/ec_dict.erl +++ b/src/ec_dict.erl @@ -34,11 +34,7 @@ %%%=================================================================== %% This should be opaque, but that kills dialyzer so for now we export it %% however you should not rely on the internal representation here --ifdef(namespaced_types). -type dictionary(_K, _V) :: dict:dict(). --else. --type dictionary(_K, _V) :: dict(). --endif. %%%=================================================================== %%% API diff --git a/src/ec_dictionary.erl b/src/ec_dictionary.erl index 423914a..ea7fdc9 100644 --- a/src/ec_dictionary.erl +++ b/src/ec_dictionary.erl @@ -42,8 +42,6 @@ -type key(T) :: T. -type value(T) :: T. --ifdef(have_callback_support). - -callback new() -> any(). -callback has_key(key(any()), any()) -> boolean(). -callback get(key(any()), any()) -> any(). @@ -55,27 +53,6 @@ -callback from_list([{key(any()), value(any())}]) -> any(). -callback keys(any()) -> [key(any())]. --else. - -%% In the case where R14 or lower is being used to compile the system -%% we need to export a behaviour info --export([behaviour_info/1]). --spec behaviour_info(atom()) -> [{atom(), arity()}] | undefined. -behaviour_info(callbacks) -> - [{new, 0}, - {has_key, 2}, - {get, 2}, - {add, 3}, - {remove, 2}, - {has_value, 2}, - {size, 1}, - {to_list, 1}, - {from_list, 1}, - {keys, 1}]; -behaviour_info(_Other) -> - undefined. --endif. - %%%=================================================================== %%% API %%%=================================================================== diff --git a/src/ec_file.erl b/src/ec_file.erl index c623573..ddbee40 100644 --- a/src/ec_file.erl +++ b/src/ec_file.erl @@ -80,7 +80,14 @@ copy(From, To) -> copy_(From, To, [{file_info, [mode, time, owner, group]}]). copy_(From, To, Options) -> - case file:copy(From, To) of + Linked + = case file:read_link(From) of + {ok, Linked0} -> Linked0; + {error, _} -> undefined + end, + case Linked =/= undefined orelse file:copy(From, To) of + true -> + file:make_symlink(Linked, To); {ok, _} -> copy_file_info(To, From, proplists:get_value(file_info, Options, [])); {error, Error} -> @@ -132,23 +139,20 @@ try_write_owner(To, #file_info{uid=OwnerId}) -> try_write_group(To, #file_info{gid=OwnerId}) -> file:write_file_info(To, #file_info{gid=OwnerId}). -%% @doc return an md5 checksum string or a binary. Same as unix utility of -%% same name. +%% @doc return the MD5 digest of a string or a binary, +%% named after the UNIX utility. -spec md5sum(string() | binary()) -> string(). md5sum(Value) -> - hex(binary_to_list(erlang:md5(Value))). + bin_to_hex(crypto:hash(md5, Value)). -%% @doc return an sha1sum checksum string or a binary. Same as unix utility of -%% same name. --ifdef(deprecated_crypto). +%% @doc return the SHA-1 digest of a string or a binary, +%% named after the UNIX utility. -spec sha1sum(string() | binary()) -> string(). sha1sum(Value) -> - hex(binary_to_list(crypto:sha(Value))). --else. --spec sha1sum(string() | binary()) -> string(). -sha1sum(Value) -> - hex(binary_to_list(crypto:hash(sha, Value))). --endif. + bin_to_hex(crypto:hash(sha, Value)). + +bin_to_hex(Bin) -> + hex(binary_to_list(Bin)). %% @doc delete a file. Use the recursive option for directories. %%
@@ -167,7 +171,7 @@ remove(Path, Options) ->
 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().
 is_symlink(Path) ->
     case file:read_link_info(Path) of
@@ -219,7 +223,7 @@ real_dir_path(Path) ->
 %% function of the same name.
 -spec insecure_mkdtemp() -> TmpDirPath::file:name() | {error, term()}.
 insecure_mkdtemp() ->
-    UniqueNumber = erlang:integer_to_list(erlang:trunc(random_uniform() * 1000000000000)),
+    UniqueNumber = erlang:integer_to_list(erlang:trunc(rand:uniform() * 1_000_000_000_000)),
     TmpDirPath =
         filename:join([tmp(), lists:flatten([".tmp_dir", UniqueNumber])]),
 
@@ -245,7 +249,7 @@ mkdir_path(Path) ->
     mkdir_p(Path).
 
 
-%% @doc read a file from the file system. Provide UEX exeption on failure.
+%% @doc read a file from the file system. Provide UEX exception on failure.
 -spec read(FilePath::file:filename()) -> {ok, binary()} | {error, Reason::term()}.
 read(FilePath) ->
     %% Now that we are moving away from exceptions again this becomes
@@ -254,7 +258,7 @@ read(FilePath) ->
     file:read_file(FilePath).
 
 
-%% @doc write a file to the file system. Provide UEX exeption on failure.
+%% @doc write a file to the file system. Provide UEX exception on failure.
 -spec write(FileName::file:filename(), Contents::string()) -> ok | {error, Reason::term()}.
 write(FileName, Contents) ->
     %% Now that we are moving away from exceptions again this becomes
@@ -371,101 +375,3 @@ hex0(I)  -> $0 + I.
 sub_files(From) ->
     {ok, SubFiles} = file:list_dir(From),
     [filename:join(From, SubFile) || SubFile <- SubFiles].
-
--ifdef(rand_module).
-random_uniform() ->
-    rand:uniform().
--else.
-random_uniform() ->
-    random:seed(os:timestamp()),
-    random:uniform().
--endif.
-
-%%%===================================================================
-%%% Test Functions
-%%%===================================================================
-
--ifdef(TEST).
--include_lib("eunit/include/eunit.hrl").
-
-setup_test() ->
-    Dir = insecure_mkdtemp(),
-    mkdir_path(Dir),
-    ?assertMatch(false, is_symlink(Dir)),
-    ?assertMatch(true, filelib:is_dir(Dir)).
-
-md5sum_test() ->
-    ?assertMatch("cfcd208495d565ef66e7dff9f98764da", md5sum("0")).
-
-sha1sum_test() ->
-    ?assertMatch("b6589fc6ab0dc82cf12099d1c2d40ab994e8410c", sha1sum("0")).
-
-file_test() ->
-    Dir = insecure_mkdtemp(),
-    TermFile = filename:join(Dir, "ec_file/dir/file.term"),
-    TermFileCopy = filename:join(Dir, "ec_file/dircopy/file.term"),
-    filelib:ensure_dir(TermFile),
-    filelib:ensure_dir(TermFileCopy),
-    write_term(TermFile, "term"),
-    ?assertMatch({ok, <<"\"term\". ">>}, read(TermFile)),
-    copy(filename:dirname(TermFile),
-         filename:dirname(TermFileCopy),
-         [recursive]).
-
-teardown_test() ->
-    Dir = insecure_mkdtemp(),
-    remove(Dir, [recursive]),
-    ?assertMatch(false, filelib:is_dir(Dir)).
-
-setup_base_and_target() ->
-    BaseDir = insecure_mkdtemp(),
-    DummyContents = <<"This should be deleted">>,
-    SourceDir = filename:join([BaseDir, "source"]),
-    ok = file:make_dir(SourceDir),
-    Name1 = filename:join([SourceDir, "fileone"]),
-    Name2 = filename:join([SourceDir, "filetwo"]),
-    Name3 = filename:join([SourceDir, "filethree"]),
-    NoName = filename:join([SourceDir, "noname"]),
-
-    ok = file:write_file(Name1, DummyContents),
-    ok = file:write_file(Name2, DummyContents),
-    ok = file:write_file(Name3, DummyContents),
-    ok = file:write_file(NoName, DummyContents),
-    {BaseDir, SourceDir, {Name1, Name2, Name3, NoName}}.
-
-exists_test() ->
-    BaseDir = insecure_mkdtemp(),
-    SourceDir = filename:join([BaseDir, "source1"]),
-    NoName = filename:join([SourceDir, "noname"]),
-    ok = file:make_dir(SourceDir),
-    Name1 = filename:join([SourceDir, "fileone"]),
-    ok = file:write_file(Name1, <<"Testn">>),
-    ?assertMatch(true, exists(Name1)),
-    ?assertMatch(false, exists(NoName)).
-
-real_path_test() ->
-    BaseDir = "foo",
-    Dir = filename:absname(filename:join(BaseDir, "source1")),
-    LinkDir = filename:join([BaseDir, "link"]),
-    ok = mkdir_p(Dir),
-    file:make_symlink(Dir, LinkDir),
-    ?assertEqual(Dir, real_dir_path(LinkDir)),
-    ?assertEqual(directory, type(Dir)),
-    ?assertEqual(symlink, type(LinkDir)),
-    TermFile = filename:join(BaseDir, "test_file"),
-    ok = write_term(TermFile, foo),
-    ?assertEqual(file, type(TermFile)),
-    ?assertEqual(true, is_symlink(LinkDir)),
-    ?assertEqual(false, is_symlink(Dir)).
-
-find_test() ->
-    %% Create a directory in /tmp for the test. Clean everything afterwards
-    {BaseDir, _SourceDir, {Name1, Name2, Name3, _NoName}} = setup_base_and_target(),
-    Result = find(BaseDir, "file[a-z]+\$"),
-    ?assertMatch(3, erlang:length(Result)),
-    ?assertEqual(true, lists:member(Name1, Result)),
-    ?assertEqual(true, lists:member(Name2, Result)),
-    ?assertEqual(true, lists:member(Name3, Result)),
-    remove(BaseDir, [recursive]).
-
--endif.
diff --git a/src/ec_gb_trees.erl b/src/ec_gb_trees.erl
index 7985fab..cde3f1b 100644
--- a/src/ec_gb_trees.erl
+++ b/src/ec_gb_trees.erl
@@ -135,79 +135,3 @@ from_list(List) when is_list(List) ->
 -spec keys(gb_trees:tree(K,_V)) -> [ec_dictionary:key(K)].
 keys(Data) ->
     gb_trees:keys(Data).
-
-%%%===================================================================
-%%% Tests
-%%%===================================================================
-
-
--ifdef(TEST).
--include_lib("eunit/include/eunit.hrl").
-
-%% For me unit testing initially is about covering the obvious case. A
-%% check to make sure that what you expect the tested functionality to
-%% do, it actually does. As time goes on and people detect bugs you
-%% add tests for those specific problems to the unit test suit.
-%%
-%% However, when getting started you can only test your basic
-%% expectations. So here are the expectations I have for the add
-%% functionality.
-%%
-%% 1) I can put arbitrary terms into the dictionary as keys
-%% 2) I can put arbitrary terms into the dictionary as values
-%% 3) When I put a value in the dictionary by a key, I can retrieve
-%% that same value
-%% 4) When I put a different value in the dictionary by key it does
-%% not change other key value pairs.
-%% 5) When I update a value the new value in available by the new key
-%% 6) When a value does not exist a not found exception is created
-
-add_test() ->
-    Dict0 = ec_dictionary:new(ec_gb_trees),
-
-    Key1 = foo,
-    Key2 = [1, 3],
-    Key3 = {"super"},
-    Key4 = <<"fabulous">>,
-    Key5 = {"Sona", 2, <<"Zuper">>},
-
-    Value1 = Key5,
-    Value2 = Key4,
-    Value3 = Key2,
-    Value4 = Key3,
-    Value5 = Key1,
-
-    Dict01 = ec_dictionary:add(Key1, Value1, Dict0),
-    Dict02 = ec_dictionary:add(Key3, Value3,
-                               ec_dictionary:add(Key2, Value2,
-                                                 Dict01)),
-    Dict1 =
-        ec_dictionary:add(Key5, Value5,
-                          ec_dictionary:add(Key4, Value4,
-                                            Dict02)),
-
-    ?assertMatch(Value1, ec_dictionary:get(Key1, Dict1)),
-    ?assertMatch(Value2, ec_dictionary:get(Key2, Dict1)),
-    ?assertMatch(Value3, ec_dictionary:get(Key3, Dict1)),
-    ?assertMatch(Value4, ec_dictionary:get(Key4, Dict1)),
-    ?assertMatch(Value5, ec_dictionary:get(Key5, Dict1)),
-
-
-    Dict2 = ec_dictionary:add(Key3, Value5,
-                              ec_dictionary:add(Key2, Value4, Dict1)),
-
-
-    ?assertMatch(Value1, ec_dictionary:get(Key1, Dict2)),
-    ?assertMatch(Value4, ec_dictionary:get(Key2, Dict2)),
-    ?assertMatch(Value5, ec_dictionary:get(Key3, Dict2)),
-    ?assertMatch(Value4, ec_dictionary:get(Key4, Dict2)),
-    ?assertMatch(Value5, ec_dictionary:get(Key5, Dict2)),
-
-
-    ?assertThrow(not_found, ec_dictionary:get(should_blow_up, Dict2)),
-    ?assertThrow(not_found, ec_dictionary:get("This should blow up too",
-                                              Dict2)).
-
-
-
--endif.
diff --git a/src/ec_git_vsn.erl b/src/ec_git_vsn.erl
index 569d78c..e67d8e4 100644
--- a/src/ec_git_vsn.erl
+++ b/src/ec_git_vsn.erl
@@ -17,6 +17,13 @@
 -export([new/0,
          vsn/1]).
 
+-ifdef(TEST).
+-export([parse_tags/1,
+	 get_patch_count/1,
+	 collect_default_refcount/1
+	]).
+-endif.
+
 -export_type([t/0]).
 
 %%%===================================================================
@@ -34,7 +41,7 @@
 new() ->
     {}.
 
--spec vsn(t()) -> {ok, string()} | {error, Reason::any()}.
+-spec vsn(t()|string()) -> {ok, string()} | {error, Reason::any()}.
 vsn(Data) ->
     {Vsn, RawRef, RawCount} = collect_default_refcount(Data),
     {ok, build_vsn_string(Vsn, RawRef, RawCount)}.
@@ -61,12 +68,7 @@ collect_default_refcount(Data) ->
 build_vsn_string(Vsn, RawRef, RawCount) ->
     %% Cleanup the tag and the Ref information. Basically leading 'v's and
     %% whitespace needs to go away.
-    RefTag = case RawRef of
-                 undefined ->
-                     "";
-                 RawRef ->
-                     [".ref", re:replace(RawRef, "\\s", "", [global])]
-             end,
+    RefTag = [".ref", re:replace(RawRef, "\\s", "", [global])],
     Count = erlang:iolist_to_binary(re:replace(RawCount, "\\s", "", [global])),
 
     %% Create the valid [semver](http://semver.org) version from the tag
@@ -80,28 +82,26 @@ build_vsn_string(Vsn, RawRef, RawCount) ->
 
 get_patch_count(RawRef) ->
     Ref = re:replace(RawRef, "\\s", "", [global]),
-    Cmd = io_lib:format("git rev-list --count ~s..HEAD",
+    Cmd = io_lib:format("git rev-list --count ~ts..HEAD",
                          [Ref]),
-    os:cmd(Cmd).
+    case os:cmd(Cmd) of
+        "fatal: " ++ _ ->
+            0;
+        Count ->
+            Count
+    end.
 
--spec parse_tags(t()) -> {string()|undefined, ec_semver:version_string()}.
+-spec parse_tags(t()|string()) -> {string()|undefined, ec_semver:version_string()}.
 parse_tags({}) ->
     parse_tags("");
 parse_tags(Pattern) ->
-    Cmd = io_lib:format("git describe --abbrev=0 --tags --match \"~s*\"", [Pattern]),
+    Cmd = io_lib:format("git describe --abbrev=0 --tags --match \"~ts*\"", [Pattern]),
     Tag = os:cmd(Cmd),
-    Vsn = slice(Tag, len(Pattern)),
-    Vsn1 = trim(trim(Vsn, left, "v"), right, "\n"),
-    {Tag, Vsn1}.
-
--ifdef(unicode_str).
-len(Str) -> string:length(Str).
-trim(Str, right, Chars) -> string:trim(Str, trailing, Chars);
-trim(Str, left, Chars) -> string:trim(Str, leading, Chars);
-trim(Str, both, Chars) -> string:trim(Str, both, Chars).
-slice(Str, Len) -> string:slice(Str, Len).
--else.
-len(Str) -> string:len(Str).
-trim(Str, Dir, [Chars|_]) -> string:strip(Str, Dir, Chars).
-slice(Str, Len) -> string:substr(Str, Len + 1).
--endif.
+    case Tag of
+        "fatal: " ++ _ ->
+            {undefined, ""};
+        _ ->
+            Vsn  = string:slice(Tag, string:length(Pattern)),
+            Vsn1 = string:trim(string:trim(Vsn, leading, "v"), trailing, "\n"),
+            {Tag, Vsn1}
+    end.
diff --git a/src/ec_lists.erl b/src/ec_lists.erl
index c95078b..fed76d0 100644
--- a/src/ec_lists.erl
+++ b/src/ec_lists.erl
@@ -52,7 +52,7 @@ find(_Fun, []) ->
     error.
 
 %% @doc Fetch a value from the list. If the function returns true the
-%% value is returend. If processing reaches the end of the list and
+%% value is returned. If processing reaches the end of the list and
 %% the function has never returned true an exception not_found is
 %% thrown.
 -spec fetch(fun(), list()) -> term().
@@ -63,184 +63,3 @@ fetch(Fun, List) when is_list(List), is_function(Fun) ->
         error ->
             throw(not_found)
     end.
-
-%%%===================================================================
-%%% Test Functions
-%%%===================================================================
-
--ifdef(TEST).
--include_lib("eunit/include/eunit.hrl").
-
-find1_test() ->
-    TestData = [1, 2, 3, 4, 5, 6],
-    Result = find(fun(5) ->
-                          true;
-                     (_) ->
-                          false
-                  end,
-                  TestData),
-    ?assertMatch({ok, 5}, Result),
-
-    Result2 = find(fun(37) ->
-                           true;
-                      (_) ->
-                           false
-                   end,
-                   TestData),
-    ?assertMatch(error, Result2).
-
-find2_test() ->
-    TestData = ["one", "two", "three", "four", "five", "six"],
-    Result = find(fun("five") ->
-                          true;
-                     (_) ->
-                          false
-                  end,
-                  TestData),
-    ?assertMatch({ok, "five"}, Result),
-
-    Result2 = find(fun(super_duper) ->
-                           true;
-                      (_) ->
-                           false
-                   end,
-                   TestData),
-    ?assertMatch(error, Result2).
-
-
-
-find3_test() ->
-    TestData = [{"one", 1}, {"two", 2}, {"three", 3}, {"four", 5}, {"five", 5},
-                {"six", 6}],
-    Result = find(fun({"one", 1}) ->
-                          true;
-                     (_) ->
-                          false
-                  end,
-                  TestData),
-    ?assertMatch({ok, {"one", 1}}, Result),
-
-    Result2 = find(fun([fo, bar, baz]) ->
-                           true;
-                      ({"onehundred", 100}) ->
-                           true;
-                      (_) ->
-                           false
-                   end,
-                   TestData),
-    ?assertMatch(error, Result2).
-
-
-
-fetch1_test() ->
-    TestData = [1, 2, 3, 4, 5, 6],
-    Result = fetch(fun(5) ->
-                           true;
-                      (_) ->
-                           false
-                   end,
-                   TestData),
-    ?assertMatch(5, Result),
-
-    ?assertThrow(not_found,
-                 fetch(fun(37) ->
-                               true;
-                          (_) ->
-                               false
-                       end,
-                       TestData)).
-
-fetch2_test() ->
-    TestData = ["one", "two", "three", "four", "five", "six"],
-    Result = fetch(fun("five") ->
-                           true;
-                      (_) ->
-                           false
-                   end,
-                   TestData),
-    ?assertMatch("five", Result),
-
-    ?assertThrow(not_found,
-                 fetch(fun(super_duper) ->
-                               true;
-                          (_) ->
-                               false
-                       end,
-                       TestData)).
-
-fetch3_test() ->
-    TestData = [{"one", 1}, {"two", 2}, {"three", 3}, {"four", 5}, {"five", 5},
-                {"six", 6}],
-    Result = fetch(fun({"one", 1}) ->
-                           true;
-                      (_) ->
-                           false
-                   end,
-                   TestData),
-    ?assertMatch({"one", 1}, Result),
-
-    ?assertThrow(not_found,
-                 fetch(fun([fo, bar, baz]) ->
-                               true;
-                          ({"onehundred", 100}) ->
-                               true;
-                          (_) ->
-                               false
-                       end,
-                       TestData)).
-
-search1_test() ->
-    TestData = [1, 2, 3, 4, 5, 6],
-    Result = search(fun(5) ->
-                            {ok, 5};
-                       (_) ->
-                            not_found
-                    end,
-                    TestData),
-    ?assertMatch({ok, 5, 5}, Result),
-
-    Result2 = search(fun(37) ->
-                             {ok, 37};
-                        (_) ->
-                             not_found
-                     end,
-                     TestData),
-    ?assertMatch(not_found, Result2).
-
-search2_test() ->
-    TestData = [1, 2, 3, 4, 5, 6],
-    Result = search(fun(1) ->
-                            {ok, 10};
-                       (_) ->
-                            not_found
-                    end,
-                    TestData),
-    ?assertMatch({ok, 10, 1}, Result),
-
-    Result2 = search(fun(6) ->
-                             {ok, 37};
-                        (_) ->
-                             not_found
-                     end,
-                     TestData),
-    ?assertMatch({ok, 37, 6}, Result2).
-
-search3_test() ->
-    TestData = [1, 2, 3, 4, 5, 6],
-    Result = search(fun(10) ->
-                            {ok, 10};
-                       (_) ->
-                            not_found
-                    end,
-                    TestData),
-    ?assertMatch(not_found, Result),
-
-    Result2 = search(fun(-1) ->
-                             {ok, 37};
-                        (_) ->
-                             not_found
-                     end,
-                     TestData),
-    ?assertMatch(not_found, Result2).
-
--endif.
diff --git a/src/ec_plists.erl b/src/ec_plists.erl
index 50f122e..221075b 100644
--- a/src/ec_plists.erl
+++ b/src/ec_plists.erl
@@ -30,7 +30,7 @@
 %%% most list operations parallel. It can operate on each element in
 %%% parallel, for IO-bound operations, on sublists in parallel, for
 %%% taking advantage of multi-core machines with CPU-bound operations,
-%%% and across erlang nodes, for parallizing inside a cluster. It
+%%% and across erlang nodes, for parallelizing inside a cluster. It
 %%% handles errors and node failures. It can be configured, tuned, and
 %%% tweaked to get optimal performance while minimizing overhead.
 %%%
@@ -38,7 +38,7 @@
 %%% lists, returning exactly the same result, and having both a form
 %%% with an identical syntax that operates on each element in parallel
 %%% and a form which takes an optional "malt", a specification for how
-%%% to parallize the operation.
+%%% to parallelize the operation.
 %%%
 %%% fold is the one exception, parallel fold is different from linear
 %%% fold.  This module also include a simple mapreduce implementation,
@@ -169,7 +169,7 @@
 %%% processes. If one of them does a non-normal exit, plists receives
 %%% the 'DOWN' message believing it to be from one of its own
 %%% processes. The error propagation system goes into effect, which
-%%% results in the error occuring in the calling process.
+%%% results in the error occurring in the calling process.
 %%%
 -module(ec_plists).
 
@@ -330,14 +330,14 @@ fold(Fun, Fuse, InitAcc, List, Malt) ->
            end,
     runmany(Fun2, Fuse, List, Malt).
 
-%% @doc Similiar to foreach in module
+%% @doc Similar to foreach in module
 %% lists
 %% except it makes no guarantee about the order it processes list elements.
 -spec foreach(fun(), list()) -> ok.
 foreach(Fun, List) ->
     foreach(Fun, List, 1).
 
-%% @doc Similiar to foreach in module
+%% @doc Similar to foreach in module
 %% lists
 %% except it makes no guarantee about the order it processes list elements.
 -spec foreach(fun(), list(), malt()) -> ok.
@@ -432,8 +432,8 @@ sort(Fun, List) ->
 %%
 %% sort splits the list into sublists and sorts them, and it merges the
 %% sorted lists together. These are done in parallel. Each sublist is
-%% sorted in a seperate process, and each merging of results is done in a
-%% seperate process. Malt defaults to 100, causing the list to be split into
+%% sorted in a separate process, and each merging of results is done in a
+%% separate process. Malt defaults to 100, causing the list to be split into
 %% 100-element sublists.
 -spec sort(fun(), list(), malt()) -> list().
 sort(Fun, List, Malt) ->
@@ -464,11 +464,11 @@ usort(Fun, List) ->
 %%
 %% usort splits the list into sublists and sorts them, and it merges the
 %% sorted lists together. These are done in parallel. Each sublist is
-%% sorted in a seperate process, and each merging of results is done in a
-%% seperate process. Malt defaults to 100, causing the list to be split into
+%% sorted in a separate process, and each merging of results is done in a
+%% separate process. Malt defaults to 100, causing the list to be split into
 %% 100-element sublists.
 %%
-%% usort removes duplicate elments while it sorts.
+%% usort removes duplicate elements while it sorts.
 -spec usort(fun(), list(), malt()) -> list().
 usort(Fun, List, Malt) ->
     Fun2 = fun (L) ->
@@ -480,16 +480,9 @@ usort(Fun, List, Malt) ->
     runmany(Fun2, {recursive, Fuse}, List, Malt).
 
 %% @doc Like below, assumes default MapMalt of 1.
--ifdef(namespaced_types).
 -spec mapreduce(MapFunc, list()) -> dict:dict() when
       MapFunc ::  fun((term()) -> DeepListOfKeyValuePairs),
       DeepListOfKeyValuePairs :: [DeepListOfKeyValuePairs] | {Key::term(), Value::term()}.
--else.
--spec mapreduce(MapFunc, list()) -> dict() when
-      MapFunc ::  fun((term()) -> DeepListOfKeyValuePairs),
-      DeepListOfKeyValuePairs :: [DeepListOfKeyValuePairs] | {Key::term(), Value::term()}.
--endif.
-
 
 mapreduce(MapFunc, List) ->
     mapreduce(MapFunc, List, 1).
@@ -514,21 +507,14 @@ mapreduce(MapFunc, List, MapMalt) ->
 %% reducer's final state.
 %%
 %% MapMalt is the malt for the mapping operation, with a default value of 1,
-%% meaning each element of the list is mapped by a seperate process.
+%% meaning each element of the list is mapped by a separate process.
 %%
 %% mapreduce requires OTP R11B, or it may leave monitoring messages in the
 %% message queue.
--ifdef(namespaced_types).
 -spec mapreduce(MapFunc, list(), InitState::term(), ReduceFunc, malt()) -> dict:dict() when
       MapFunc :: fun((term()) -> DeepListOfKeyValuePairs),
       DeepListOfKeyValuePairs :: [DeepListOfKeyValuePairs] | {Key::term(), Value::term()},
       ReduceFunc :: fun((OldState::term(), Key::term(), Value::term()) -> NewState::term()).
--else.
--spec mapreduce(MapFunc, list(), InitState::term(), ReduceFunc, malt()) -> dict() when
-      MapFunc :: fun((term()) -> DeepListOfKeyValuePairs),
-      DeepListOfKeyValuePairs :: [DeepListOfKeyValuePairs] | {Key::term(), Value::term()},
-      ReduceFunc :: fun((OldState::term(), Key::term(), Value::term()) -> NewState::term()).
--endif.
 mapreduce(MapFunc, List, InitState, ReduceFunc, MapMalt) ->
     Parent = self(),
     {Reducer, ReducerRef} =
@@ -586,7 +572,7 @@ add_key(Dict, Key, Value) ->
     end.
 
 %% @doc Like below, but assumes a Malt of 1,
-%% meaning each element of the list is processed by a seperate process.
+%% meaning each element of the list is processed by a separate process.
 -spec runmany(fun(), fuse(), list()) -> term().
 runmany(Fun, Fuse, List) ->
     runmany(Fun, Fuse, List, 1).
@@ -615,7 +601,7 @@ runmany(Fun, Fuse, List) ->
 %% continues fusing pairs of results until it is down to one.
 %%
 %% Recursive fuse is down in parallel with processing the sublists, and a
-%% process is spawned to fuse each pair of results. It is a parallized
+%% process is spawned to fuse each pair of results. It is a parallelized
 %% algorithm. Linear fuse is done after all results of processing sublists
 %% have been collected, and can only run in a single process.
 %%
@@ -691,7 +677,7 @@ runmany(Fun, {recursive, Fuse}, List, local, Split, []) ->
     %% or {nodes, NodeList}. Degenerates recursive fuse into linear fuse.
     runmany(Fun, Fuse, List, local, Split, []);
 runmany(Fun, Fuse, List, Nodes, no_split, []) ->
-    %% by default, operate on each element seperately
+    %% by default, operate on each element separately
     runmany(Fun, Fuse, List, Nodes, 1, []);
 runmany(Fun, Fuse, List, local, Split, []) ->
     List2 = splitmany(List, Split),
@@ -872,24 +858,9 @@ cluster_runmany(_, _, [_Non|_Empty], []=_Nodes, []=_Running, _) ->
 %% We have data, but no nodes either available or occupied
     erlang:exit(allnodescrashed).
 
--ifdef(fun_stacktrace).
 runmany_wrap(Fun, Parent) ->
     try
-        Fun
-    catch
-        exit:siblingdied ->
-            ok;
-        exit:Reason ->
-            Parent ! {erlang:self(), error, Reason};
-        error:R ->
-            Parent ! {erlang:self(), error, {R, erlang:get_stacktrace()}};
-        throw:R ->
-            Parent ! {erlang:self(), error, {{nocatch, R}, erlang:get_stacktrace()}}
-    end.
--else.
-runmany_wrap(Fun, Parent) ->
-    try
-        Fun
+        Fun()
     catch
         exit:siblingdied ->
             ok;
@@ -900,7 +871,6 @@ runmany_wrap(Fun, Parent) ->
         throw:R:Stacktrace ->
             Parent ! {erlang:self(), error, {{nocatch, R}, Stacktrace}}
     end.
--endif.
 
 delete_running(Pid, [{Pid, Node, List}|Running], Acc) ->
     {Running ++ Acc, Node, List};
diff --git a/src/ec_rbdict.erl b/src/ec_rbdict.erl
index 60e337f..9f3b617 100644
--- a/src/ec_rbdict.erl
+++ b/src/ec_rbdict.erl
@@ -32,7 +32,7 @@
 %%% representation of a dictionary, where a red-black tree is used to
 %%% store the keys and values.
 %%%
-%%% This module implents exactly the same interface as the module
+%%% This module implements exactly the same interface as the module
 %%% ec_dictionary but with a defined representation. One difference is
 %%% that while dict considers two keys as different if they do not
 %%% match (=:=), this module considers two keys as different if and
@@ -296,7 +296,7 @@ to_list(empty, List) -> List;
 to_list({_, A, Xk, Xv, B}, List) ->
     to_list(A, [{Xk, Xv} | to_list(B, List)]).
 
-%% Balance a tree afer (possibly) adding a node to the left/right.
+%% Balance a tree after (possibly) adding a node to the left/right.
 -spec lbalance(color(), dictionary(K, V),
                ec_dictionary:key(K), ec_dictionary:value(V),
                dictionary(K, V)) ->
diff --git a/src/ec_semver.erl b/src/ec_semver.erl
index 2982dfc..3ffd591 100644
--- a/src/ec_semver.erl
+++ b/src/ec_semver.erl
@@ -202,13 +202,13 @@ pes(VsnA, VsnB) ->
 %%%===================================================================
 %%% Friend Functions
 %%%===================================================================
-%% @doc helper function for the peg grammer to parse the iolist into a semver
+%% @doc helper function for the peg grammar to parse the iolist into a semver
 -spec internal_parse_version(iolist()) -> semver().
 internal_parse_version([MMP, AlphaPart, BuildPart, _]) ->
     {parse_major_minor_patch_minpatch(MMP), {parse_alpha_part(AlphaPart),
                                              parse_alpha_part(BuildPart)}}.
 
-%% @doc helper function for the peg grammer to parse the iolist into a major_minor_patch
+%% @doc helper function for the peg grammar to parse the iolist into a major_minor_patch
 -spec parse_major_minor_patch_minpatch(iolist()) -> major_minor_patch_minpatch().
 parse_major_minor_patch_minpatch([MajVsn, [], [], []]) ->
     strip_maj_version(MajVsn);
@@ -224,7 +224,7 @@ parse_major_minor_patch_minpatch([MajVsn,
                                   [<<".">>, MinPatch]]) ->
     {strip_maj_version(MajVsn), MinVsn, PatchVsn, MinPatch}.
 
-%% @doc helper function for the peg grammer to parse the iolist into an alpha part
+%% @doc helper function for the peg grammar to parse the iolist into an alpha part
 -spec parse_alpha_part(iolist()) -> [alpha_part()].
 parse_alpha_part([]) ->
     [];
@@ -309,407 +309,3 @@ internal_pes(VsnA, {{LM, LMI, LP, LMP}, Alpha})
         lt(VsnA, {{LM, LMI, LP + 1, 0}, {[], []}});
 internal_pes(Vsn, LVsn) ->
     gte(Vsn, LVsn).
-
-%%%===================================================================
-%%% Test Functions
-%%%===================================================================
-
--ifdef(TEST).
--include_lib("eunit/include/eunit.hrl").
-
-eql_test() ->
-    ?assertMatch(true, eql("1.0.0-alpha",
-                           "1.0.0-alpha")),
-    ?assertMatch(true, eql("v1.0.0-alpha",
-                           "1.0.0-alpha")),
-    ?assertMatch(true, eql("1",
-                           "1.0.0")),
-    ?assertMatch(true, eql("v1",
-                           "v1.0.0")),
-    ?assertMatch(true, eql("1.0",
-                           "1.0.0")),
-    ?assertMatch(true, eql("1.0.0",
-                           "1")),
-    ?assertMatch(true, eql("1.0.0.0",
-                           "1")),
-    ?assertMatch(true, eql("1.0+alpha.1",
-                           "1.0.0+alpha.1")),
-    ?assertMatch(true, eql("1.0-alpha.1+build.1",
-                           "1.0.0-alpha.1+build.1")),
-    ?assertMatch(true, eql("1.0-alpha.1+build.1",
-                           "1.0.0.0-alpha.1+build.1")),
-    ?assertMatch(true, eql("1.0-alpha.1+build.1",
-                           "v1.0.0.0-alpha.1+build.1")),
-    ?assertMatch(true, eql("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-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-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-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-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-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("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-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("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-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",
-                                   "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")).
-
-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+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">>, erlang:iolist_to_binary(format({1, {[],[]}}))).
-
--endif.
diff --git a/src/ec_semver_parser.erl b/src/ec_semver_parser.erl
index 556f984..c2fe186 100644
--- a/src/ec_semver_parser.erl
+++ b/src/ec_semver_parser.erl
@@ -44,11 +44,10 @@ parse(Input) when is_binary(Input) ->
 
 -spec 'alpha_part'(input(), index()) -> parse_result().
 'alpha_part'(Input, Index) ->
-  p(Input, Index, 'alpha_part', fun(I,D) -> (p_one_or_more(p_charclass(<<"[A-Za-z0-9]">>)))(I,D) end, fun(Node, _Idx) ->erlang:iolist_to_binary(Node) end).
+  p(Input, Index, 'alpha_part', fun(I,D) -> (p_one_or_more(p_charclass(<<"[A-Za-z0-9-]">>)))(I,D) end, fun(Node, _Idx) ->erlang:iolist_to_binary(Node) end).
 
 
 transform(_,Node,_Index) -> Node.
--file("peg_includes.hrl", 1).
 -type index() :: {{line, pos_integer()}, {column, pos_integer()}}.
 -type input() :: binary().
 -type parse_failure() :: {fail, term()}.
diff --git a/src/ec_talk.erl b/src/ec_talk.erl
index 36cff8e..8c3a105 100644
--- a/src/ec_talk.erl
+++ b/src/ec_talk.erl
@@ -39,6 +39,11 @@
          say/1,
          say/2]).
 
+-ifdef(TEST).
+-export([get_boolean/1,
+	 get_integer/1]).
+-endif.
+
 -export_type([prompt/0,
               type/0,
               supported/0]).
@@ -75,7 +80,7 @@ ask(Prompt) ->
 ask_default(Prompt, Default) ->
     ask_convert(Prompt, fun get_string/1, string, Default).
 
-%% @doc Asks the user to respond to the prompt. Trys to return the
+%% @doc Asks the user to respond to the prompt. Tries to return the
 %% value in the format specified by 'Type'.
 -spec ask(prompt(), type()) ->  supported().
 ask(Prompt, boolean) ->
@@ -85,7 +90,7 @@ ask(Prompt, number) ->
 ask(Prompt, string) ->
     ask_convert(Prompt, fun get_string/1, string, none).
 
-%% @doc Asks the user to respond to the prompt. Trys to return the
+%% @doc Asks the user to respond to the prompt. Tries to return the
 %% value in the format specified by 'Type'.
 -spec ask_default(prompt(), type(), supported()) ->  supported().
 ask_default(Prompt, boolean, Default)  ->
@@ -127,7 +132,7 @@ ask_convert(Prompt, TransFun, Type,  Default) ->
                                                            Default ->
                                                                [" (", io_lib:format("~p", [Default]) , ")"]
                                                        end, "> "])),
-    Data = trim(trim(io:get_line(NewPrompt)), both, [$\n]),
+    Data = string:trim(string:trim(io:get_line(NewPrompt)), both, [$\n]),
     Ret = TransFun(Data),
     case Ret of
         no_data ->
@@ -145,7 +150,7 @@ ask_convert(Prompt, TransFun, Type,  Default) ->
             Ret
     end.
 
-%% @doc Trys to translate the result into a boolean
+%% @doc Tries to translate the result into a boolean
 -spec get_boolean(string()) -> boolean().
 get_boolean([]) ->
     no_data;
@@ -172,7 +177,7 @@ get_boolean([$N | _]) ->
 get_boolean(_) ->
     no_clue.
 
-%% @doc Trys to translate the result into an integer
+%% @doc Tries to translate the result into an integer
 -spec get_integer(string()) -> integer().
 get_integer([]) ->
     no_data;
@@ -196,36 +201,3 @@ get_string(String) ->
         false ->
             no_clue
     end.
-
--ifdef(unicode_str).
-trim(Str) -> string:trim(Str).
-trim(Str, right, Chars) -> string:trim(Str, trailing, Chars);
-trim(Str, left, Chars) -> string:trim(Str, leading, Chars);
-trim(Str, both, Chars) -> string:trim(Str, both, Chars).
--else.
-trim(Str) -> string:strip(Str).
-trim(Str, Dir, [Chars|_]) -> string:strip(Str, Dir, Chars).
--endif.
-
-%%%====================================================================
-%%% tests
-%%%====================================================================
--ifdef(TEST).
--include_lib("eunit/include/eunit.hrl").
-
-general_test_() ->
-    [?_test(42 == get_integer("42")),
-     ?_test(500211 == get_integer("500211")),
-     ?_test(1234567890 == get_integer("1234567890")),
-     ?_test(12345678901234567890 == get_integer("12345678901234567890")),
-     ?_test(true == get_boolean("true")),
-     ?_test(false == get_boolean("false")),
-     ?_test(true == get_boolean("Ok")),
-     ?_test(true == get_boolean("ok")),
-     ?_test(true == get_boolean("Y")),
-     ?_test(true == get_boolean("y")),
-     ?_test(false == get_boolean("False")),
-     ?_test(false == get_boolean("No")),
-     ?_test(false == get_boolean("no"))].
-
--endif.
diff --git a/src/ec_vsn.erl b/src/ec_vsn.erl
index 2f38090..e407b9f 100644
--- a/src/ec_vsn.erl
+++ b/src/ec_vsn.erl
@@ -27,24 +27,9 @@
 %% however you should not rely on the internal representation here
 -type t() :: #t{}.
 
--ifdef(have_callback_support).
-
 -callback new() -> any().
 -callback vsn(any()) -> {ok, string()} | {error, Reason::any()}.
 
--else.
-
-%% In the case where R14 or lower is being used to compile the system
-%% we need to export a behaviour info
--export([behaviour_info/1]).
--spec behaviour_info(atom()) -> [{atom(), arity()}] | undefined.
-behaviour_info(callbacks) ->
-    [{new, 0},
-     {vsn, 1}];
-behaviour_info(_Other) ->
-    undefined.
--endif.
-
 %%%===================================================================
 %%% API
 %%%===================================================================
diff --git a/src/erlware_commons.app.src b/src/erlware_commons.app.src
index 66f75bd..7709d81 100644
--- a/src/erlware_commons.app.src
+++ b/src/erlware_commons.app.src
@@ -6,6 +6,6 @@
               {applications,[kernel,stdlib,cf]},
               {maintainers,["Eric Merritt","Tristan Sloughter",
                             "Jordan Wilberding","Martin Logan"]},
-              {licenses,["Apache"]},
+              {licenses,["Apache", "MIT"]},
               {links,[{"Github",
                        "https://github.com/erlware/erlware_commons"}]}]}.
diff --git a/test/ec_cmd_log_tests.erl b/test/ec_cmd_log_tests.erl
new file mode 100644
index 0000000..f1d1181
--- /dev/null
+++ b/test/ec_cmd_log_tests.erl
@@ -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")).
diff --git a/test/ec_cnv_tests.erl b/test/ec_cnv_tests.erl
new file mode 100644
index 0000000..6bbad6e
--- /dev/null
+++ b/test/ec_cnv_tests.erl
@@ -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)).
diff --git a/test/ec_file_tests.erl b/test/ec_file_tests.erl
new file mode 100644
index 0000000..885f3dc
--- /dev/null
+++ b/test/ec_file_tests.erl
@@ -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]).
diff --git a/test/ec_gb_trees_tests.erl b/test/ec_gb_trees_tests.erl
new file mode 100644
index 0000000..2c0ee12
--- /dev/null
+++ b/test/ec_gb_trees_tests.erl
@@ -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)).
diff --git a/test/ec_git_vsn_tests.erl b/test/ec_git_vsn_tests.erl
new file mode 100644
index 0000000..0d2efe1
--- /dev/null
+++ b/test/ec_git_vsn_tests.erl
@@ -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")).
diff --git a/test/ec_lists_tests.erl b/test/ec_lists_tests.erl
new file mode 100644
index 0000000..f6f4025
--- /dev/null
+++ b/test/ec_lists_tests.erl
@@ -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).
diff --git a/test/ec_semver_tests.erl b/test/ec_semver_tests.erl
new file mode 100644
index 0000000..0d3a18a
--- /dev/null
+++ b/test/ec_semver_tests.erl
@@ -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, {[],[]}}))).
diff --git a/test/ec_talk_tests.erl b/test/ec_talk_tests.erl
new file mode 100644
index 0000000..9b7bd07
--- /dev/null
+++ b/test/ec_talk_tests.erl
@@ -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"))].