diff --git a/.github/workflows/erlang.yml b/.github/workflows/erlang.yml new file mode 100644 index 0000000..96dda2e --- /dev/null +++ b/.github/workflows/erlang.yml @@ -0,0 +1,47 @@ +--- +name: Erlang CI + +"on": [push, pull_request] + +jobs: + build: + runs-on: ubuntu-22.04 + + strategy: + matrix: + otp: ['24', '25', '26'] + rebar: ['3.22'] + + steps: + - uses: actions/checkout@v3 + - uses: erlef/setup-beam@v1 + id: setup-beam + with: + otp-version: ${{matrix.otp}} + rebar3-version: ${{matrix.rebar}} + - name: Restore _build + uses: actions/cache@v3 + with: + path: _build + key: "_build-cache-for\ + -os-${{runner.os}}\ + -otp-${{steps.setup-beam.outputs.otp-version}}\ + -rebar3-${{steps.setup-beam.outputs.rebar3-version}}\ + -hash-${{hashFiles('rebar.lock')}}" + - name: Restore rebar3's cache + uses: actions/cache@v3 + with: + path: ~/.cache/rebar3 + key: "rebar3-cache-for\ + -os-${{runner.os}}\ + -otp-${{steps.setup-beam.outputs.otp-version}}\ + -rebar3-${{steps.setup-beam.outputs.rebar3-version}}\ + -hash-${{hashFiles('rebar.lock')}}" + - name: Compile + run: rebar3 compile + - name: Format check + run: rebar3 format --verify + - name: Run tests and verifications + run: rebar3 test + - name: Run meta verifications + run: rebar3 as test test diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..1dca35a --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,29 @@ +--- +name: Lint + +"on": [push, pull_request] + +jobs: + build: + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v3 + + # uses .markdownlint-cli2.yaml for configuration + - name: markdownlint + uses: DavidAnson/markdownlint-cli2-action@v11 + with: + globs: | + *.md + LICENSE + !CHANGELOG.md + + - name: yamllint + uses: ibiqlik/action-yamllint@v3 + with: + file_or_dir: | + .github/**/erlang.yml + .*.yml + strict: true + config_file: .yamllint.yml diff --git a/.gitignore b/.gitignore index 76d5f5c..cf3ccb5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,6 @@ doc/ -.erlang.mk/ -zipper.d -.eunit -deps -*.o -*.beam -*.plt erl_crash.dump -ebin -.erlang.mk.* -log*/ -_build/ +rebar3.crashdump +.rebar3 +_* +logs diff --git a/.markdownlint.yml b/.markdownlint.yml new file mode 100644 index 0000000..90b8143 --- /dev/null +++ b/.markdownlint.yml @@ -0,0 +1,4 @@ +--- +default: true +MD013: + line_length: 100 diff --git a/.yamllint.yml b/.yamllint.yml new file mode 100644 index 0000000..8cd0bbe --- /dev/null +++ b/.yamllint.yml @@ -0,0 +1,5 @@ +--- +extends: default +rules: + line-length: + max: 100 diff --git a/LICENSE b/LICENSE index 74882e3..85a77dc 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ -Apache License +# Apache License + Version 2.0, January 2004 - http://www.apache.org/licenses/ + https://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION @@ -192,7 +193,7 @@ Apache License you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + https://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, diff --git a/README.md b/README.md index d6fc785..a0391af 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +# Zipper [![GitHub Actions CI](https://github.com/inaka/zipper/workflows/build/badge.svg)](https://github.com/inaka/zipper) + Generic zipper implementation in Erlang. ## Zippers: what are they good for? @@ -5,9 +7,11 @@ Generic zipper implementation in Erlang. Zippers let you traverse immutable data structures with ease and flexibility. ### Contact Us -If you find any **bugs** or have a **problem** while using this library, please [open an issue](https://github.com/inaka/zipper/issues/new) in this repo (or a pull request :)). -And you can check all of our open-source projects at [inaka.github.io](http://inaka.github.io) +If you find any **bugs** or have a **problem** while using this library, please +[open an issue](https://github.com/inaka/zipper/issues/new) in this repo (or a pull request :)). + +And you can check all of our open-source projects at [inaka.github.io](https://inaka.github.io) ## Usage @@ -44,6 +48,7 @@ Root = #{type => planet, ``` You can build a zipper by providing three simple functions: + - `IsBranchFun`: takes a node and returns `true` if it is a branch node or `false` otherwise. - `ChildrenFun`: takes a node and returns a list of its children. @@ -85,7 +90,10 @@ io:format("~p", [America]), ## Tests -Circular dependency in test environment ([Katana Test](https://github.com/inaka/katana-test) -> [Elvis Core](https://github.com/inaka/elvis_core) -> [Zipper](https://github.com/inaka/zipper)) is fixed by including Zipper as a dep in the test profile in `rebar.config` +Circular dependency in test environment ([Katana Test](https://github.com/inaka/katana-test) -> +[Elvis Core](https://github.com/inaka/elvis_core) -> [Zipper](https://github.com/inaka/zipper)) is +fixed by including Zipper as a dep in the test profile in `rebar.config` + ```erlang ... {profiles, [ @@ -99,10 +107,12 @@ Circular dependency in test environment ([Katana Test](https://github.com/inaka/ ]}. ... ``` + but then, we still replace the tag with the current branch. This is done in `rebar.config.script`. -Therefore, it's really important to have the branch updated and pushed to github before running the tests with `rebar3 ct`. +Therefore, it's really important to have the branch updated and pushed to github before running the +tests with `rebar3 ct`. ## References - [The Zipper, GERARD HUET](https://www.st.cs.uni-saarland.de/edu/seminare/2005/advanced-fp/docs/huet-zipper.pdf) -- [clojure.zip](http://clojure.github.io/clojure/clojure.zip-api.html#clojure.zip/zipper) +- [clojure.zip](https://clojure.github.io/clojure/clojure.zip-api.html#clojure.zip/zipper) diff --git a/elvis.config b/elvis.config index 33e90af..971053a 100644 --- a/elvis.config +++ b/elvis.config @@ -1,58 +1,11 @@ -[ - { - elvis, - [ - {config, - [#{dirs => ["src", "test"], - filter => "*.erl", - rules => [{elvis_style, line_length, #{limit => 80, - skip_comments => false}}, - {elvis_style, no_tabs}, - {elvis_style, no_trailing_whitespace}, - {elvis_style, macro_names}, - {elvis_style, macro_module_names}, - {elvis_style, operator_spaces, #{rules => [{right, ","}, - {right, "++"}, - {left, "++"}]}}, - {elvis_style, nesting_level, #{level => 3}}, - {elvis_style, god_modules, #{limit => 30}}, - {elvis_style, no_if_expression}, - {elvis_style, invalid_dynamic_call}, - {elvis_style, used_ignored_variable}, - {elvis_style, no_behavior_info}, - { - elvis_style, - module_naming_convention, - #{regex => "^([a-z][a-z0-9]*_?)*(_SUITE)?$", - ignore => []} - }, - { - elvis_style, - function_naming_convention, - #{regex => "^([a-z][a-z0-9]*_?)*$"} - }, - {elvis_style, state_record_and_type}, - {elvis_style, no_spec_with_records}, - {elvis_style, dont_repeat_yourself, #{min_complexity => 18}}, - {elvis_style, no_debug_call} - ] - }, - #{dirs => ["."], - filter => "Makefile", - rules => [{elvis_project, no_deps_master_erlang_mk}, - {elvis_project, protocol_for_deps_erlang_mk, #{regex => "(https://.*|[0-9]+([.][0-9]+)*)"}}] - }, - #{dirs => ["."], - filter => "rebar.config", - rules => [{elvis_project, no_deps_master_rebar}, - {elvis_project, protocol_for_deps_rebar}] - }, - #{dirs => ["."], - filter => "elvis.config", - rules => [{elvis_project, old_configuration_format}] - } - ] - } - ] - } -]. +[{elvis, + [{config, + [#{dirs => ["src", "test"], + filter => "*.erl", + ruleset => erl_files}, + #{dirs => ["."], + filter => "rebar.config", + ruleset => rebar_config}, + #{dirs => ["."], + filter => "elvis.config", + ruleset => elvis_config}]}]}]. diff --git a/rebar.config b/rebar.config index 0be6968..519c399 100644 --- a/rebar.config +++ b/rebar.config @@ -1,85 +1,56 @@ -%% -*- mode: erlang;erlang-indent-level: 2;indent-tabs-mode: nil -*- -%% ex: ts=4 sw=4 ft=erlang et - -%% == Erlang Compiler == - -%% Erlang compiler options -{erl_opts, [ - warn_unused_vars, - warn_export_all, - warn_shadow_vars, - warn_unused_import, - warn_unused_function, - warn_bif_clash, - warn_unused_record, - warn_deprecated_function, - warn_obsolete_guard, - strict_validation, - warn_export_vars, - warn_exported_vars, - warn_missing_spec, - warn_untyped_record, debug_info - ]}. - -{profiles, [ - {test, [ - {deps, [ - {xref_runner, "1.0.0"} - ]} - ]}, - {shell, [ - {deps, [ - {sync, {git, "https://github.com/rustyio/sync.git", {ref, "9c78e7b"}}} - ]} - ]} -]}. - -%% == Common Test == - -%% {erl_opts, [...]}, but for CT runs -{ct_compile_opts, [ - warn_unused_vars, - warn_export_all, - warn_shadow_vars, - warn_unused_import, - warn_unused_function, - warn_bif_clash, - warn_unused_record, - warn_deprecated_function, - warn_obsolete_guard, - strict_validation, - warn_export_vars, - warn_exported_vars, - warn_missing_spec, - warn_untyped_record, debug_info - ]}. - -{ct_opts, []}. - -%% == Cover == - -{cover_enabled, true}. - -{cover_opts, [verbose]}. - -%% == Dependencies == - -{deps, []}. - -%% == Dialyzer == - -{dialyzer, [ - {warnings, [ unmatched_returns - , error_handling - ]}, - {get_warnings, true}, - {plt_apps, top_level_deps}, - {plt_extra_apps, []}, - {plt_location, local}, - {base_plt_apps, [stdlib, kernel]}, - {base_plt_location, global} -]}. - -%% == Shell == - -{shell, [{apps, [sync]}]}. +%% == Compiler and Profiles == + +{erl_opts, + [warn_unused_import, warn_export_vars, warnings_as_errors, verbose, report, debug_info]}. + +{minimum_otp_vsn, "24"}. + +{profiles, + [{test, + [{cover_enabled, true}, + {cover_opts, [verbose]}, + {ct_opts, [{verbose, true}]}, + {dialyzer, + [{warnings, [no_return, unmatched_returns, error_handling, unknown]}, + {plt_extra_apps, [syntax_tools]}]}]}, + {shell, [{deps, [{sync, "0.4.1"}]}, {apps, [sync]}]}]}. + +{alias, [{test, [compile, format, hank, lint, xref, dialyzer, ct, cover, ex_doc]}]}. + +%% == Dependencies and plugins == + +{project_plugins, + [{rebar3_hank, "~> 1.4.0"}, + {rebar3_hex, "~> 7.0.7"}, + {rebar3_format, "~> 1.3.0"}, + {rebar3_lint, "~> 3.1.0"}, + {rebar3_ex_doc, "~> 0.2.20"}]}. + +%% == Documentation == + +{ex_doc, + [{source_url, <<"https://github.com/inaka/zipper">>}, + {extras, [<<"README.md">>, <<"LICENSE">>]}, + {main, <<"README.md">>}, + {prefix_ref_vsn_with_v, false}]}. + +{hex, [{doc, #{provider => ex_doc}}]}. + +%% == Format == + +{format, [{files, ["*.config", "src/*", "test/*"]}]}. + +%% == Hank == + +{hank, [{ignore, [{"test/*.erl", unnecessary_function_arguments}]}]}. + +%% == Dialyzer + XRef == + +{dialyzer, + [{warnings, [no_return, unmatched_returns, error_handling, unknown]}, + {plt_extra_apps, [syntax_tools]}]}. + +{xref_checks, + [undefined_function_calls, deprecated_function_calls, deprecated_functions]}. + +{xref_extra_paths, ["test/**"]}. diff --git a/src/zipper.app.src b/src/zipper.app.src index 72a2fc5..3148a5c 100644 --- a/src/zipper.app.src +++ b/src/zipper.app.src @@ -1,14 +1,11 @@ -{ - application, zipper, - [ - {description, "Generic Zipper Implementation for Erlang"}, - {vsn, "1.0.1"}, - {applications, [kernel, stdlib]}, - {modules, []}, - {registered, []}, - {maintainers, ["Inaka"]}, - {licenses, ["Apache 2.0"]}, - {links, [{"Github", "https://github.com/inaka/zipper"}]}, - {build_tools, ["erlang.mk", "rebar"]} - ] -}. +{application, + zipper, + [{description, "Generic Zipper Implementation for Erlang"}, + {vsn, semver}, + {applications, [kernel, stdlib]}, + {modules, []}, + {registered, []}, + {maintainers, ["Inaka"]}, + {licenses, ["Apache 2.0"]}, + {links, [{"GitHub", "https://github.com/inaka/zipper"}]}, + {build_tools, ["rebar3"]}]}. diff --git a/src/zipper.erl b/src/zipper.erl index 5b710f2..65d0f11 100644 --- a/src/zipper.erl +++ b/src/zipper.erl @@ -1,62 +1,40 @@ %%% @doc Generic Zipper Implementation. %%% Zippers let you traverse immutable data structures with ease and %%% flexibility. -%%% @end -module(zipper). + -compile({no_auto_import, [node/1]}). --export([ - %% Creation - new/4, - %% Traverse - up/1, - down/1, - left/1, - leftmost/1, - right/1, - rightmost/1, - next/1, - is_end/1, - prev/1, - root/1, - traverse/2, - %% Editing - insert_left/2, - insert_right/2, - replace/2, - edit/3, - insert_child/2, - append_child/2, - remove/1, - %% Iteration - map/2, - fmap/3, - filter/2, - fold/3, - size/1, - %% Info - node/1, - children/1, - is_branch/1 - ]). +%% Creation +-export([new/4]). +%% Traverse +-export([up/1, down/1, left/1, leftmost/1, right/1, rightmost/1, next/1, is_end/1, prev/1, + root/1, traverse/2]). +%% Editing +-export([insert_left/2, insert_right/2, replace/2, edit/3, insert_child/2, append_child/2, + remove/1]). +%% Iteration +-export([map/2, fmap/3, filter/2, fold/3, size/1]). +%% Info +-export([node/1, children/1, is_branch/1]). -type is_branch_fun(T) :: fun((T) -> boolean()). -type make_node_fun(T) :: fun((T, [T]) -> T). --type children_fun(T) :: fun((T) -> [T]). --type info(T) :: #{ lefts => [T] - , rights => [T] - , parent_node => undefined | T - , parent_info => undefined | info(T) - , is_modified => boolean() - }. +-type children_fun(T) :: fun((T) -> [T]). +-type info(T) :: + #{lefts => [T], + rights => [T], + parent_node => undefined | T, + parent_info => undefined | info(T), + is_modified => boolean()}. -opaque zipper(T) :: - #{spec => #{is_branch => is_branch_fun(T), - make_node => make_node_fun(T), - children => children_fun(T)}, - node => T, - info => 'end' | info(T) - }. + #{spec => + #{is_branch => is_branch_fun(T), + make_node => make_node_fun(T), + children => children_fun(T)}, + node => T, + info => 'end' | info(T)}. -type operation() :: next | prev | up | down | left | right | root | node | rightmost | leftmost. @@ -65,55 +43,72 @@ -export_type([info/1, is_branch_fun/1, make_node_fun/1, children_fun/1]). -export_type([operation/0]). +-ifdef(TEST). + +-export([as_list_of_maps/1]). + +-endif. + +-elvis([{elvis_style, dont_repeat_yourself, disable}]). +-elvis([{elvis_style, god_modules, disable}]). +-elvis([{elvis_style, no_throw, disable}]). + +-ifdef(TEST). + +-spec as_list_of_maps([zipper(any())]) -> [map()]. +as_list_of_maps(Children) -> + Children. + +-endif. + %% @doc Builds a new zipper with nodes of type T. -spec new(is_branch_fun(T), children_fun(T), make_node_fun(T), T) -> zipper(T). new(IsBranch, Children, MakeNode, Root) -> - #{spec => #{is_branch => IsBranch, - children => Children, - make_node => MakeNode - }, + #{spec => + #{is_branch => IsBranch, + children => Children, + make_node => MakeNode}, node => Root, - info => #{lefts => [], - rights => [], - parent_node => undefined, - parent_info => undefined - } - }. + info => + #{lefts => [], + rights => [], + parent_node => undefined, + parent_info => undefined}}. %% @doc Returns the zipper in the parent node, if possible. -spec up(zipper(T)) -> zipper(T) | undefined. up(#{info := #{parent_node := undefined}}) -> undefined; -up(Zipper = #{spec := #{make_node := MakeNode}, - node := Node, - info := #{lefts := Lefts, - rights := Rights, - parent_node := ParentNode, - parent_info := ParentInfo, - is_modified := true}}) -> +up(#{spec := #{make_node := MakeNode}, + node := Node, + info := + #{lefts := Lefts, + rights := Rights, + parent_node := ParentNode, + parent_info := ParentInfo, + is_modified := true}} = + Zipper) -> Children = lists:reverse(Lefts) ++ [Node | Rights], NewParentNode = MakeNode(ParentNode, Children), - Zipper#{node => NewParentNode, - info => ParentInfo}; -up(Zipper = #{info := #{parent_node := Parent, - parent_info := ParentInfo}}) -> - Zipper#{node => Parent, - info => ParentInfo}. + Zipper#{node => NewParentNode, info => ParentInfo}; +up(#{info := #{parent_node := Parent, parent_info := ParentInfo}} = Zipper) -> + Zipper#{node => Parent, info => ParentInfo}. %% @doc Returns the zipper in the first child, if any. -spec down(zipper(T)) -> zipper(T) | undefined. -down(Zipper = #{node := Node, - info := Info, - spec := #{children := Children}}) -> +down(#{node := Node, + info := Info, + spec := #{children := Children}} = + Zipper) -> case is_branch(Zipper) of true -> [NewNode | Rights] = Children(Node), Zipper#{node => NewNode, - info => #{lefts => [], - rights => Rights, - parent_node => Node, - parent_info => Info} - }; + info => + #{lefts => [], + rights => Rights, + parent_node => Node, + parent_info => Info}}; false -> undefined end. @@ -122,68 +117,59 @@ down(Zipper = #{node := Node, -spec left(zipper(T)) -> zipper(T) | undefined. left(#{info := #{lefts := []}}) -> undefined; -left(Zipper = #{info := Info = #{lefts := [NewNode | Lefts], - rights := Rights}, - node := Node}) -> - Zipper#{info => Info#{lefts => Lefts, - rights => [Node | Rights]}, - node => NewNode - }. +left(#{info := Info = #{lefts := [NewNode | Lefts], rights := Rights}, node := Node} = + Zipper) -> + Zipper#{info => Info#{lefts => Lefts, rights => [Node | Rights]}, node => NewNode}. %% @doc Returns the leftmost zipper in the current zipper. -spec leftmost(zipper(T)) -> zipper(T). -leftmost(Zipper = #{info := #{lefts := []}}) -> +leftmost(#{info := #{lefts := []}} = Zipper) -> Zipper; -leftmost(Zipper = #{info := Info = #{lefts := Lefts, - rights := Rights}, - node := Node}) -> +leftmost(#{info := Info = #{lefts := Lefts, rights := Rights}, node := Node} = Zipper) -> Fun = fun(Item, Acc) -> [Item | Acc] end, - [NewNode | NewRights] = lists:foldl(Fun, [Node | Rights], Lefts), - Zipper#{info => Info#{lefts => [], - rights => NewRights}, - node => NewNode}. + [NewNode | NewRights] = lists:foldl(Fun, [Node | Rights], Lefts), + Zipper#{info => Info#{lefts => [], rights => NewRights}, node => NewNode}. %% @doc Returns the zipper on the right, if any. -spec right(zipper(T)) -> zipper(T) | undefined. right(#{info := #{rights := []}}) -> undefined; -right(Zipper = #{info := Info = #{rights := [NewNode | Rights], - lefts := Lefts}, - node := Node}) -> - Zipper#{info => Info#{rights := Rights, - lefts := [Node | Lefts]}, - node := NewNode}. +right(#{info := Info = #{rights := [NewNode | Rights], lefts := Lefts}, node := Node} = + Zipper) -> + Zipper#{info => Info#{rights := Rights, lefts := [Node | Lefts]}, node := NewNode}. %% @doc Returns the rightmost zipper in the current zipper. -spec rightmost(zipper(T)) -> zipper(T). -rightmost(Zipper = #{info := Info = #{rights := Rights, - lefts := Lefts}, - node := Node}) -> +rightmost(#{info := Info = #{rights := Rights, lefts := Lefts}, node := Node} = Zipper) -> Fun = fun(Item, Acc) -> [Item | Acc] end, - [NewNode | NewLefts] = lists:foldl(Fun, [Node | Lefts], Rights), - Zipper#{info => Info#{lefts => NewLefts, - rights => []}, - node => NewNode}. + [NewNode | NewLefts] = lists:foldl(Fun, [Node | Lefts], Rights), + Zipper#{info => Info#{lefts => NewLefts, rights => []}, node => NewNode}. %% @doc Returns the next zipper. -spec next(zipper(T)) -> zipper(T). -next(Zipper = #{info := 'end'}) -> +next(#{info := 'end'} = Zipper) -> Zipper; next(Zipper) -> case {is_branch(Zipper), right(Zipper)} of - {true, _} -> down(Zipper); - {false, undefined} -> next_recur(Zipper); - {false, Right} -> Right + {true, _} -> + down(Zipper); + {false, undefined} -> + next_recur(Zipper); + {false, Right} -> + Right end. -spec next_recur(zipper(T)) -> zipper(T). next_recur(Zipper) -> case up(Zipper) of - undefined -> Zipper#{info => 'end'}; + undefined -> + Zipper#{info => 'end'}; UpZipper -> case right(UpZipper) of - undefined -> next_recur(UpZipper); - Next -> Next + undefined -> + next_recur(UpZipper); + Next -> + Next end end. @@ -196,7 +182,7 @@ is_end(_Zipper) -> %% @doc Returns the previous zipper. -spec prev(zipper(T)) -> zipper(T) | undefined. -prev(Zipper = #{info := #{lefts := []}}) -> +prev(#{info := #{lefts := []}} = Zipper) -> up(Zipper); prev(Zipper) -> prev_recur(left(Zipper)). @@ -204,7 +190,8 @@ prev(Zipper) -> -spec prev_recur(zipper(T)) -> zipper(T). prev_recur(Zipper) -> case down(Zipper) of - undefined -> Zipper; + undefined -> + Zipper; DownZipper -> RightMost = rightmost(DownZipper), prev_recur(RightMost) @@ -214,8 +201,10 @@ prev_recur(Zipper) -> -spec root(zipper(T)) -> T. root(Zipper) -> case up(Zipper) of - undefined -> node(Zipper); - Parent -> root(Parent) + undefined -> + node(Zipper); + Parent -> + root(Parent) end. %% @doc Traverses the zipper following the given list of operations. @@ -230,47 +219,43 @@ traverse([Op | Rest], Zipper) -> -spec insert_left(T, zipper(T)) -> zipper(T). insert_left(_, #{info := #{parent_node := undefined}}) -> throw(insert_at_top); -insert_left(Node, Zipper = #{info := Info = #{lefts := Lefts}}) -> - NewInfo = Info#{lefts => [Node | Lefts], - is_modified => true}, +insert_left(Node, #{info := Info = #{lefts := Lefts}} = Zipper) -> + NewInfo = Info#{lefts => [Node | Lefts], is_modified => true}, Zipper#{info => NewInfo}. %% @doc Inserts a node to the right of the current one. -spec insert_right(T, zipper(T)) -> zipper(T). insert_right(_, #{info := #{parent_node := undefined}}) -> throw(insert_at_top); -insert_right(Node, Zipper = #{info := Info = #{rights := Rights}}) -> - NewInfo = Info#{rights => [Node | Rights], - is_modified => true}, +insert_right(Node, #{info := Info = #{rights := Rights}} = Zipper) -> + NewInfo = Info#{rights => [Node | Rights], is_modified => true}, Zipper#{info => NewInfo}. %% @doc Replaces the current node. -spec replace(T, zipper(T)) -> zipper(T). -replace(Node, Zipper = #{info := Info}) -> - Zipper#{node => Node, - info => Info#{is_modified => true}}. +replace(Node, #{info := Info} = Zipper) -> + Zipper#{node => Node, info => Info#{is_modified => true}}. %% @doc Edits the current node by applying the given function. %% The parameters of said function will be [Node | Args]. -spec edit(fun((...) -> T), [term()], zipper(T)) -> zipper(T). -edit(Fun, Args, Zipper = #{node := Node, info := Info}) -> +edit(Fun, Args, #{node := Node, info := Info} = Zipper) -> NewNode = erlang:apply(Fun, [Node | Args]), - Zipper#{node => NewNode, - info => Info#{is_modified => true}}. + Zipper#{node => NewNode, info => Info#{is_modified => true}}. %% @doc Adds a node as the leftmost child of the current one. -spec insert_child(T, zipper(T)) -> zipper(T). -insert_child(Child, Zipper = #{spec := #{make_node := MakeNode}}) -> +insert_child(Child, #{spec := #{make_node := MakeNode}} = Zipper) -> Node = node(Zipper), - Children = children(Zipper), + Children = children(Zipper), NewNode = MakeNode(Node, [Child | Children]), replace(NewNode, Zipper). %% @doc Adds a node as the rightmost child of the current one. -spec append_child(T, zipper(T)) -> zipper(T). -append_child(Child, Zipper = #{spec := #{make_node := MakeNode}}) -> +append_child(Child, #{spec := #{make_node := MakeNode}} = Zipper) -> Node = node(Zipper), - Children = children(Zipper), + Children = children(Zipper), NewNode = MakeNode(Node, Children ++ [Child]), replace(NewNode, Zipper). @@ -280,26 +265,28 @@ append_child(Child, Zipper = #{spec := #{make_node := MakeNode}}) -> remove(#{info := #{parent_node := undefined}}) -> throw(remove_at_top); remove(#{spec := Spec = #{make_node := MakeNode}, - info := #{lefts := [], - rights := Rights, - parent_node := ParentNode, - parent_info := ParentInfo}}) -> + info := + #{lefts := [], + rights := Rights, + parent_node := ParentNode, + parent_info := ParentInfo}}) -> NewParentNode = MakeNode(ParentNode, Rights), #{spec => Spec, node => NewParentNode, info => ParentInfo#{is_modified => true}}; -remove(#{spec := Spec, - info := Info = #{lefts := [Node | Lefts]}}) -> - NewZipper = #{spec => Spec, - node => Node, - info => Info#{lefts => Lefts, - is_modified => true}}, +remove(#{spec := Spec, info := Info = #{lefts := [Node | Lefts]}}) -> + NewZipper = + #{spec => Spec, + node => Node, + info => Info#{lefts => Lefts, is_modified => true}}, prev_removed(NewZipper). prev_removed(Zipper) -> case down(Zipper) of - undefined -> Zipper; - Child -> prev_removed(rightmost(Child)) + undefined -> + Zipper; + Child -> + prev_removed(rightmost(Child)) end. %% Iteration @@ -316,7 +303,6 @@ map(Fun, Zipper) -> %% (after the current location of Zipper) is replaced with the %% result from applying Fun to the node as the first argument %% and Args as additional arguments. -%% @end -spec fmap(fun((...) -> T), [term()], zipper(T)) -> T. fmap(Fun, Args, Zipper) -> NewZipper = edit(Fun, Args, Zipper), @@ -345,12 +331,15 @@ fold(Fun, Acc, Zipper) -> %% @doc Returns a list of all the nodes in the zipper that match a predicate. -spec filter(fun((T) -> boolean()), zipper(T)) -> [T]. filter(Pred, Zipper) -> - FilterFun = fun(X, Acc) -> - case Pred(X) of - true -> [X | Acc]; - false -> Acc - end - end, + FilterFun = + fun(X, Acc) -> + case Pred(X) of + true -> + [X | Acc]; + false -> + Acc + end + end, fold(FilterFun, [], Zipper). %% @doc Returns the size of the zipper. @@ -368,10 +357,12 @@ node(#{node := Node}) -> %% @doc Returns the list of children zippers. -spec children(zipper(T)) -> [zipper(T)]. -children(Zipper = #{spec := #{children := Children}, node := Node}) -> +children(#{spec := #{children := Children}, node := Node} = Zipper) -> case is_branch(Zipper) of - true -> Children(Node); - false -> throw(children_on_leaf) + true -> + Children(Node); + false -> + throw(children_on_leaf) end. %% @doc Is this node a branch? diff --git a/src/zipper_default.erl b/src/zipper_default.erl index 4fdb6b5..94d4d14 100644 --- a/src/zipper_default.erl +++ b/src/zipper_default.erl @@ -2,6 +2,10 @@ -export([list/1, bin_tree/1, map_tree/2]). +-type bin_tree_node(T) :: nil | {T, bin_tree_node(T), bin_tree_node(T)}. + +-export_type([bin_tree_node/1]). + %% @doc Generates a zipper for lists. -spec list(list()) -> zipper:zipper(list()). list(Root) -> @@ -11,27 +15,22 @@ list(Root) -> zipper:new(IsBranchFun, ChildrenFun, MakeNodeFun, Root). %% @doc Generates a zipper for binary trees. --type bin_tree_node(T) :: nil | {T, bin_tree_node(T), bin_tree_node(T)}. -spec bin_tree(bin_tree_node(T)) -> zipper:zipper(bin_tree_node(T)). bin_tree(Root) -> - IsBranchFun = fun(Node) -> - is_tuple(Node) - andalso (tuple_size(Node) == 3) - end, + IsBranchFun = fun(Node) -> is_tuple(Node) andalso tuple_size(Node) == 3 end, ChildrenFun = fun({_, Left, Right}) -> [Left, Right] end, MakeNodeFun = fun(Node, [Left, Right]) -> {Node, Left, Right} end, zipper:new(IsBranchFun, ChildrenFun, MakeNodeFun, Root). %% @doc Generates a zipper for maps. --spec map_tree(M, CK) -> zipper:zipper(M) when M :: #{K => _}, CK :: K. +-spec map_tree(M, CK) -> zipper:zipper(M) + when M :: #{K => _}, + CK :: K. map_tree(Root, ChildrenKey) -> - IsBranchFun = fun (M) -> - is_map(M) - andalso maps:is_key(ChildrenKey, M) - andalso maps:get(ChildrenKey, M) /= [] - end, + IsBranchFun = + fun(M) -> + is_map(M) andalso maps:is_key(ChildrenKey, M) andalso maps:get(ChildrenKey, M) /= [] + end, ChildrenFun = fun(Node) -> maps:get(ChildrenKey, Node) end, - MakeNodeFun = fun(Node, Children) -> - maps:put(ChildrenKey, Children, Node) - end, + MakeNodeFun = fun(Node, Children) -> maps:put(ChildrenKey, Children, Node) end, zipper:new(IsBranchFun, ChildrenFun, MakeNodeFun, Root). diff --git a/test/cover.spec b/test/cover.spec index 93bda7f..53884c0 100644 --- a/test/cover.spec +++ b/test/cover.spec @@ -1,8 +1,2 @@ %% Specific modules to include in cover. -{ - incl_mods, - [ - zipper, - zipper_default - ] -}. +{incl_mods, [zipper, zipper_default]}. diff --git a/test/zipper_SUITE.erl b/test/zipper_SUITE.erl index 8e75f41..193fcf7 100644 --- a/test/zipper_SUITE.erl +++ b/test/zipper_SUITE.erl @@ -1,49 +1,25 @@ -module(zipper_SUITE). --export([ - all/0 - ]). - --export([ - %% Info - zipper_node/1, - zipper_children/1, - %% Traverse - zipper_root/1, - zipper_next/1, - zipper_prev/1, - zipper_up/1, - zipper_down/1, - zipper_left/1, - zipper_right/1, - zipper_leftmost/1, - zipper_rightmost/1, - %% Editing - zipper_insert_left/1, - zipper_insert_right/1, - zipper_replace/1, - zipper_edit/1, - zipper_insert_child/1, - zipper_append_child/1, - zipper_remove/1, - %% Iteration - zipper_map/1, - zipper_fmap/1, - zipper_filter/1, - zipper_size/1 - ]). - --define(EXCLUDED_FUNS, - [ - module_info, - all, - test, - init_per_suite, - end_per_suite - ]). +-export([all/0]). +%% Info +-export([zipper_node/1, zipper_children/1]). +%% Traverse +-export([zipper_root/1, zipper_next/1, zipper_prev/1, zipper_up/1, zipper_down/1, + zipper_left/1, zipper_right/1, zipper_leftmost/1, zipper_rightmost/1]). +%% Editing +-export([zipper_insert_left/1, zipper_insert_right/1, zipper_replace/1, zipper_edit/1, + zipper_insert_child/1, zipper_append_child/1, zipper_remove/1]). +%% Iteration +-export([zipper_map/1, zipper_fmap/1, zipper_filter/1, zipper_size/1]). + +-define(EXCLUDED_FUNS, [module_info, all, test, init_per_suite, end_per_suite]). -type config() :: [{atom(), term()}]. +-export_type([config/0]). + +-elvis([{elvis_style, dont_repeat_yourself, disable}]). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Common test %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -68,15 +44,19 @@ zipper_node(_Config) -> zipper_children(_Config) -> Root = root(), Zipper = zipper_default:map_tree(Root, children), - Children = zipper:children(Zipper), + Children = + zipper:as_list_of_maps( + zipper:children(Zipper)), Children = maps:get(children, Root), ArgZipper = zipper:traverse([next, next], Zipper), - ok = try - zipper:children(ArgZipper) - catch - throw:children_on_leaf -> ok - end, + ok = + try + zipper:children(ArgZipper) + catch + children_on_leaf -> + ok + end, {comment, ""}. -spec zipper_root(config()) -> {comment, string()}. @@ -92,23 +72,23 @@ zipper_down(_Config) -> Zipper = zipper_default:map_tree(root(), children), Zipper1 = zipper:down(Zipper), America = zipper:node(Zipper1), - America = #{type => continent, - attrs => #{name => "America"}, - children => [ - #{type => country, - attrs => #{name => "Argentina"}, - children => []}, - #{type => country, - attrs => #{name => "Brasil"}, - children => []} - ] - }, + America = + #{type => continent, + attrs => #{name => "America"}, + children => + [#{type => country, + attrs => #{name => "Argentina"}, + children => []}, + #{type => country, + attrs => #{name => "Brasil"}, + children => []}]}, Zipper2 = zipper:down(Zipper1), Argentina = zipper:node(Zipper2), - Argentina = #{type => country, - attrs => #{name => "Argentina"}, - children => []}, + Argentina = + #{type => country, + attrs => #{name => "Argentina"}, + children => []}, undefined = zipper:down(Zipper2), {comment, ""}. @@ -121,23 +101,23 @@ zipper_up(_Config) -> Zipper1 = zipper:traverse([down, down], Zipper), Argentina = zipper:node(Zipper1), - Argentina = #{type => country, - attrs => #{name => "Argentina"}, - children => []}, + Argentina = + #{type => country, + attrs => #{name => "Argentina"}, + children => []}, Zipper2 = zipper:up(Zipper1), America = zipper:node(Zipper2), - America = #{type => continent, - attrs => #{name => "America"}, - children => [ - #{type => country, - attrs => #{name => "Argentina"}, - children => []}, - #{type => country, - attrs => #{name => "Brasil"}, - children => []} - ] - }, + America = + #{type => continent, + attrs => #{name => "America"}, + children => + [#{type => country, + attrs => #{name => "Argentina"}, + children => []}, + #{type => country, + attrs => #{name => "Brasil"}, + children => []}]}, Zipper3 = zipper:up(Zipper2), Earth = zipper:node(Zipper3), @@ -150,15 +130,17 @@ zipper_right(_Config) -> Zipper1 = zipper:traverse([down, down], Zipper), Argentina = zipper:node(Zipper1), - Argentina = #{type => country, - attrs => #{name => "Argentina"}, - children => []}, + Argentina = + #{type => country, + attrs => #{name => "Argentina"}, + children => []}, Zipper2 = zipper:right(Zipper1), Brasil = zipper:node(Zipper2), - Brasil = #{type => country, - attrs => #{name => "Brasil"}, - children => []}, + Brasil = + #{type => country, + attrs => #{name => "Brasil"}, + children => []}, {comment, ""}. -spec zipper_left(config()) -> {comment, string()}. @@ -167,15 +149,17 @@ zipper_left(_Config) -> Zipper1 = zipper:traverse([down, down, right], Zipper), Brasil = zipper:node(Zipper1), - Brasil = #{type => country, - attrs => #{name => "Brasil"}, - children => []}, + Brasil = + #{type => country, + attrs => #{name => "Brasil"}, + children => []}, Zipper2 = zipper:left(Zipper1), Argentina = zipper:node(Zipper2), - Argentina = #{type => country, - attrs => #{name => "Argentina"}, - children => []}, + Argentina = + #{type => country, + attrs => #{name => "Argentina"}, + children => []}, undefined = zipper:left(Zipper2), {comment, ""}. @@ -188,41 +172,44 @@ zipper_next(_Config) -> Zipper2 = zipper:next(Zipper1), Argentina = zipper:node(Zipper2), - Argentina = #{type => country, - attrs => #{name => "Argentina"}, - children => []}, + Argentina = + #{type => country, + attrs => #{name => "Argentina"}, + children => []}, Zipper3 = zipper:next(Zipper2), Brasil = zipper:node(Zipper3), - Brasil = #{type => country, - attrs => #{name => "Brasil"}, - children => []}, + Brasil = + #{type => country, + attrs => #{name => "Brasil"}, + children => []}, Zipper4 = zipper:next(Zipper3), Europe = zipper:node(Zipper4), - Europe = #{type => continent, - attrs => #{name => "Europe"}, - children => [ - #{type => country, - attrs => #{name => "Sweden"}, - children => []}, - #{type => country, - attrs => #{name => "England"}, - children => []} - ] - }, + Europe = + #{type => continent, + attrs => #{name => "Europe"}, + children => + [#{type => country, + attrs => #{name => "Sweden"}, + children => []}, + #{type => country, + attrs => #{name => "England"}, + children => []}]}, Zipper5 = zipper:next(Zipper4), Sweden = zipper:node(Zipper5), - Sweden = #{type => country, - attrs => #{name => "Sweden"}, - children => []}, + Sweden = + #{type => country, + attrs => #{name => "Sweden"}, + children => []}, Zipper6 = zipper:next(Zipper5), England = zipper:node(Zipper6), - England = #{type => country, - attrs => #{name => "England"}, - children => []}, + England = + #{type => country, + attrs => #{name => "England"}, + children => []}, ZipperEnd = zipper:next(Zipper6), true = zipper:is_end(ZipperEnd), @@ -239,55 +226,57 @@ zipper_prev(_Config) -> Zipper1 = zipper:traverse([next, next, next, next, next, next], Zipper), England = zipper:node(Zipper1), - England = #{type => country, - attrs => #{name => "England"}, - children => []}, + England = + #{type => country, + attrs => #{name => "England"}, + children => []}, Zipper2 = zipper:prev(Zipper1), Sweden = zipper:node(Zipper2), - Sweden = #{type => country, - attrs => #{name => "Sweden"}, - children => []}, + Sweden = + #{type => country, + attrs => #{name => "Sweden"}, + children => []}, Zipper3 = zipper:prev(Zipper2), Europe = zipper:node(Zipper3), - Europe = #{type => continent, - attrs => #{name => "Europe"}, - children => [ - #{type => country, - attrs => #{name => "Sweden"}, - children => []}, - #{type => country, - attrs => #{name => "England"}, - children => []} - ] - }, + Europe = + #{type => continent, + attrs => #{name => "Europe"}, + children => + [#{type => country, + attrs => #{name => "Sweden"}, + children => []}, + #{type => country, + attrs => #{name => "England"}, + children => []}]}, Zipper4 = zipper:prev(Zipper3), Brasil = zipper:node(Zipper4), - Brasil = #{type => country, - attrs => #{name => "Brasil"}, - children => []}, + Brasil = + #{type => country, + attrs => #{name => "Brasil"}, + children => []}, Zipper5 = zipper:prev(Zipper4), Argentina = zipper:node(Zipper5), - Argentina = #{type => country, - attrs => #{name => "Argentina"}, - children => []}, + Argentina = + #{type => country, + attrs => #{name => "Argentina"}, + children => []}, Zipper6 = zipper:prev(Zipper5), America = zipper:node(Zipper6), - America = #{type => continent, - attrs => #{name => "America"}, - children => [ - #{type => country, - attrs => #{name => "Argentina"}, - children => []}, - #{type => country, - attrs => #{name => "Brasil"}, - children => []} - ] - }, + America = + #{type => continent, + attrs => #{name => "America"}, + children => + [#{type => country, + attrs => #{name => "Argentina"}, + children => []}, + #{type => country, + attrs => #{name => "Brasil"}, + children => []}]}, Zipper7 = zipper:prev(Zipper6), Earth = zipper:node(Zipper7), @@ -315,25 +304,27 @@ zipper_rightmost(_Config) -> -spec zipper_insert_left(config()) -> {comment, string()}. zipper_insert_left(_Config) -> Zipper = zipper_default:map_tree(root(), children), - ok = try - zipper:insert_left(#{}, Zipper) - catch - _:insert_at_top -> ok - end, - - AfricaNode = #{type => continent, - attrs => #{name => "Africa"}, - children => [#{type => country, - attrs => #{name => "Kenya"}, - children => []}, - #{type => country, - attrs => #{name => "Egypt"}, - children => []}, - #{type => country, - attrs => #{name => "South Africa"}, - children => []} - ] - }, + ok = + try + zipper:insert_left(#{}, Zipper) + catch + _:insert_at_top -> + ok + end, + + AfricaNode = + #{type => continent, + attrs => #{name => "Africa"}, + children => + [#{type => country, + attrs => #{name => "Kenya"}, + children => []}, + #{type => country, + attrs => #{name => "Egypt"}, + children => []}, + #{type => country, + attrs => #{name => "South Africa"}, + children => []}]}, America = zipper:traverse([down], Zipper), AmericaInserted = zipper:insert_left(AfricaNode, America), @@ -349,25 +340,27 @@ zipper_insert_left(_Config) -> -spec zipper_insert_right(config()) -> {comment, string()}. zipper_insert_right(_Config) -> Zipper = zipper_default:map_tree(root(), children), - ok = try - zipper:insert_right(#{}, Zipper) - catch - _:insert_at_top -> ok - end, - - AsiaNode = #{type => continent, - attrs => #{name => "Asia"}, - children => [#{type => country, - attrs => #{name => "China"}, - children => []}, - #{type => country, - attrs => #{name => "India"}, - children => []}, - #{type => country, - attrs => #{name => "Israel"}, - children => []} - ] - }, + ok = + try + zipper:insert_right(#{}, Zipper) + catch + _:insert_at_top -> + ok + end, + + AsiaNode = + #{type => continent, + attrs => #{name => "Asia"}, + children => + [#{type => country, + attrs => #{name => "China"}, + children => []}, + #{type => country, + attrs => #{name => "India"}, + children => []}, + #{type => country, + attrs => #{name => "Israel"}, + children => []}]}, America = zipper:traverse([down], Zipper), AmericaInserted = zipper:insert_right(AsiaNode, America), @@ -383,26 +376,27 @@ zipper_insert_right(_Config) -> -spec zipper_replace(config()) -> {comment, string()}. zipper_replace(_Config) -> Zipper = zipper_default:map_tree(root(), children), - MarsNode = #{type => planet, - attrs => #{name => "Mars"}, - children => []}, + MarsNode = + #{type => planet, + attrs => #{name => "Mars"}, + children => []}, ReplacedEarth = zipper:replace(MarsNode, Zipper), MarsNode = zipper:root(ReplacedEarth), - AsiaNode = #{type => continent, - attrs => #{name => "Asia"}, - children => [#{type => country, - attrs => #{name => "China"}, - children => []}, - #{type => country, - attrs => #{name => "India"}, - children => []}, - #{type => country, - attrs => #{name => "Israel"}, - children => []} - ] - }, + AsiaNode = + #{type => continent, + attrs => #{name => "Asia"}, + children => + [#{type => country, + attrs => #{name => "China"}, + children => []}, + #{type => country, + attrs => #{name => "India"}, + children => []}, + #{type => country, + attrs => #{name => "Israel"}, + children => []}]}, Europe = zipper:traverse([down, right], Zipper), ReplacedEurope = zipper:replace(AsiaNode, Europe), NewEarthNode = zipper:root(ReplacedEurope), @@ -414,9 +408,8 @@ zipper_replace(_Config) -> -spec zipper_edit(config()) -> {comment, string()}. zipper_edit(_Config) -> Zipper = zipper_default:map_tree(root(), children), - EditNameFun = fun (Node = #{attrs := Attrs}, Name) -> - Node#{attrs => Attrs#{name => Name}} - end, + EditNameFun = + fun(#{attrs := Attrs} = Node, Name) -> Node#{attrs => Attrs#{name => Name}} end, RenamedEarth = zipper:edit(EditNameFun, ["Big Blue Ball"], Zipper), #{attrs := #{name := "Big Blue Ball"}} = zipper:root(RenamedEarth), @@ -432,51 +425,40 @@ zipper_edit(_Config) -> -spec zipper_fmap(config()) -> {comment, string()}. zipper_fmap(_Config) -> Zipper = zipper_default:map_tree(root(), children), - EditNameFun = fun (Node = #{attrs := Attrs}, Name) -> - Node#{attrs => Attrs#{name => Name}} - end, + EditNameFun = + fun(#{attrs := Attrs} = Node, Name) -> Node#{attrs => Attrs#{name => Name}} end, RenamedEarth = zipper:fmap(EditNameFun, ["X"], Zipper), #{attrs := #{name := "X"}, - children := [ - #{attrs := #{name := "X"}, - children := [ - #{attrs := #{name := "X"}, - children := []}, - #{attrs := #{name := "X"}, - children := []} - ] - }, - #{attrs := #{name := "X"}, - children := [ - #{attrs := #{name := "X"}, - children := []}, - #{attrs := #{name := "X"}, - children := []} - ] - } - ] - } = RenamedEarth, + children := + [#{attrs := #{name := "X"}, + children := + [#{attrs := #{name := "X"}, children := []}, + #{attrs := #{name := "X"}, children := []}]}, + #{attrs := #{name := "X"}, + children := + [#{attrs := #{name := "X"}, children := []}, + #{attrs := #{name := "X"}, children := []}]}]} = + RenamedEarth, {comment, ""}. - -spec zipper_insert_child(config()) -> {comment, string()}. zipper_insert_child(_Config) -> Zipper = zipper_default:map_tree(root(), children), - AsiaNode = #{type => continent, - attrs => #{name => "Asia"}, - children => [#{type => country, - attrs => #{name => "China"}, - children => []}, - #{type => country, - attrs => #{name => "India"}, - children => []}, - #{type => country, - attrs => #{name => "Israel"}, - children => []} - ] - }, + AsiaNode = + #{type => continent, + attrs => #{name => "Asia"}, + children => + [#{type => country, + attrs => #{name => "China"}, + children => []}, + #{type => country, + attrs => #{name => "India"}, + children => []}, + #{type => country, + attrs => #{name => "Israel"}, + children => []}]}, AsiaInserted = zipper:insert_child(AsiaNode, Zipper), EarthWithAsiaNode = zipper:root(AsiaInserted), @@ -484,9 +466,10 @@ zipper_insert_child(_Config) -> Asia = zipper:down(EarthWithAsia), #{attrs := #{name := "Asia"}} = zipper:node(Asia), - UruguayNode = #{type => country, - attrs => #{name => "Uruguay"}, - children => []}, + UruguayNode = + #{type => country, + attrs => #{name => "Uruguay"}, + children => []}, America = zipper:traverse([down, right], EarthWithAsia), #{attrs := #{name := "America"}} = zipper:node(America), @@ -501,19 +484,19 @@ zipper_insert_child(_Config) -> zipper_append_child(_Config) -> Zipper = zipper_default:map_tree(root(), children), - AsiaNode = #{type => continent, - attrs => #{name => "Asia"}, - children => [#{type => country, - attrs => #{name => "China"}, - children => []}, - #{type => country, - attrs => #{name => "India"}, - children => []}, - #{type => country, - attrs => #{name => "Israel"}, - children => []} - ] - }, + AsiaNode = + #{type => continent, + attrs => #{name => "Asia"}, + children => + [#{type => country, + attrs => #{name => "China"}, + children => []}, + #{type => country, + attrs => #{name => "India"}, + children => []}, + #{type => country, + attrs => #{name => "Israel"}, + children => []}]}, AsiaInserted = zipper:append_child(AsiaNode, Zipper), EarthWithAsiaNode = zipper:root(AsiaInserted), @@ -521,9 +504,10 @@ zipper_append_child(_Config) -> Asia = zipper:traverse([down, rightmost], EarthWithAsia), #{attrs := #{name := "Asia"}} = zipper:node(Asia), - UruguayNode = #{type => country, - attrs => #{name => "Uruguay"}, - children => []}, + UruguayNode = + #{type => country, + attrs => #{name => "Uruguay"}, + children => []}, America = zipper:traverse([down], EarthWithAsia), #{attrs := #{name := "America"}} = zipper:node(America), @@ -537,11 +521,13 @@ zipper_append_child(_Config) -> -spec zipper_remove(config()) -> {comment, string()}. zipper_remove(_Config) -> Zipper = zipper_default:map_tree(root(), children), - ok = try - zipper:remove(Zipper) - catch - _:remove_at_top -> ok - end, + ok = + try + zipper:remove(Zipper) + catch + _:remove_at_top -> + ok + end, America = zipper:down(Zipper), AmericaRemoved = zipper:remove(America), @@ -563,9 +549,8 @@ zipper_map(_Config) -> Zipper = zipper_default:map_tree(root(), children), GetNameFun = fun(#{attrs := #{name := Name}}) -> Name end, - ["Earth", - "America", "Argentina", "Brasil", - "Europe", "Sweden", "England"] = zipper:map(GetNameFun, Zipper), + ["Earth", "America", "Argentina", "Brasil", "Europe", "Sweden", "England"] = + zipper:map(GetNameFun, Zipper), {comment, ""}. -spec zipper_filter(config()) -> {comment, string()}. @@ -575,7 +560,8 @@ zipper_filter(_Config) -> FilterFun = fun(#{attrs := #{name := Name}}) -> Name == "Brasil" end, [#{type := country, attrs := #{name := "Brasil"}, - children := []}] = zipper:filter(FilterFun, Zipper), + children := []}] = + zipper:filter(FilterFun, Zipper), {comment, ""}. -spec zipper_size(config()) -> {comment, string()}. @@ -591,28 +577,22 @@ zipper_size(_Config) -> root() -> #{type => planet, attrs => #{name => "Earth"}, - children => [ - #{type => continent, - attrs => #{name => "America"}, - children => [ - #{type => country, - attrs => #{name => "Argentina"}, - children => []}, - #{type => country, - attrs => #{name => "Brasil"}, - children => []} - ] - }, - #{type => continent, - attrs => #{name => "Europe"}, - children => [ - #{type => country, - attrs => #{name => "Sweden"}, - children => []}, - #{type => country, - attrs => #{name => "England"}, - children => []} - ] - } - ] - }. + children => + [#{type => continent, + attrs => #{name => "America"}, + children => + [#{type => country, + attrs => #{name => "Argentina"}, + children => []}, + #{type => country, + attrs => #{name => "Brasil"}, + children => []}]}, + #{type => continent, + attrs => #{name => "Europe"}, + children => + [#{type => country, + attrs => #{name => "Sweden"}, + children => []}, + #{type => country, + attrs => #{name => "England"}, + children => []}]}]}. diff --git a/test/zipper_default_SUITE.erl b/test/zipper_default_SUITE.erl index 17cd6f2..a4e12fd 100644 --- a/test/zipper_default_SUITE.erl +++ b/test/zipper_default_SUITE.erl @@ -1,26 +1,16 @@ -module(zipper_default_SUITE). --export([ - all/0 - ]). +-export([all/0]). +-export([zipper_list/1, zipper_bin_tree/1, zipper_map_tree/1]). --export([ - zipper_list/1, - zipper_bin_tree/1, - zipper_map_tree/1 - ]). - --define(EXCLUDED_FUNS, - [ - module_info, - all, - test, - init_per_suite, - end_per_suite - ]). +-define(EXCLUDED_FUNS, [module_info, all, test, init_per_suite, end_per_suite]). -type config() :: [{atom(), term()}]. +-export_type([config/0]). + +-elvis([{elvis_style, dont_repeat_yourself, disable}]). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Common test %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -50,11 +40,7 @@ zipper_list(_Config) -> -spec zipper_bin_tree(config()) -> {comment, string()}. zipper_bin_tree(_Config) -> - Root = {1, - {2, 3, 4}, - {5, - {6, 7, 8}, - {9, 10, 11}}}, + Root = {1, {2, 3, 4}, {5, {6, 7, 8}, {9, 10, 11}}}, Zipper = zipper_default:bin_tree(Root), Root = zipper:node(Zipper), @@ -68,33 +54,28 @@ zipper_bin_tree(_Config) -> -spec zipper_map_tree(config()) -> {comment, string()}. zipper_map_tree(_Config) -> - Root = #{type => planet, - name => "Earth", - children => [ - #{type => continent, - name => "America", - children => [ - #{type => country, - name => "Argentina", - children => []}, - #{type => country, - name => "Brasil", - children => []} - ] - }, - #{type => continent, - name => "Europe", - children => [ - #{type => country, - name => "Sweden", - children => []}, - #{type => country, - name => "England", - children => []} - ] - } - ] - }, + Root = + #{type => planet, + name => "Earth", + children => + [#{type => continent, + name => "America", + children => + [#{type => country, + name => "Argentina", + children => []}, + #{type => country, + name => "Brasil", + children => []}]}, + #{type => continent, + name => "Europe", + children => + [#{type => country, + name => "Sweden", + children => []}, + #{type => country, + name => "England", + children => []}]}]}, Zipper = zipper_default:map_tree(Root, children), #{name := "Earth"} = zipper:node(Zipper), #{name := "America"} = zipper:traverse([down, node], Zipper), @@ -102,6 +83,5 @@ zipper_map_tree(_Config) -> #{name := "Brasil"} = zipper:traverse([down, down, right, node], Zipper), #{name := "Europe"} = zipper:traverse([down, right, node], Zipper), #{name := "Sweden"} = zipper:traverse([down, right, down, node], Zipper), - #{name := "England"} = zipper:traverse([down, right, down, right, node], - Zipper), + #{name := "England"} = zipper:traverse([down, right, down, right, node], Zipper), {comment, ""}. diff --git a/test/zipper_meta_SUITE.erl b/test/zipper_meta_SUITE.erl deleted file mode 100644 index 81a55ec..0000000 --- a/test/zipper_meta_SUITE.erl +++ /dev/null @@ -1,50 +0,0 @@ --module(zipper_meta_SUITE). --author('euen@inaka.net'). - --export([all/0]). --export([dialyzer/1, xref/1]). - --type config() :: [{atom(), term()}]. - --spec all() -> [dialyzer | xref]. -all() -> [dialyzer, xref]. - --spec dialyzer(config()) -> {comment, []}. -dialyzer(_Config) -> - BaseDir = code:lib_dir(zipper), - DefaultRebar3PltLoc = filename:join(BaseDir, "../../../default"), - Plts = filelib:wildcard(filename:join(DefaultRebar3PltLoc, "*_plt")), - Dirs = [filename:join(BaseDir, Dir) || Dir <- ["ebin", "test"]], - Warnings = [error_handling, race_conditions, unmatched_returns], - ct:comment("Dialyzer must emit no warnings"), - Opts = - [ {analysis_type, succ_typings} - , {plts, Plts} - , {files_rec, Dirs} - , {check_plt, true} - , {warnings, Warnings} - , {get_warnings, true} - ], - [] = [dialyzer:format_warning(W, basename) || W <- dialyzer:run(Opts)], - {comment, ""}. - --spec xref(config()) -> {comment, []}. -xref(_Config) -> - BaseDir = code:lib_dir(zipper), - Dirs = [filename:join(BaseDir, Dir) || Dir <- ["ebin", "test"]], - XrefConfig = #{ dirs => Dirs - , xref_defaults => - [ {verbose, true} - , {recurse, true} - , {builtins, true} - ] - }, - Checks = [ undefined_function_calls - , locals_not_used - , deprecated_function_calls - ], - ct:comment("There are no Warnings"), - [] = - [ Warning - || Check <- Checks, Warning <- xref_runner:check(Check, XrefConfig)], - {comment, ""}. \ No newline at end of file