From b7193f28a10cc4cf9de297104cd54f984cdab9f3 Mon Sep 17 00:00:00 2001 From: "Paulo F. Oliveira" Date: Fri, 4 Aug 2023 17:56:09 +0100 Subject: [PATCH 01/22] Preemptively accept format results Before starting to change files in the scope of this branch --- src/zipper.erl | 257 ++++++++-------- src/zipper_default.erl | 23 +- test/cover.spec | 8 +- test/zipper_SUITE.erl | 549 ++++++++++++++++------------------ test/zipper_default_SUITE.erl | 78 ++--- test/zipper_meta_SUITE.erl | 63 ++-- 6 files changed, 446 insertions(+), 532 deletions(-) diff --git a/src/zipper.erl b/src/zipper.erl index 5b710f2..23f74e0 100644 --- a/src/zipper.erl +++ b/src/zipper.erl @@ -3,60 +3,41 @@ %%% flexibility. %%% @end -module(zipper). + -compile({no_auto_import, [node/1]}). --export([ - %% Creation - new/4, +-export([new/4, up/1, down/1, left/1, leftmost/1, right/1, rightmost/1, next/1, is_end/1, + prev/1, root/1, traverse/2, insert_left/2, insert_right/2, replace/2, edit/3, + insert_child/2, append_child/2, remove/1, map/2, fmap/3, filter/2, fold/3, size/1, node/1, + children/1, is_branch/1]). + + %% Creation + %% 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 - ]). -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. @@ -68,52 +49,51 @@ %% @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(Zipper = + #{spec := #{make_node := MakeNode}, + node := Node, + info := + #{lefts := Lefts, + rights := Rights, + parent_node := ParentNode, + parent_info := ParentInfo, + is_modified := true}}) -> 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(Zipper = #{info := #{parent_node := Parent, parent_info := ParentInfo}}) -> + 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(Zipper = + #{node := Node, + info := Info, + spec := #{children := Children}}) -> 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,48 +102,33 @@ 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(Zipper = + #{info := Info = #{lefts := [NewNode | Lefts], rights := Rights}, node := Node}) -> + 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 := []}}) -> Zipper; -leftmost(Zipper = #{info := Info = #{lefts := Lefts, - rights := Rights}, - node := Node}) -> +leftmost(Zipper = #{info := Info = #{lefts := Lefts, rights := Rights}, node := Node}) -> 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(Zipper = + #{info := Info = #{rights := [NewNode | Rights], lefts := Lefts}, node := Node}) -> + 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(Zipper = #{info := Info = #{rights := Rights, lefts := Lefts}, node := Node}) -> 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). @@ -171,19 +136,25 @@ next(Zipper = #{info := 'end'}) -> 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. @@ -204,7 +175,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 +186,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. @@ -231,8 +205,7 @@ traverse([Op | Rest], Zipper) -> 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}, + NewInfo = Info#{lefts => [Node | Lefts], is_modified => true}, Zipper#{info => NewInfo}. %% @doc Inserts a node to the right of the current one. @@ -240,29 +213,26 @@ insert_left(Node, Zipper = #{info := Info = #{lefts := Lefts}}) -> 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}, + 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}}. + 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}) -> 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}}) -> Node = node(Zipper), - Children = children(Zipper), + Children = children(Zipper), NewNode = MakeNode(Node, [Child | Children]), replace(NewNode, Zipper). @@ -270,7 +240,7 @@ insert_child(Child, Zipper = #{spec := #{make_node := MakeNode}}) -> -spec append_child(T, zipper(T)) -> zipper(T). append_child(Child, Zipper = #{spec := #{make_node := MakeNode}}) -> Node = node(Zipper), - Children = children(Zipper), + Children = children(Zipper), NewNode = MakeNode(Node, Children ++ [Child]), replace(NewNode, Zipper). @@ -280,26 +250,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 @@ -345,12 +317,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. @@ -370,8 +345,10 @@ node(#{node := Node}) -> -spec children(zipper(T)) -> [zipper(T)]. children(Zipper = #{spec := #{children := Children}, node := Node}) -> 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..e96a8e2 100644 --- a/src/zipper_default.erl +++ b/src/zipper_default.erl @@ -12,26 +12,23 @@ list(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..061fabd 100644 --- a/test/zipper_SUITE.erl +++ b/test/zipper_SUITE.erl @@ -1,46 +1,21 @@ -module(zipper_SUITE). --export([ - all/0 - ]). - --export([ - %% Info - zipper_node/1, - zipper_children/1, +-export([all/0]). +-export([zipper_node/1, zipper_children/1, 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, 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, + zipper_map/1, zipper_fmap/1, zipper_filter/1, zipper_size/1]). + + %% Info + %% 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 - ]). + +-define(EXCLUDED_FUNS, [module_info, all, test, init_per_suite, end_per_suite]). -type config() :: [{atom(), term()}]. @@ -72,11 +47,13 @@ zipper_children(_Config) -> 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 +69,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 +98,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 +127,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 +146,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 +169,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 +223,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 +301,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 +337,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 +373,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 +405,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(Node = #{attrs := Attrs}, 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 +422,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(Node = #{attrs := Attrs}, 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 +463,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 +481,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 +501,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 +518,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 +546,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 +557,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 +574,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..e8c518c 100644 --- a/test/zipper_default_SUITE.erl +++ b/test/zipper_default_SUITE.erl @@ -1,23 +1,9 @@ -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()}]. @@ -50,11 +36,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 +50,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 +79,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 index 81a55ec..ec38619 100644 --- a/test/zipper_meta_SUITE.erl +++ b/test/zipper_meta_SUITE.erl @@ -1,4 +1,5 @@ -module(zipper_meta_SUITE). + -author('euen@inaka.net'). -export([all/0]). @@ -7,44 +8,36 @@ -type config() :: [{atom(), term()}]. -spec all() -> [dialyzer | xref]. -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, ""}. + 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 + 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, ""}. From 4bc1263e02c50261b4c52cdba59ed7e555b588d8 Mon Sep 17 00:00:00 2001 From: "Paulo F. Oliveira" Date: Fri, 4 Aug 2023 18:42:28 +0100 Subject: [PATCH 02/22] Make Elvis rock --- src/zipper.erl | 64 +++++++++++++++++++---------------- src/zipper_default.erl | 6 ++-- test/zipper_SUITE.erl | 8 +++-- test/zipper_default_SUITE.erl | 4 +++ test/zipper_meta_SUITE.erl | 4 +-- 5 files changed, 50 insertions(+), 36 deletions(-) diff --git a/src/zipper.erl b/src/zipper.erl index 23f74e0..7ab7c4c 100644 --- a/src/zipper.erl +++ b/src/zipper.erl @@ -46,6 +46,10 @@ -export_type([info/1, is_branch_fun/1, make_node_fun/1, children_fun/1]). -export_type([operation/0]). +-elvis([{elvis_style, dont_repeat_yourself, disable}]). +-elvis([{elvis_style, god_modules, disable}]). +-elvis([{elvis_style, no_throw, disable}]). + %% @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) -> @@ -64,27 +68,27 @@ new(IsBranch, Children, MakeNode, Root) -> -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}}) -> +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), @@ -102,15 +106,15 @@ down(Zipper = -spec left(zipper(T)) -> zipper(T) | undefined. left(#{info := #{lefts := []}}) -> undefined; -left(Zipper = - #{info := Info = #{lefts := [NewNode | Lefts], rights := Rights}, node := Node}) -> +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}. @@ -119,20 +123,20 @@ leftmost(Zipper = #{info := Info = #{lefts := Lefts, rights := Rights}, node := -spec right(zipper(T)) -> zipper(T) | undefined. right(#{info := #{rights := []}}) -> undefined; -right(Zipper = - #{info := Info = #{rights := [NewNode | Rights], lefts := Lefts}, node := Node}) -> +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}. %% @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 @@ -167,7 +171,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 +208,7 @@ 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}}) -> +insert_left(Node, #{info := Info = #{lefts := Lefts}} = Zipper) -> NewInfo = Info#{lefts => [Node | Lefts], is_modified => true}, Zipper#{info => NewInfo}. @@ -212,25 +216,25 @@ insert_left(Node, Zipper = #{info := Info = #{lefts := Lefts}}) -> -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}}) -> +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}) -> +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}}. %% @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), NewNode = MakeNode(Node, [Child | Children]), @@ -238,7 +242,7 @@ insert_child(Child, Zipper = #{spec := #{make_node := MakeNode}}) -> %% @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), NewNode = MakeNode(Node, Children ++ [Child]), @@ -343,7 +347,7 @@ 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); diff --git a/src/zipper_default.erl b/src/zipper_default.erl index e96a8e2..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,8 +15,6 @@ 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, diff --git a/test/zipper_SUITE.erl b/test/zipper_SUITE.erl index 061fabd..e562701 100644 --- a/test/zipper_SUITE.erl +++ b/test/zipper_SUITE.erl @@ -19,6 +19,10 @@ -type config() :: [{atom(), term()}]. +-export_type([config/0]). + +-elvis([{elvis_style, dont_repeat_yourself, disable}]). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Common test %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -406,7 +410,7 @@ zipper_replace(_Config) -> zipper_edit(_Config) -> Zipper = zipper_default:map_tree(root(), children), EditNameFun = - fun(Node = #{attrs := Attrs}, Name) -> Node#{attrs => Attrs#{name => Name}} end, + 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), @@ -423,7 +427,7 @@ zipper_edit(_Config) -> zipper_fmap(_Config) -> Zipper = zipper_default:map_tree(root(), children), EditNameFun = - fun(Node = #{attrs := Attrs}, Name) -> Node#{attrs => Attrs#{name => Name}} end, + fun(#{attrs := Attrs} = Node, Name) -> Node#{attrs => Attrs#{name => Name}} end, RenamedEarth = zipper:fmap(EditNameFun, ["X"], Zipper), #{attrs := #{name := "X"}, diff --git a/test/zipper_default_SUITE.erl b/test/zipper_default_SUITE.erl index e8c518c..a4e12fd 100644 --- a/test/zipper_default_SUITE.erl +++ b/test/zipper_default_SUITE.erl @@ -7,6 +7,10 @@ -type config() :: [{atom(), term()}]. +-export_type([config/0]). + +-elvis([{elvis_style, dont_repeat_yourself, disable}]). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Common test %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/test/zipper_meta_SUITE.erl b/test/zipper_meta_SUITE.erl index ec38619..0808503 100644 --- a/test/zipper_meta_SUITE.erl +++ b/test/zipper_meta_SUITE.erl @@ -1,12 +1,12 @@ -module(zipper_meta_SUITE). --author('euen@inaka.net'). - -export([all/0]). -export([dialyzer/1, xref/1]). -type config() :: [{atom(), term()}]. +-export_type([config/0]). + -spec all() -> [dialyzer | xref]. all() -> [dialyzer, xref]. From ce966bfa0a844af1d7a7ad7b0fd4c8e1d2bed556 Mon Sep 17 00:00:00 2001 From: "Paulo F. Oliveira" Date: Sat, 5 Aug 2023 18:09:44 +0100 Subject: [PATCH 03/22] Have tests pass Don't know how this wasn't an issue before, with us pattern matching against an opaque structure, but it is now Solution is not the most elegant, but is enough for tests Will wait for review comments to potentially update this --- src/zipper.erl | 14 ++++++++++++++ test/zipper_SUITE.erl | 4 +++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/zipper.erl b/src/zipper.erl index 7ab7c4c..e19ba1f 100644 --- a/src/zipper.erl +++ b/src/zipper.erl @@ -46,10 +46,24 @@ -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) -> diff --git a/test/zipper_SUITE.erl b/test/zipper_SUITE.erl index e562701..5c9b955 100644 --- a/test/zipper_SUITE.erl +++ b/test/zipper_SUITE.erl @@ -47,7 +47,9 @@ 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), From 6205dc940c80cf3a20c92047303ade2afe70f7f7 Mon Sep 17 00:00:00 2001 From: "Paulo F. Oliveira" Date: Sun, 6 Aug 2023 19:19:28 +0100 Subject: [PATCH 04/22] Ease maintenance of .gitignore Only keep generated stuff + _* (e.g. for _build and _checkouts) --- .gitignore | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) 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 From e85e723c26920e5a69284ddb75d164e693014219 Mon Sep 17 00:00:00 2001 From: "Paulo F. Oliveira" Date: Mon, 7 Aug 2023 13:13:30 +0100 Subject: [PATCH 05/22] Adapt to ex_doc --- src/zipper.erl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/zipper.erl b/src/zipper.erl index e19ba1f..91ce9eb 100644 --- a/src/zipper.erl +++ b/src/zipper.erl @@ -1,7 +1,6 @@ %%% @doc Generic Zipper Implementation. %%% Zippers let you traverse immutable data structures with ease and %%% flexibility. -%%% @end -module(zipper). -compile({no_auto_import, [node/1]}). @@ -306,7 +305,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), From ae1f4c0c27392e3202bef5bdcc3eb48182e9b6f9 Mon Sep 17 00:00:00 2001 From: "Paulo F. Oliveira" Date: Mon, 7 Aug 2023 13:14:02 +0100 Subject: [PATCH 06/22] Further accept format results --- src/zipper.app.src | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) 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"]}]}. From 0a2b7691c688548eee337b5f4dd2ab4973694bd8 Mon Sep 17 00:00:00 2001 From: "Paulo F. Oliveira" Date: Mon, 7 Aug 2023 13:14:17 +0100 Subject: [PATCH 07/22] Modernize rebar.config --- rebar.config | 141 ++++++++++++++++++++------------------------------- 1 file changed, 56 insertions(+), 85 deletions(-) diff --git a/rebar.config b/rebar.config index 0be6968..9b615c7 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}]}, + {deps, [{xref_runner, "1.2.0"}]}, + {dialyzer, + [{warnings, [no_return, unmatched_returns, error_handling, unknown]}, + {plt_extra_apps, [syntax_tools, common_test, dialyzer, xref_runner]}]}]}, + {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.0.1"}, + {rebar3_ex_doc, "0.2.18"}]}. + +%% == Documentation == + +{ex_doc, + [{source_url, <<"https://github.com/inaka/zipper">>}, + {extras, [<<"README.md">>, <<"LICENSE">>]}, + {main, <<"readme">>}]}. + +{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/**"]}. From 98cb5ad75441b2a22b5b9ff12ecb4289cc82b742 Mon Sep 17 00:00:00 2001 From: "Paulo F. Oliveira" Date: Mon, 7 Aug 2023 13:14:32 +0100 Subject: [PATCH 08/22] Move http:// to https:// --- LICENSE | 4 ++-- README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/LICENSE b/LICENSE index 74882e3..f7ba8bd 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ 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 +192,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..6a30829 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ 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) +And you can check all of our open-source projects at [inaka.github.io](https://inaka.github.io) ## Usage @@ -105,4 +105,4 @@ Therefore, it's really important to have the branch updated and pushed to github ## 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) From 4f3c984f27033c949f24f3515f584cdf7c46259f Mon Sep 17 00:00:00 2001 From: "Paulo F. Oliveira" Date: Mon, 7 Aug 2023 13:14:49 +0100 Subject: [PATCH 09/22] Accept elvis' defaults --- elvis.config | 69 +++++++++------------------------------------------- 1 file changed, 11 insertions(+), 58 deletions(-) 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}]}]}]. From 0c1eb56900da19017de7c2b09b6d592ee820086c Mon Sep 17 00:00:00 2001 From: "Paulo F. Oliveira" Date: Mon, 7 Aug 2023 13:15:07 +0100 Subject: [PATCH 10/22] Add a header to the README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 6a30829..c4c5c1f 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? From 6074e0884e817fea70ad0e707798b07a54a9223c Mon Sep 17 00:00:00 2001 From: "Paulo F. Oliveira" Date: Mon, 7 Aug 2023 13:15:52 +0100 Subject: [PATCH 11/22] Lint README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index c4c5c1f..d5a69c2 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ 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](https://inaka.github.io) @@ -46,6 +47,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. @@ -88,6 +90,7 @@ 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` + ```erlang ... {profiles, [ @@ -101,6 +104,7 @@ 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`. From 0567e5801e4132d027d67fcd6b9b681553c845c9 Mon Sep 17 00:00:00 2001 From: "Paulo F. Oliveira" Date: Mon, 7 Aug 2023 13:16:09 +0100 Subject: [PATCH 12/22] Increase consumer confidence --- .github/workflows/erlang.yml | 45 ++++++++++++++++++++++++++++++++++++ .github/workflows/lint.yml | 28 ++++++++++++++++++++++ .markdownlint.yml | 4 ++++ .yamllint.yml | 5 ++++ 4 files changed, 82 insertions(+) create mode 100644 .github/workflows/erlang.yml create mode 100644 .github/workflows/lint.yml create mode 100644 .markdownlint.yml create mode 100644 .yamllint.yml diff --git a/.github/workflows/erlang.yml b/.github/workflows/erlang.yml new file mode 100644 index 0000000..fdcf691 --- /dev/null +++ b/.github/workflows/erlang.yml @@ -0,0 +1,45 @@ +--- +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 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..00cf4fc --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,28 @@ +--- +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 + + - name: yamllint + uses: ibiqlik/action-yamllint@v3 + with: + file_or_dir: | + .github/**/erlang.yml + .*.yml + strict: true + config_file: .yamllint.yml 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 From 6e62aaa35fa68e6668fa3bea0728e7c9f5cb74f8 Mon Sep 17 00:00:00 2001 From: "Paulo F. Oliveira" Date: Mon, 7 Aug 2023 13:20:48 +0100 Subject: [PATCH 13/22] Fix as per CI results: OTP 26 errors out on option race_conditions This option used to exist, but not any more --- test/zipper_meta_SUITE.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/zipper_meta_SUITE.erl b/test/zipper_meta_SUITE.erl index 0808503..94bb815 100644 --- a/test/zipper_meta_SUITE.erl +++ b/test/zipper_meta_SUITE.erl @@ -19,7 +19,7 @@ dialyzer(_Config) -> filelib:wildcard( filename:join(DefaultRebar3PltLoc, "*_plt")), Dirs = [filename:join(BaseDir, Dir) || Dir <- ["ebin", "test"]], - Warnings = [error_handling, race_conditions, unmatched_returns], + Warnings = [error_handling, unmatched_returns], ct:comment("Dialyzer must emit no warnings"), Opts = [{analysis_type, succ_typings}, From fb58fb247a4542a44fa3525e95cc82c7b03c0ea5 Mon Sep 17 00:00:00 2001 From: "Paulo F. Oliveira" Date: Mon, 7 Aug 2023 13:19:45 +0100 Subject: [PATCH 14/22] Fix as per CI results: don't run yamllint on CHANGELOG.md --- .github/workflows/lint.yml | 1 + .yamllint.yml | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 00cf4fc..b876e27 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -17,6 +17,7 @@ jobs: globs: | *.md LICENSE + !CHANGELOG.md - name: yamllint uses: ibiqlik/action-yamllint@v3 diff --git a/.yamllint.yml b/.yamllint.yml index 8cd0bbe..7d5d51e 100644 --- a/.yamllint.yml +++ b/.yamllint.yml @@ -3,3 +3,5 @@ extends: default rules: line-length: max: 100 +ignore: | + CHANGELOG.md From cd4e7ba01ce37bfeb3227ac931124714111e44f2 Mon Sep 17 00:00:00 2001 From: "Paulo F. Oliveira" Date: Mon, 7 Aug 2023 13:22:46 +0100 Subject: [PATCH 15/22] Fix as per CI results: LICENSE was not valid Markdown --- LICENSE | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index f7ba8bd..85a77dc 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,5 @@ -Apache License +# Apache License + Version 2.0, January 2004 https://www.apache.org/licenses/ From 981d55d4435fc0a510ae448619acd8a49c3cb28d Mon Sep 17 00:00:00 2001 From: "Paulo F. Oliveira" Date: Mon, 7 Aug 2023 13:23:55 +0100 Subject: [PATCH 16/22] Fix as per CI results: README.md was not valid Markdown --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d5a69c2..a0391af 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,8 @@ 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 :)). +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) @@ -89,7 +90,9 @@ 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 ... @@ -106,7 +109,8 @@ 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 From edbdd142de8db6f297be493ef99720d08661d7f4 Mon Sep 17 00:00:00 2001 From: "Paulo F. Oliveira" Date: Mon, 7 Aug 2023 13:29:53 +0100 Subject: [PATCH 17/22] Fix as per CI results: yamllint and truthy values --- .github/workflows/erlang.yml | 2 +- .github/workflows/lint.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/erlang.yml b/.github/workflows/erlang.yml index fdcf691..b68dca0 100644 --- a/.github/workflows/erlang.yml +++ b/.github/workflows/erlang.yml @@ -1,7 +1,7 @@ --- name: Erlang CI -on: [push, pull_request] +"on": [push, pull_request] jobs: build: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b876e27..1dca35a 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,7 +1,7 @@ --- name: Lint -on: [push, pull_request] +"on": [push, pull_request] jobs: build: From 93d742930c521aeec8523e2b62a1ec9b76fb1186 Mon Sep 17 00:00:00 2001 From: "Paulo F. Oliveira" Date: Mon, 7 Aug 2023 14:24:52 +0100 Subject: [PATCH 18/22] Fix as per CI results: replace meta SUITE with GitHub CI For OTP 26 we get several functions as unknown (since this is the new default). However, adding no_unknown to dialyzer options for OTP 24 and 25 errors out, so this is an Ok compromise, I guess --- .github/workflows/erlang.yml | 2 ++ rebar.config | 3 +-- test/zipper_meta_SUITE.erl | 43 ------------------------------------ 3 files changed, 3 insertions(+), 45 deletions(-) delete mode 100644 test/zipper_meta_SUITE.erl diff --git a/.github/workflows/erlang.yml b/.github/workflows/erlang.yml index b68dca0..96dda2e 100644 --- a/.github/workflows/erlang.yml +++ b/.github/workflows/erlang.yml @@ -43,3 +43,5 @@ jobs: run: rebar3 format --verify - name: Run tests and verifications run: rebar3 test + - name: Run meta verifications + run: rebar3 as test test diff --git a/rebar.config b/rebar.config index 9b615c7..7210e20 100644 --- a/rebar.config +++ b/rebar.config @@ -10,10 +10,9 @@ [{cover_enabled, true}, {cover_opts, [verbose]}, {ct_opts, [{verbose, true}]}, - {deps, [{xref_runner, "1.2.0"}]}, {dialyzer, [{warnings, [no_return, unmatched_returns, error_handling, unknown]}, - {plt_extra_apps, [syntax_tools, common_test, dialyzer, xref_runner]}]}]}, + {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]}]}. diff --git a/test/zipper_meta_SUITE.erl b/test/zipper_meta_SUITE.erl deleted file mode 100644 index 94bb815..0000000 --- a/test/zipper_meta_SUITE.erl +++ /dev/null @@ -1,43 +0,0 @@ --module(zipper_meta_SUITE). - --export([all/0]). --export([dialyzer/1, xref/1]). - --type config() :: [{atom(), term()}]. - --export_type([config/0]). - --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, 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, ""}. From 8f02292e9e1baedfd873a1adcc95d4c5ae6d8918 Mon Sep 17 00:00:00 2001 From: "Paulo F. Oliveira" Date: Mon, 7 Aug 2023 14:30:28 +0100 Subject: [PATCH 19/22] Act on self-review: move .md to where it's supposed to be found --- .yamllint.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.yamllint.yml b/.yamllint.yml index 7d5d51e..8cd0bbe 100644 --- a/.yamllint.yml +++ b/.yamllint.yml @@ -3,5 +3,3 @@ extends: default rules: line-length: max: 100 -ignore: | - CHANGELOG.md From c0a28b12abd535f8611ade8f6c6424bce74ba2e1 Mon Sep 17 00:00:00 2001 From: "Paulo F. Oliveira" Date: Mon, 7 Aug 2023 14:37:32 +0100 Subject: [PATCH 20/22] Act on self-review: trick the formatter too :) --- src/zipper.erl | 26 ++++++++++++-------------- test/zipper_SUITE.erl | 23 ++++++++++------------- 2 files changed, 22 insertions(+), 27 deletions(-) diff --git a/src/zipper.erl b/src/zipper.erl index 91ce9eb..65d0f11 100644 --- a/src/zipper.erl +++ b/src/zipper.erl @@ -5,20 +5,18 @@ -compile({no_auto_import, [node/1]}). --export([new/4, up/1, down/1, left/1, leftmost/1, right/1, rightmost/1, next/1, is_end/1, - prev/1, root/1, traverse/2, insert_left/2, insert_right/2, replace/2, edit/3, - insert_child/2, append_child/2, remove/1, map/2, fmap/3, filter/2, fold/3, size/1, node/1, - children/1, is_branch/1]). - - %% Creation - - %% Traverse - - %% Editing - - %% Iteration - - %% Info +%% 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). diff --git a/test/zipper_SUITE.erl b/test/zipper_SUITE.erl index 5c9b955..193fcf7 100644 --- a/test/zipper_SUITE.erl +++ b/test/zipper_SUITE.erl @@ -1,19 +1,16 @@ -module(zipper_SUITE). -export([all/0]). --export([zipper_node/1, zipper_children/1, 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, 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, - zipper_map/1, zipper_fmap/1, zipper_filter/1, zipper_size/1]). - - %% Info - - %% Traverse - - %% Editing - - %% Iteration +%% 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]). From f11e328d6273e4c2abb59f5861d4d485185cbd32 Mon Sep 17 00:00:00 2001 From: "Paulo F. Oliveira" Date: Mon, 7 Aug 2023 22:33:20 +0100 Subject: [PATCH 21/22] Act on self-review: generate a proper link The README.md fix (for reference) is just a bonus! --- rebar.config | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rebar.config b/rebar.config index 7210e20..f2db46f 100644 --- a/rebar.config +++ b/rebar.config @@ -31,7 +31,8 @@ {ex_doc, [{source_url, <<"https://github.com/inaka/zipper">>}, {extras, [<<"README.md">>, <<"LICENSE">>]}, - {main, <<"readme">>}]}. + {main, <<"README.md">>}, + {prefix_ref_vsn_with_v, false}]}. {hex, [{doc, #{provider => ex_doc}}]}. From f0cf6912d9982d79e199128efee6588ff9ab3908 Mon Sep 17 00:00:00 2001 From: "Paulo F. Oliveira" Date: Wed, 23 Aug 2023 22:20:25 +0100 Subject: [PATCH 22/22] Update plugins versions --- rebar.config | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rebar.config b/rebar.config index f2db46f..519c399 100644 --- a/rebar.config +++ b/rebar.config @@ -23,8 +23,8 @@ [{rebar3_hank, "~> 1.4.0"}, {rebar3_hex, "~> 7.0.7"}, {rebar3_format, "~> 1.3.0"}, - {rebar3_lint, "~> 3.0.1"}, - {rebar3_ex_doc, "0.2.18"}]}. + {rebar3_lint, "~> 3.1.0"}, + {rebar3_ex_doc, "~> 0.2.20"}]}. %% == Documentation ==