├── .gitignore ├── LICENSE ├── Makefile ├── README.markdown ├── include └── mixer.hrl ├── rebar ├── rebar.config ├── src ├── mixer.app.src └── mixer.erl └── test ├── alias.erl ├── bar.erl ├── conflicts.erl.bad ├── duplicates.erl.bad ├── failure_test.erl ├── foo.erl ├── import_test.erl ├── missing.erl.bad ├── missing_all.erl.bad ├── multiple.erl └── single.erl /.gitignore: -------------------------------------------------------------------------------- 1 | ebin/* 2 | .eunit/* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: compile eunit dialyzer 2 | 3 | clean: 4 | @./rebar clean 5 | 6 | compile: 7 | @./rebar compile 8 | 9 | eunit: 10 | @./rebar eunit 11 | 12 | dialyzer: 13 | @dialyzer --src src 14 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | `foo.erl`: 2 | 3 | -module(foo). 4 | 5 | -export([doit/0, doit/1, doit/2]). 6 | 7 | doit() -> 8 | doit. 9 | 10 | doit(A) -> 11 | [doit, A]. 12 | 13 | doit(A, B) -> 14 | [doit, A, B]. 15 | 16 | Module `bar.erl` which 'mixes in' `foo`: 17 | 18 | -module(bar). 19 | -include_lib("mixer/include/mixer.hrl"). 20 | -mixin([foo]). 21 | 22 | or all except specific functions from `foo`: 23 | 24 | -module(bar). 25 | -include_lib("mixer/include/mixer.hrl"). 26 | -mixin([{foo, except, [doit/0, doit/2]}]). 27 | 28 | or only specific functions from `foo`: 29 | 30 | -module(bar). 31 | -include_lib("mixer/include/mixer.hrl"). 32 | -mixin([{foo, [doit/0, doit/2]}]). 33 | 34 | Another version of `bar.erl` which mixes in all functions from `foo` and select functions from `baz`: 35 | 36 | -module(bar). 37 | -include_lib("mixer/include/mixer.hrl"). 38 | -mixin([foo, {baz, [doit/0, doit/1]}]). 39 | 40 | One more version of `bar.erl` which mixes in `foo:doit/0` and renames it to `do_it_now/0`: 41 | 42 | -module(bar). 43 | -include_lib("mixer/include/mixer.hrl"). 44 | -mixin([{foo, [{doit/0, do_it_now}]}]). 45 | 46 | The original motivation for this parse transform was to permit reuse of functions implementing common 47 | logic for tasks such as signature verification and authorization across multiple webmachine resources. -------------------------------------------------------------------------------- /include/mixer.hrl: -------------------------------------------------------------------------------- 1 | %% -*- mode: erlang -*- 2 | %% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- 3 | %% ex: ts=4 sw=4 ft=erlang et 4 | %% 5 | %% Copyright 2012 Opscode, Inc. All Rights Reserved. 6 | %% 7 | %% This file is provided to you under the Apache License, 8 | %% Version 2.0 (the "License"); you may not use this file 9 | %% except in compliance with the License. You may obtain 10 | %% a copy of the License at 11 | %% 12 | %% http://www.apache.org/licenses/LICENSE-2.0 13 | %% 14 | %% Unless required by applicable law or agreed to in writing, 15 | %% software distributed under the License is distributed on an 16 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | %% KIND, either express or implied. See the License for the 18 | %% specific language governing permissions and limitations 19 | %% under the License. 20 | %% 21 | 22 | -compile({parse_transform, mixer}). 23 | -------------------------------------------------------------------------------- /rebar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chef/mixer/0d1322433e7e2237eb1270dc5a028fa014335134/rebar -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {eunit_first_files, ["src/wm_delegate.erl", "test/foo.erl", "test/bar.erl"]}. 2 | {erl_opts, [debug_info, warnings_as_errors]}. 3 | -------------------------------------------------------------------------------- /src/mixer.app.src: -------------------------------------------------------------------------------- 1 | %% -*- mode: erlang -*- 2 | %% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- 3 | %% ex: ts=4 sw=4 ft=erlang et 4 | %% Copyright 2011-2012 Opscode, Inc. All Rights Reserved. 5 | %% 6 | %% This file is provided to you under the Apache License, 7 | %% Version 2.0 (the "License"); you may not use this file 8 | %% except in compliance with the License. You may obtain 9 | %% a copy of the License at 10 | %% 11 | %% http://www.apache.org/licenses/LICENSE-2.0 12 | %% 13 | %% Unless required by applicable law or agreed to in writing, 14 | %% software distributed under the License is distributed on an 15 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | %% KIND, either express or implied. See the License for the 17 | %% specific language governing permissions and limitations 18 | %% under the License. 19 | %% 20 | 21 | {application, mixer, 22 | [ 23 | {description, "Mix in public functions from external modules"}, 24 | {vsn, "0.1.1"}, 25 | {registered, []}, 26 | {applications, [kernel, 27 | stdlib]}, 28 | {env, []} 29 | ]}. 30 | -------------------------------------------------------------------------------- /src/mixer.erl: -------------------------------------------------------------------------------- 1 | %% -*- mode: erlang -*- 2 | %% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- 3 | %% ex: ts=4 sw=4 ft=erlang et 4 | %% 5 | %% Copyright 2012 Opscode, Inc. All Rights Reserved. 6 | %% 7 | %% This file is provided to you under the Apache License, 8 | %% Version 2.0 (the "License"); you may not use this file 9 | %% except in compliance with the License. You may obtain 10 | %% a copy of the License at 11 | %% 12 | %% http://www.apache.org/licenses/LICENSE-2.0 13 | %% 14 | %% Unless required by applicable law or agreed to in writing, 15 | %% software distributed under the License is distributed on an 16 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | %% KIND, either express or implied. See the License for the 18 | %% specific language governing permissions and limitations 19 | %% under the License. 20 | %% 21 | -module(mixer). 22 | 23 | -export([parse_transform/2]). 24 | 25 | -define(arity_limit, 26). 26 | 27 | -record(mixin, {line, 28 | mod, 29 | fname, 30 | alias, 31 | arity}). 32 | 33 | -spec parse_transform([term()], [term()]) -> [term()]. 34 | parse_transform(Forms, _Options) -> 35 | set_mod_info(Forms), 36 | {EOF, Forms1} = strip_eof(Forms), 37 | case parse_and_expand_mixins(Forms1, []) of 38 | [] -> 39 | Forms; 40 | Mixins -> 41 | no_dupes(Mixins), 42 | {EOF1, Forms2} = insert_stubs(Mixins, EOF, Forms1), 43 | finalize(Mixins, EOF1, Forms2) 44 | end. 45 | 46 | %% Internal functions 47 | set_mod_info([{attribute, _, file, {FileName, _}}|_]) -> 48 | erlang:put(mixer_delegate_file, FileName); 49 | set_mod_info([{attribute, _, module, Mod}|_]) -> 50 | erlang:put(mixer_calling_mod, Mod). 51 | 52 | get_file_name() -> 53 | erlang:get(mixer_delegate_file). 54 | 55 | %% get_calling_mod() -> 56 | %% erlang:get(mixer_calling_mod). 57 | 58 | finalize(Mixins, NewEOF, Forms) -> 59 | insert_exports(Mixins, Forms, []) ++ [{eof, NewEOF}]. 60 | 61 | insert_exports([], Forms, Accum) -> 62 | Accum ++ Forms; 63 | insert_exports([#mixin{line=Line}|_]=Mixins, [{attribute, Line, mixin, _}|FT], Accum) -> 64 | {Exports, Mixins1} = make_export_statement(Line, Mixins), 65 | insert_exports(Mixins1, FT, Accum ++ Exports); 66 | insert_exports([#mixin{line=Line}|_]=Mixins, [], Accum) -> 67 | {Exports, Mixins1} = make_export_statement(Line, Mixins), 68 | insert_exports(Mixins1, [], Accum ++ Exports); 69 | insert_exports(Mixins, [H|T], Accum) -> 70 | insert_exports(Mixins, T, Accum ++ [H]). 71 | 72 | strip_eof(Forms) -> 73 | strip_eof(Forms, []). 74 | 75 | strip_eof([], Accum) -> 76 | lists:reverse(Accum); 77 | strip_eof([{eof, EOF}|T], Accum) -> 78 | {EOF, lists:reverse(Accum) ++ T}; 79 | strip_eof([H|T], Accum) -> 80 | strip_eof(T, [H|Accum]). 81 | 82 | parse_and_expand_mixins([], []) -> 83 | []; 84 | parse_and_expand_mixins([], Accum) -> 85 | group_mixins({none, 0}, lists:keysort(2, Accum), []); 86 | parse_and_expand_mixins([{attribute, Line, mixin, Mixins0}|T], Accum) when is_list(Mixins0) -> 87 | Mixins = [expand_mixin(Line, Mixin) || Mixin <- Mixins0], 88 | parse_and_expand_mixins(T, lists:flatten([Accum, Mixins])); 89 | parse_and_expand_mixins([_|T], Accum) -> 90 | parse_and_expand_mixins(T, Accum). 91 | 92 | group_mixins(_, [], Accum) -> 93 | lists:keysort(2, Accum); 94 | group_mixins({CMod, CLine}, [#mixin{mod=CMod, line=CLine}=H|T], Accum) -> 95 | group_mixins({CMod, CLine}, T, [H|Accum]); 96 | group_mixins({CMod, CLine}, [#mixin{mod=CMod}=H|T], Accum) -> 97 | group_mixins({CMod, CLine}, T, [H#mixin{line=CLine}|Accum]); 98 | group_mixins({_CMod, _}, [#mixin{mod=Mod, line=Line}=H|T], Accum) -> 99 | group_mixins({Mod, Line}, T, [H|Accum]). 100 | 101 | expand_mixin(Line, Name) when is_atom(Name) -> 102 | case catch Name:module_info(exports) of 103 | {'EXIT', _} -> 104 | io:format("~s:~p Unable to resolve imported module ~p~n", [get_file_name(), Line, Name]), 105 | exit({error, undef_mixin_module}); 106 | Exports -> 107 | [#mixin{line=Line, mod=Name, fname=Fun, alias=Fun, arity=Arity} || {Fun, Arity} <- Exports, 108 | Fun /= module_info] 109 | end; 110 | expand_mixin(Line, {Name, except, Funs}) when is_atom(Name), 111 | is_list(Funs) -> 112 | case catch Name:module_info(exports) of 113 | {'EXIT', _} -> 114 | io:format("~s:~p Unable to resolve imported module ~p~n", [get_file_name(), Line, Name]), 115 | exit({error, undef_mixin_module}); 116 | Exports -> 117 | [#mixin{line=Line, mod=Name, fname=Fun, alias=Fun, arity=Arity} || {Fun, Arity} <- Exports, 118 | Fun /= module_info andalso not lists:member({Fun, Arity}, Funs)] 119 | end; 120 | expand_mixin(Line, {Name, Funs}) when is_atom(Name), 121 | is_list(Funs) -> 122 | [begin 123 | {Fun, Arity, Alias} = parse_mixin_ref(MixinRef), 124 | #mixin{line=Line, mod=Name, fname=Fun, arity=Arity, alias=Alias} 125 | end || MixinRef <- Funs]. 126 | 127 | parse_mixin_ref({{Fun, Arity}, Alias}) -> 128 | {Fun, Arity, Alias}; 129 | parse_mixin_ref({Fun, Arity}) -> 130 | {Fun, Arity, Fun}. 131 | 132 | no_dupes([]) -> 133 | ok; 134 | no_dupes([H|T]) -> 135 | no_dupes(H, T), 136 | no_dupes(T). 137 | 138 | no_dupes(_, []) -> 139 | ok; 140 | no_dupes(#mixin{mod=Mod, fname=Fun, arity=Arity, line=Line}, Rest) -> 141 | case find_dupe(Fun, Arity, Rest) of 142 | {ok, {Mod1, Fun, Arity}} -> 143 | io:format("~s:~p Duplicate mixin detected importing ~p/~p from ~p and ~p~n", 144 | [get_file_name(), Line, Fun, Arity, Mod, Mod1]), 145 | exit({error, duplicate_mixins}); 146 | not_found -> 147 | ok 148 | end. 149 | 150 | find_dupe(_Fun, _Arity, []) -> 151 | not_found; 152 | find_dupe(Fun, Arity, [#mixin{mod=Name, fname=Fun, arity=Arity}|_]) -> 153 | {ok, {Name, Fun, Arity}}; 154 | find_dupe(Fun, Arity, [_|T]) -> 155 | find_dupe(Fun, Arity, T). 156 | 157 | insert_stubs(Mixins, EOF, Forms) -> 158 | F = fun(#mixin{mod=Mod, fname=Fun, arity=Arity, alias=Alias}, {CurrEOF, Acc}) -> 159 | {CurrEOF + 1, [generate_stub(atom_to_list(Mod), atom_to_list(Alias), atom_to_list(Fun), Arity, CurrEOF)|Acc]} end, 160 | {EOF1, Stubs} = lists:foldr(F, {EOF, []}, Mixins), 161 | {EOF1, Forms ++ lists:reverse(Stubs)}. 162 | 163 | 164 | generate_stub(Mixin, Alias, Name, Arity, CurrEOF) when Arity =< ?arity_limit -> 165 | ArgList = "(" ++ make_param_list(Arity) ++ ")", 166 | Code = Alias ++ ArgList ++ "-> " ++ Mixin ++ ":" ++ Name ++ ArgList ++ ".", 167 | {ok, Tokens, _} = erl_scan:string(Code), 168 | {ok, Form} = erl_parse:parse_form(Tokens), 169 | replace_stub_linenum(CurrEOF, Form). 170 | 171 | replace_stub_linenum(CurrEOF, {function, _, Name, Arity, Body}) -> 172 | {function, CurrEOF, Name, Arity, replace_stub_linenum(CurrEOF, Body, [])}. 173 | 174 | replace_stub_linenum(CurrEOF, [{clause, _, Vars, [], Call}], _) -> 175 | [{clause, CurrEOF, replace_stub_linenum(CurrEOF, Vars, []), [], replace_stub_linenum(CurrEOF, Call, [])}]; 176 | replace_stub_linenum(_CurrEOF, [], Accum) -> 177 | lists:reverse(Accum); 178 | replace_stub_linenum(CurrEOF, [{var, _, Var}|T], Accum) -> 179 | replace_stub_linenum(CurrEOF, T, [{var, CurrEOF, Var}|Accum]); 180 | replace_stub_linenum(CurrEOF, [{call, _, {remote, _, {atom, _, Mod}, {atom, _, Fun}}, Args}], _Accum) -> 181 | [{call, CurrEOF, {remote, CurrEOF, {atom, CurrEOF, Mod}, {atom, CurrEOF, Fun}}, 182 | replace_stub_linenum(CurrEOF, Args, [])}]. 183 | 184 | %% Use single upper-case letters for params 185 | make_param_list(0) -> 186 | ""; 187 | make_param_list(Arity) when Arity =< ?arity_limit -> 188 | make_param_list(Arity, []). 189 | 190 | make_param_list(1, Accum) -> 191 | push_param(1, Accum); 192 | make_param_list(Count, Accum) -> 193 | make_param_list(Count - 1, push_param(Count, Accum)). 194 | 195 | push_param(Pos, []) -> 196 | [(64 + Pos)]; 197 | push_param(Pos, Accum) -> 198 | [(64 + Pos), 44|Accum]. 199 | 200 | make_export_statement(Line, Mixins) -> 201 | F = fun(Mixin) -> Mixin#mixin.line == Line end, 202 | case lists:partition(F, Mixins) of 203 | {[], Mixins} -> 204 | {[], Mixins}; 205 | {ME, Mixins1} -> 206 | Export = {attribute, Line, export, [{Alias, Arity} || #mixin{alias=Alias, arity=Arity} <- ME]}, 207 | {[Export], Mixins1} 208 | end. 209 | -------------------------------------------------------------------------------- /test/alias.erl: -------------------------------------------------------------------------------- 1 | -module(alias). 2 | 3 | -include("mixer.hrl"). 4 | 5 | -mixin([{foo, [{doit/0, blah}]}]). 6 | -mixin([{bar, [{canhas/0, can_has}]}]). 7 | -------------------------------------------------------------------------------- /test/bar.erl: -------------------------------------------------------------------------------- 1 | -module(bar). 2 | 3 | -export([canhas/0, canhas/1]). 4 | -export([doit/1]). 5 | 6 | canhas() -> 7 | true. 8 | 9 | canhas(A) -> 10 | A. 11 | 12 | doit(A) -> 13 | {doit, A}. 14 | -------------------------------------------------------------------------------- /test/conflicts.erl.bad: -------------------------------------------------------------------------------- 1 | -module(conflicts). 2 | 3 | -include("mixer.hrl"). 4 | 5 | -mixin([{foo, [do_it/1]}, 6 | {bar, [do_it/1]}]). -------------------------------------------------------------------------------- /test/duplicates.erl.bad: -------------------------------------------------------------------------------- 1 | -module(duplicates). 2 | 3 | -include("mixer.hrl"). 4 | 5 | -mixin([{foo, [doit/0]}, bar]). 6 | -mixin([bar]). 7 | -------------------------------------------------------------------------------- /test/failure_test.erl: -------------------------------------------------------------------------------- 1 | -module(failure_test). 2 | 3 | -include_lib("kernel/include/file.hrl"). 4 | -include_lib("eunit/include/eunit.hrl"). 5 | 6 | -define(EXPORTS(Mod), Mod:module_info(exports)). 7 | 8 | duplicate_test_() -> 9 | [{<<"Duplicate mixins detected">>, 10 | fun() -> 11 | Error = compile_bad_test_file("duplicates"), 12 | ?assertMatch({error,[{"duplicates.erl", 13 | [{none,compile, 14 | {parse_transform,mixer,{error,duplicate_mixins}}}]}], 15 | []}, Error) end}]. 16 | 17 | conflicting_mixins_test_() -> 18 | [{<<"Conflicting mixins detected">>, 19 | fun()-> 20 | Error = compile_bad_test_file("conflicts"), 21 | ?assertMatch({error,[{"conflicts.erl", 22 | [{none,compile, 23 | {parse_transform,mixer,{error,duplicate_mixins}}}]}], 24 | []}, Error) end}]. 25 | 26 | %% Internal functions 27 | compile_bad_test_file(Module) -> 28 | From = filename:join(["..", "test", Module ++ ".erl.bad"]), 29 | To = "./" ++ Module ++ ".erl", 30 | {ok, BytesCopied} = file:copy(From, To), 31 | {ok, Info} = file:read_file_info(From), 32 | ?assertEqual(Info#file_info.size, BytesCopied), 33 | compile:file(To, [{i, "../include"}, return_errors]). 34 | -------------------------------------------------------------------------------- /test/foo.erl: -------------------------------------------------------------------------------- 1 | -module(foo). 2 | 3 | -export([doit/0, doit/1, doit/2]). 4 | 5 | doit() -> 6 | doit. 7 | 8 | doit(A) -> 9 | [doit, A]. 10 | 11 | doit(A, B) -> 12 | [doit, A, B]. 13 | -------------------------------------------------------------------------------- /test/import_test.erl: -------------------------------------------------------------------------------- 1 | -module(import_test). 2 | 3 | -include_lib("eunit/include/eunit.hrl"). 4 | 5 | -define(EXPORTS(Mod), Mod:module_info(exports)). 6 | 7 | single_test_() -> 8 | [{<<"All functions on 'single' stubbed properly">>, 9 | [?_assert(lists:member({doit, 0}, ?EXPORTS(single))), 10 | ?_assert(lists:member({doit, 1}, ?EXPORTS(single))), 11 | ?_assert(lists:member({doit, 2}, ?EXPORTS(single)))]}, 12 | {<<"All functions on 'single' work correctly">>, 13 | [?_assertMatch(doit, single:doit()), 14 | ?_assertMatch([doit, 1], single:doit(1)), 15 | ?_assertMatch([doit, 1, 2], single:doit(1, 2))]}]. 16 | 17 | multiple_test_() -> 18 | [{<<"All functions stubbed">>, 19 | [?_assert(lists:member({doit, 0}, ?EXPORTS(multiple))), 20 | ?_assert(lists:member({doit, 1}, ?EXPORTS(multiple))), 21 | ?_assert(lists:member({doit, 2}, ?EXPORTS(multiple))), 22 | ?_assert(lists:member({canhas, 0}, ?EXPORTS(multiple))), 23 | ?_assert(lists:member({canhas, 1}, ?EXPORTS(multiple)))]}, 24 | {<<"All stubbed functions work">>, 25 | [?_assertMatch(doit, multiple:doit()), 26 | ?_assertMatch({doit, one}, multiple:doit(one)), 27 | ?_assertMatch([doit, one, two], multiple:doit(one, two)), 28 | ?_assert(multiple:canhas()), 29 | ?_assertMatch(cheezburger, multiple:canhas(cheezburger))]}]. 30 | 31 | alias_test_() -> 32 | [{<<"Function stubbed with alias">>, 33 | [?_assert(lists:member({blah, 0}, ?EXPORTS(alias))), 34 | ?_assert(lists:member({can_has, 0}, ?EXPORTS(alias)))]}, 35 | {<<"All stubbed functions work">>, 36 | [?_assertMatch(doit, alias:blah()), 37 | ?_assertMatch(true, alias:can_has())]}]. 38 | -------------------------------------------------------------------------------- /test/missing.erl.bad: -------------------------------------------------------------------------------- 1 | -module(missing). 2 | 3 | -include("mixer.hrl"). 4 | 5 | -mixin([{wtf, [foo/0]}]). -------------------------------------------------------------------------------- /test/missing_all.erl.bad: -------------------------------------------------------------------------------- 1 | -module(missing). 2 | 3 | -include("mixer.hrl"). 4 | 5 | -mixin([wtf]). -------------------------------------------------------------------------------- /test/multiple.erl: -------------------------------------------------------------------------------- 1 | -module(multiple). 2 | 3 | -include("mixer.hrl"). 4 | 5 | -mixin([{foo, [doit/0]}, 6 | bar, 7 | {foo, except, [doit/0, doit/1]}]). 8 | -------------------------------------------------------------------------------- /test/single.erl: -------------------------------------------------------------------------------- 1 | -module(single). 2 | 3 | -include("mixer.hrl"). 4 | 5 | -mixin([foo]). 6 | --------------------------------------------------------------------------------