├── LICENSE.txt ├── Makefile ├── README.textile ├── deps ├── erlydtl │ ├── README │ ├── ebin │ │ └── .gitignore │ ├── priv │ │ └── custom_tags │ │ │ └── flashvideo │ └── src │ │ ├── Makefile │ │ ├── erlydtl.erl │ │ ├── erlydtl_compiler.erl │ │ ├── erlydtl_dateformat.erl │ │ ├── erlydtl_deps.erl │ │ ├── erlydtl_filters.erl │ │ ├── erlydtl_parser.erl │ │ ├── erlydtl_parser.yrl │ │ ├── erlydtl_runtime.erl │ │ └── erlydtl_scanner.erl └── mochiweb │ ├── LICENSE │ ├── README │ ├── ebin │ └── .gitignore │ └── src │ ├── Makefile │ ├── mochifmt.erl │ ├── mochifmt_records.erl │ ├── mochifmt_std.erl │ ├── mochihex.erl │ ├── mochijson.erl │ ├── mochijson2.erl │ ├── mochinum.erl │ ├── mochiweb.app │ ├── mochiweb.erl │ ├── mochiweb_app.erl │ ├── mochiweb_charref.erl │ ├── mochiweb_cookies.erl │ ├── mochiweb_echo.erl │ ├── mochiweb_headers.erl │ ├── mochiweb_html.erl │ ├── mochiweb_http.erl │ ├── mochiweb_multipart.erl │ ├── mochiweb_request.erl │ ├── mochiweb_response.erl │ ├── mochiweb_skel.erl │ ├── mochiweb_socket_server.erl │ ├── mochiweb_sup.erl │ ├── mochiweb_util.erl │ └── reloader.erl ├── doc ├── beepbeep.html ├── beepbeep_args.html ├── beepbeep_router.html ├── edoc-info ├── erlang.png ├── index.html ├── modules-frame.html ├── overview-summary.html ├── overview.edoc ├── packages-frame.html └── stylesheet.css ├── ebin └── .gitignore ├── example └── blog │ ├── Makefile │ ├── ebin │ └── .gitignore │ ├── src │ ├── Makefile │ ├── blog_database.erl │ ├── blog_example.app │ ├── blog_example.erl │ ├── blog_example_app.erl │ ├── blog_example_deps.erl │ ├── blog_example_sup.erl │ ├── blog_example_web.erl │ ├── home_controller.erl │ └── login_controller.erl │ ├── start-server.sh │ ├── support │ └── include.mk │ ├── views │ ├── base.html │ ├── flash.html │ ├── home │ │ ├── index.html │ │ └── new.html │ └── login │ │ └── new.html │ └── www │ └── stylesheets │ └── style.css ├── include └── beepbeep.hrl ├── priv └── skel │ ├── Makefile │ ├── deps │ └── .gitignore │ ├── ebin │ └── .gitignore │ ├── src │ ├── Makefile │ ├── home_controller.erl │ ├── skel.app │ ├── skel.erl │ ├── skel_app.erl │ ├── skel_deps.erl │ ├── skel_sup.erl │ └── skel_web.erl │ ├── start-server.sh │ ├── support │ └── include.mk │ ├── views │ ├── base.html │ ├── flash.html │ └── home │ │ ├── index.html │ │ └── show.html │ └── www │ └── stylesheets │ └── style.css ├── script └── new_beep.erl ├── src ├── Makefile ├── beepbeep.erl ├── beepbeep_args.erl ├── beepbeep_router.erl ├── beepbeep_session_server.erl ├── beepbeep_skel.erl └── mochiweb_env.erl └── support └── include.mk /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2009 Dave Bryson (miceda.org) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | (cd src;$(MAKE)) 3 | (cd deps/erlydtl/src;$(MAKE)) 4 | (cd deps/mochiweb/src;$(MAKE)) 5 | (cd example/blog;$(MAKE)) 6 | 7 | docs: 8 | erl -pa `pwd`/ebin \ 9 | -noshell \ 10 | -run edoc_run application "'BeepBeep'" '"."' '[no_packages]' 11 | 12 | clean: 13 | (cd src;$(MAKE) clean) 14 | (cd deps/erlydtl/src;$(MAKE) clean) 15 | (cd deps/mochiweb/src;$(MAKE) clean) 16 | (cd example/blog;$(MAKE) clean) 17 | -------------------------------------------------------------------------------- /README.textile: -------------------------------------------------------------------------------- 1 | h2. BeepBeep a simple web application for Erlang 2 | 3 | BeepBeep is a simple Web Application framework for Erlang inspired by Rails and Merb. It follows the principle of convention over configuration - meaning if you follow the code structure layout and a few rules when building your app, it'll require no extra work on you behalf to map Url requests to your Controllers and Views. 4 | 5 | BeepBeep is built on "MochiWeb":http://code.google.com/p/mochiweb/ and "ErlyDTL":http://code.google.com/p/erlydtl, providing a super fast web server and the abiity to define your templates with the Django template language. 6 | 7 | 8 | It looks like one of the many Google Groups I created for the project is finally available: 9 | "Google Group":http://groups.google.com/group/beepbeep-web-framework 10 | 11 | h3. Features 12 | 13 | * A script to generate a new web application (based on mochiweb's approach) 14 | * Session Server to store your application state 15 | * Before filter on your controllers for things like authentication 16 | * Django templates for the view 17 | 18 | h3. Getting Started 19 | 20 | # download the code 21 | # CD into the beepbeep directory 22 | # run make 23 | # generate a new web application by running ./script/new_beep.erl YouAppName "DestinationDirectory 24 | 25 | This will create a web app with everything you need. It includes a Sample controller (main_controller.erl). 26 | 27 | To run the sample: 28 | 29 | # cd into the new application's directory you created above 30 | # Run make to compile the new project 31 | # Start the server: ./start-server.sh 32 | # Open a browser and visit "http://localhost:8000 33 | 34 | h3. How it works: 35 | 36 | You write a controller with a set of methods that look like this: 37 |
38 | 
39 | handle_request(Action,Params)
40 | 
41 | 
42 | 43 | Where Action is a string that will match to the request in the Url. 44 | 45 | And Params is an Array of optional parameters that will be passed to variables in your controller. 46 | 47 | BeepBeep will automatically map Url requests to controller and functions (or actions). For example a request to "/hello/show" would map to the "hello_controller" and invoke the "handle_request("show",[])" function. 48 | 49 | Here's an example: 50 | 51 |
52 |  
53 | %% hello_controller.erl
54 | -module(hello_controller).
55 | 
56 | -export([handle_request/2, before_filter/0]).
57 | 
58 | %% Maps to http://localhost:8000/hello/show
59 | handle_request("show",[]) ->
60 |     {render, "hello/show.html",[{name,"BeepBeep"}]};
61 | 
62 | %% Maps to http://localhost:8000/hello/feed/2007
63 | handle_request("feed",[Year]) ->
64 |     %% Now Year contains the value 2007
65 |     {render,"hello/feed.html",[{year,Year}]}.
66 |   
67 | 
68 | %% Callback filter hook
69 | before_filter(Params) ->
70 |     ok.
71 | 
72 |  
73 | 
74 | 75 | From "handle_request" we return a tuple that tells the framework what view to use. Views are located in the views directory. In our example we'll use the view located in the subdirectory "hello" and the file "show.html" 76 | 77 | Here's an example of the "show.html" template: 78 | 79 |

80 | 

Hello from {{ name }}

81 |
82 | 83 | Which will result in: 84 |

85 | 

Hello from BeepBeep

86 |
87 | 88 | The "name" key set in the controller is passed to the template and expanded using the Django format via erlyDTL. 89 | 90 | This approach provides a clean separation of the erlang logic in the controller and the html code in the template. 91 | 92 | You can also implement the before_filter to check requests before the matching "handle_request" is called. Filters that pass should simply return the atom ok, otherwise they should return one of the request responses such as {render...} or {redirect..." 93 | 94 | For more information, see the documention and example blog app included with the source code. And there's a small tutorial on the "Wiki":http://wiki.github.com/davebryson/beepbeep 95 | -------------------------------------------------------------------------------- /deps/erlydtl/README: -------------------------------------------------------------------------------- 1 | ErlyDTL 2 | ======= 3 | 4 | ErlyDTL implements most but not all of the Django Template Language. 5 | 6 | Project homepage: http://code.google.com/p/erlydtl/ 7 | 8 | 9 | Compilation 10 | ----------- 11 | 12 | To compile ErlyDTL, type "make" in this directory. 13 | 14 | 15 | Template compilation 16 | -------------------- 17 | 18 | Four ways: 19 | 20 | erlydtl:compile("/path/to/template.dtl", my_module_name) 21 | 22 | erlydtl:compile("/path/to/template.dtl", my_module_name, Options) 23 | 24 | erlydtl:compile(<<"{{ foo }}">>, my_module_name) 25 | 26 | erlydtl:compile(<<"{{ foo }}">>, my_module_name, Options) 27 | 28 | Options is a proplist possibly containing: 29 | 30 | doc_root - Included template paths will be relative to this directory; 31 | defaults to the compiled template's directory. 32 | 33 | custom_tags_dir - Directory of DTL files (no extension) includable as tags. 34 | E.g. if $custom_tags_dir/foo contains "{{ bar }}", then 35 | "{{ foo bar=100 }}" will evaluate to "100". Get it? 36 | 37 | vars - Variables (and their values) to evaluate at compile-time rather than 38 | render-time. 39 | 40 | reader - {module, function} tuple that takes a path to a template and returns 41 | a binary with the file contents. Defaults to {file, read_file}. Useful 42 | for reading templates from a network resource. 43 | 44 | compiler_options - Proplist passed directly to compiler:forms/2 45 | 46 | force_recompile - Recompile the module even if the source's checksum has not 47 | changed. Useful for debugging. 48 | 49 | 50 | Usage (of a compiled template) 51 | ------------------------------ 52 | 53 | my_compiled_template:render(Variables) -> {ok, IOList} | {error, Err} 54 | 55 | Variables is a proplist, dict, gb_tree, or a parameterized module 56 | (whose method names correspond to variable names). The variable 57 | values can be atoms, strings, binaries, or (nested) variables. 58 | 59 | IOList is the rendered template. 60 | 61 | my_compiled_template:source() -> {FileName, CheckSum} 62 | 63 | Name and checksum of the original template file. 64 | 65 | my_compiled_template:dependencies() -> [{FileName, CheckSum}] 66 | 67 | List of names/checksums of templates included by the original template 68 | file. Useful for frameworks that recompile a template only when the 69 | template's dependencies change. 70 | 71 | 72 | Tests 73 | ----- 74 | 75 | From a Unix shell, run: 76 | 77 | make test 78 | 79 | Note that the tests will create some output in examples/rendered_output. 80 | -------------------------------------------------------------------------------- /deps/erlydtl/ebin/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebryson/beepbeep/62db46d268c6cb6ad86345562b3c77f8ff070b27/deps/erlydtl/ebin/.gitignore -------------------------------------------------------------------------------- /deps/erlydtl/priv/custom_tags/flashvideo: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

To view the Video:

13 |

14 | 15 | Get Adobe Flash player 16 | 17 |

18 | 19 |
20 | 21 |
-------------------------------------------------------------------------------- /deps/erlydtl/src/Makefile: -------------------------------------------------------------------------------- 1 | include ../../../support/include.mk 2 | 3 | all: $(EBIN_FILES_NO_DOCS) 4 | 5 | debug: 6 | $(MAKE) DEBUG=-DDEBUG 7 | 8 | clean: 9 | rm -rf $(EBIN_FILES_NO_DOCS) 10 | -------------------------------------------------------------------------------- /deps/erlydtl/src/erlydtl.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% File: erlydtl.erl 3 | %%% @author Roberto Saccon [http://rsaccon.com] 4 | %%% @author Evan Miller 5 | %%% @copyright 2008 Roberto Saccon, Evan Miller 6 | %%% @doc 7 | %%% Public interface for ErlyDTL 8 | %%% @end 9 | %%% 10 | %%% The MIT License 11 | %%% 12 | %%% Copyright (c) 2008 Roberto Saccon, Evan Miller 13 | %%% 14 | %%% Permission is hereby granted, free of charge, to any person obtaining a copy 15 | %%% of this software and associated documentation files (the "Software"), to deal 16 | %%% in the Software without restriction, including without limitation the rights 17 | %%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | %%% copies of the Software, and to permit persons to whom the Software is 19 | %%% furnished to do so, subject to the following conditions: 20 | %%% 21 | %%% The above copyright notice and this permission notice shall be included in 22 | %%% all copies or substantial portions of the Software. 23 | %%% 24 | %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | %%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | %%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | %%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | %%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 30 | %%% THE SOFTWARE. 31 | %%% 32 | %%% @since 2007-11-11 by Roberto Saccon, Evan Miller 33 | %%%------------------------------------------------------------------- 34 | -module(erlydtl). 35 | -author('rsaccon@gmail.com'). 36 | -author('emmiller@gmail.com'). 37 | 38 | %% API 39 | -export([compile/2, compile/3]). 40 | 41 | compile(FileOrBinary, Module) -> 42 | erlydtl_compiler:compile(FileOrBinary, Module). 43 | 44 | compile(FileOrBinary, Module, Options) -> 45 | erlydtl_compiler:compile(FileOrBinary, Module, Options). 46 | -------------------------------------------------------------------------------- /deps/erlydtl/src/erlydtl_dateformat.erl: -------------------------------------------------------------------------------- 1 | -module(erlydtl_dateformat). 2 | -export([format/1, format/2]). 3 | 4 | -define(TAG_SUPPORTED(C), 5 | C =:= $a orelse 6 | C =:= $A orelse 7 | C =:= $b orelse 8 | C =:= $B orelse 9 | C =:= $d orelse 10 | C =:= $D orelse 11 | C =:= $f orelse 12 | C =:= $F orelse 13 | C =:= $g orelse 14 | C =:= $G orelse 15 | C =:= $h orelse 16 | C =:= $H orelse 17 | C =:= $i orelse 18 | C =:= $I orelse 19 | C =:= $j orelse 20 | C =:= $l orelse 21 | C =:= $L orelse 22 | C =:= $m orelse 23 | C =:= $M orelse 24 | C =:= $n orelse 25 | C =:= $N orelse 26 | C =:= $O orelse 27 | C =:= $P orelse 28 | C =:= $r orelse 29 | C =:= $s orelse 30 | C =:= $S orelse 31 | C =:= $t orelse 32 | C =:= $T orelse 33 | C =:= $U orelse 34 | C =:= $w orelse 35 | C =:= $W orelse 36 | C =:= $y orelse 37 | C =:= $Y orelse 38 | C =:= $z orelse 39 | C =:= $Z 40 | ). 41 | 42 | % 43 | % Format the current date/time 44 | % 45 | format(FormatString) -> 46 | {Date, Time} = erlang:localtime(), 47 | replace_tags(Date, Time, FormatString). 48 | % 49 | % Format a tuple of the form {{Y,M,D},{H,M,S}} 50 | % This is the format returned by erlang:localtime() 51 | % and other standard date/time BIFs 52 | % 53 | format({{_,_,_} = Date,{_,_,_} = Time}, FormatString) -> 54 | replace_tags(Date, Time, FormatString); 55 | % 56 | % Format a tuple of the form {Y,M,D} 57 | % 58 | format({_,_,_} = Date, FormatString) -> 59 | replace_tags(Date, {0,0,0}, FormatString); 60 | format(DateTime, FormatString) -> 61 | io:format("Unrecognised date paramater : ~p~n", [DateTime]), 62 | FormatString. 63 | 64 | replace_tags(Date, Time, Input) -> 65 | replace_tags(Date, Time, Input, [], noslash). 66 | replace_tags(_Date, _Time, [], Out, _State) -> 67 | lists:reverse(Out); 68 | replace_tags(Date, Time, [C|Rest], Out, noslash) when ?TAG_SUPPORTED(C) -> 69 | replace_tags(Date, Time, Rest, 70 | lists:reverse(tag_to_value(C, Date, Time)) ++ Out, noslash); 71 | replace_tags(Date, Time, [$\\|Rest], Out, noslash) -> 72 | replace_tags(Date, Time, Rest, Out, slash); 73 | replace_tags(Date, Time, [C|Rest], Out, slash) -> 74 | replace_tags(Date, Time, Rest, [C|Out], noslash); 75 | replace_tags(Date, Time, [C|Rest], Out, _State) -> 76 | replace_tags(Date, Time, Rest, [C|Out], noslash). 77 | 78 | 79 | %----------------------------------------------------------- 80 | % Time formatting 81 | %----------------------------------------------------------- 82 | 83 | % 'a.m.' or 'p.m.' 84 | tag_to_value($a, _, {H, _, _}) when H > 11 -> "p.m."; 85 | tag_to_value($a, _, _) -> "a.m."; 86 | 87 | % 'AM' or 'PM' 88 | tag_to_value($A, _, {H, _, _}) when H > 11 -> "PM"; 89 | tag_to_value($A, _, _) -> "AM"; 90 | 91 | % Swatch Internet time 92 | tag_to_value($B, _, _) -> 93 | ""; % NotImplementedError 94 | 95 | % 96 | % Time, in 12-hour hours and minutes, with minutes 97 | % left off if they're zero. 98 | % 99 | % Examples: '1', '1:30', '2:05', '2' 100 | % 101 | % Proprietary extension. 102 | % 103 | tag_to_value($f, Date, {H, 0, S}) -> 104 | % If min is zero then return the hour only 105 | tag_to_value($g, Date, {H, 0, S}); 106 | tag_to_value($f, Date, Time) -> 107 | % Otherwise return hours and mins 108 | tag_to_value($g, Date, Time) 109 | ++ ":" ++ tag_to_value($i, Date, Time); 110 | 111 | % Hour, 12-hour format without leading zeros; i.e. '1' to '12' 112 | tag_to_value($g, _, {H,_,_}) -> 113 | integer_to_list(hour_24to12(H)); 114 | 115 | % Hour, 24-hour format without leading zeros; i.e. '0' to '23' 116 | tag_to_value($G, _, {H,_,_}) -> 117 | integer_to_list(H); 118 | 119 | % Hour, 12-hour format; i.e. '01' to '12' 120 | tag_to_value($h, _, {H,_,_}) -> 121 | integer_to_list_zerofill(integer_to_list(hour_24to12(H))); 122 | 123 | % Hour, 24-hour format; i.e. '00' to '23' 124 | tag_to_value($H, _, {H,_,_}) -> 125 | integer_to_list_zerofill(H); 126 | 127 | % Minutes; i.e. '00' to '59' 128 | tag_to_value($i, _, {_,M,_}) -> 129 | integer_to_list_zerofill(M); 130 | 131 | % Time, in 12-hour hours, minutes and 'a.m.'/'p.m.', with minutes left off 132 | % if they're zero and the strings 'midnight' and 'noon' if appropriate. 133 | % Examples: '1 a.m.', '1:30 p.m.', 'midnight', 'noon', '12:30 p.m.' 134 | % Proprietary extension. 135 | tag_to_value($P, _, {0, 0, _}) -> "midnight"; 136 | tag_to_value($P, _, {12, 0, _}) -> "noon"; 137 | tag_to_value($P, Date, Time) -> 138 | tag_to_value($f, Date, Time) 139 | ++ " " ++ tag_to_value($a, Date, Time); 140 | 141 | % Seconds; i.e. '00' to '59' 142 | tag_to_value($s, _, {_,_,S}) -> 143 | integer_to_list_zerofill(S); 144 | 145 | %----------------------------------------------------------- 146 | % Date formatting 147 | %----------------------------------------------------------- 148 | 149 | % Month, textual, 3 letters, lowercase; e.g. 'jan' 150 | tag_to_value($b, {_,M,_}, _) -> 151 | string:sub_string(monthname(M), 1, 3); 152 | 153 | % Day of the month, 2 digits with leading zeros; i.e. '01' to '31' 154 | tag_to_value($d, {_, _, D}, _) -> 155 | integer_to_list_zerofill(D); 156 | 157 | % Day of the week, textual, 3 letters; e.g. 'Fri' 158 | tag_to_value($D, Date, _) -> 159 | Dow = calendar:day_of_the_week(Date), 160 | ucfirst(string:sub_string(dayname(Dow), 1, 3)); 161 | 162 | % Month, textual, long; e.g. 'January' 163 | tag_to_value($F, {_,M,_}, _) -> 164 | ucfirst(monthname(M)); 165 | 166 | % '1' if Daylight Savings Time, '0' otherwise. 167 | tag_to_value($I, _, _) -> 168 | "TODO"; 169 | 170 | % Day of the month without leading zeros; i.e. '1' to '31' 171 | tag_to_value($j, {_, _, D}, _) -> 172 | integer_to_list(D); 173 | 174 | % Day of the week, textual, long; e.g. 'Friday' 175 | tag_to_value($l, Date, _) -> 176 | ucfirst(dayname(calendar:day_of_the_week(Date))); 177 | 178 | % Boolean for whether it is a leap year; i.e. True or False 179 | tag_to_value($L, {Y,_,_}, _) -> 180 | case calendar:is_leap_year(Y) of 181 | true -> "True"; 182 | _ -> "False" 183 | end; 184 | 185 | % Month; i.e. '01' to '12' 186 | tag_to_value($m, {_, M, _}, _) -> 187 | integer_to_list_zerofill(M); 188 | 189 | % Month, textual, 3 letters; e.g. 'Jan' 190 | tag_to_value($M, {_,M,_}, _) -> 191 | ucfirst(string:sub_string(monthname(M), 1, 3)); 192 | 193 | % Month without leading zeros; i.e. '1' to '12' 194 | tag_to_value($n, {_, M, _}, _) -> 195 | integer_to_list(M); 196 | 197 | % Month abbreviation in Associated Press style. Proprietary extension. 198 | tag_to_value($N, {_,M,_}, _) when M =:= 9 -> 199 | % Special case - "Sept." 200 | ucfirst(string:sub_string(monthname(M), 1, 4)) ++ "."; 201 | tag_to_value($N, {_,M,_}, _) when M < 3 orelse M > 7 -> 202 | % Jan, Feb, Aug, Oct, Nov, Dec are all 203 | % abbreviated with a full-stop appended. 204 | ucfirst(string:sub_string(monthname(M), 1, 3)) ++ "."; 205 | tag_to_value($N, {_,M,_}, _) -> 206 | % The rest are the fullname. 207 | ucfirst(monthname(M)); 208 | 209 | % Difference to Greenwich time in hours; e.g. '+0200' 210 | tag_to_value($O, Date, Time) -> 211 | Diff = utc_diff(Date, Time), 212 | Offset = case utc_diff(Date, Time) of 213 | Diff when abs(Diff) > 2400 -> "+0000"; 214 | Diff when Diff < 0 -> 215 | io_lib:format("-~4..0w", [trunc(abs(Diff))]); 216 | _ -> 217 | io_lib:format("+~4..0w", [trunc(abs(Diff))]) 218 | end, 219 | lists:flatten(Offset); 220 | 221 | % RFC 2822 formatted date; e.g. 'Thu, 21 Dec 2000 16:01:07 +0200' 222 | tag_to_value($r, Date, Time) -> 223 | replace_tags(Date, Time, "D, j M Y H:i:s O"); 224 | 225 | % English ordinal suffix for the day of the month, 2 characters; 226 | % i.e. 'st', 'nd', 'rd' or 'th' 227 | tag_to_value($S, {_, _, D}, _) when 228 | D rem 100 =:= 11 orelse 229 | D rem 100 =:= 12 orelse 230 | D rem 100 =:= 13 -> "th"; 231 | tag_to_value($S, {_, _, D}, _) when D rem 10 =:= 1 -> "st"; 232 | tag_to_value($S, {_, _, D}, _) when D rem 10 =:= 2 -> "nd"; 233 | tag_to_value($S, {_, _, D}, _) when D rem 10 =:= 3 -> "rd"; 234 | tag_to_value($S, _, _) -> "th"; 235 | 236 | % Number of days in the given month; i.e. '28' to '31' 237 | tag_to_value($t, {Y,M,_}, _) -> 238 | integer_to_list(calendar:last_day_of_the_month(Y,M)); 239 | 240 | % Time zone of this machine; e.g. 'EST' or 'MDT' 241 | tag_to_value($T, _, _) -> 242 | "TODO"; 243 | 244 | % Seconds since the Unix epoch (January 1 1970 00:00:00 GMT) 245 | tag_to_value($U, Date, Time) -> 246 | EpochSecs = calendar:datetime_to_gregorian_seconds({Date, Time}) 247 | - calendar:datetime_to_gregorian_seconds({{1970,1,1},{0,0,0}}), 248 | integer_to_list(EpochSecs); 249 | 250 | % Day of the week, numeric, i.e. '0' (Sunday) to '6' (Saturday) 251 | tag_to_value($w, Date, _) -> 252 | % Note: calendar:day_of_the_week returns 253 | % 1 | .. | 7. Monday = 1, Tuesday = 2, ..., Sunday = 7 254 | integer_to_list(calendar:day_of_the_week(Date) rem 7); 255 | 256 | % ISO-8601 week number of year, weeks starting on Monday 257 | tag_to_value($W, {Y,M,D}, _) -> 258 | integer_to_list(year_weeknum(Y,M,D)); 259 | 260 | % Year, 2 digits; e.g. '99' 261 | tag_to_value($y, {Y, _, _}, _) -> 262 | string:sub_string(integer_to_list(Y), 3); 263 | 264 | % Year, 4 digits; e.g. '1999' 265 | tag_to_value($Y, {Y, _, _}, _) -> 266 | integer_to_list(Y); 267 | 268 | % Day of the year; i.e. '0' to '365' 269 | tag_to_value($z, {Y,M,D}, _) -> 270 | integer_to_list(day_of_year(Y,M,D)); 271 | 272 | % Time zone offset in seconds (i.e. '-43200' to '43200'). The offset for 273 | % timezones west of UTC is always negative, and for those east of UTC is 274 | % always positive. 275 | tag_to_value($Z, _, _) -> 276 | "TODO"; 277 | 278 | tag_to_value(C, Date, Time) -> 279 | io:format("Unimplemented tag : ~p [Date : ~p] [Time : ~p]", 280 | [C, Date, Time]), 281 | "". 282 | 283 | % Date helper functions 284 | day_of_year(Y,M,D) -> 285 | day_of_year(Y,M,D,0). 286 | day_of_year(_Y,M,D,Count) when M =< 1 -> 287 | D + Count; 288 | day_of_year(Y,M,D,Count) when M =< 12 -> 289 | day_of_year(Y, M - 1, D, Count + calendar:last_day_of_the_month(Y,M)); 290 | day_of_year(Y,_M,D,_Count) -> 291 | day_of_year(Y, 12, D, 0). 292 | 293 | hour_24to12(0) -> 12; 294 | hour_24to12(H) when H < 13 -> H; 295 | hour_24to12(H) when H < 24 -> H - 12; 296 | hour_24to12(H) -> H. 297 | 298 | year_weeknum(Y,M,D) -> 299 | First = (calendar:day_of_the_week(Y, 1, 1) rem 7) - 1, 300 | Wk = ((((calendar:date_to_gregorian_days(Y, M, D) - 301 | calendar:date_to_gregorian_days(Y, 1, 1)) + First) div 7) 302 | + (case First < 4 of true -> 1; _ -> 0 end)), 303 | case Wk of 304 | 0 -> weeks_in_year(Y - 1); 305 | _ -> case weeks_in_year(Y) of 306 | WksInThisYear when Wk > WksInThisYear -> 1; 307 | _ -> Wk 308 | end 309 | end. 310 | 311 | weeks_in_year(Y) -> 312 | D1 = calendar:day_of_the_week(Y, 1, 1), 313 | D2 = calendar:day_of_the_week(Y, 12, 31), 314 | if (D1 =:= 4 orelse D2 =:= 4) -> 53; true -> 52 end. 315 | 316 | utc_diff(Date, Time) -> 317 | % Note: this code is plagarised from yaws_log 318 | UTime = erlang:universaltime(), 319 | DiffSecs = calendar:datetime_to_gregorian_seconds({Date,Time}) 320 | - calendar:datetime_to_gregorian_seconds(UTime), 321 | ((DiffSecs/3600)*100). 322 | 323 | dayname(1) -> "monday"; 324 | dayname(2) -> "tuesday"; 325 | dayname(3) -> "wednesday"; 326 | dayname(4) -> "thursday"; 327 | dayname(5) -> "friday"; 328 | dayname(6) -> "saturday"; 329 | dayname(7) -> "sunday"; 330 | dayname(_) -> "???". 331 | 332 | monthname(1) -> "january"; 333 | monthname(2) -> "february"; 334 | monthname(3) -> "march"; 335 | monthname(4) -> "april"; 336 | monthname(5) -> "may"; 337 | monthname(6) -> "june"; 338 | monthname(7) -> "july"; 339 | monthname(8) -> "august"; 340 | monthname(9) -> "september"; 341 | monthname(10) -> "october"; 342 | monthname(11) -> "november"; 343 | monthname(12) -> "december"; 344 | monthname(_) -> "???". 345 | 346 | % Utility functions 347 | integer_to_list_zerofill(N) when N < 10 -> 348 | lists:flatten(io_lib:format("~2..0B", [N])); 349 | integer_to_list_zerofill(N) -> 350 | integer_to_list(N). 351 | 352 | ucfirst([First | Rest]) when First >= $a, First =< $z -> 353 | [First-($a-$A) | Rest]; 354 | ucfirst(Other) -> 355 | Other. 356 | 357 | 358 | -------------------------------------------------------------------------------- /deps/erlydtl/src/erlydtl_deps.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% File: erlydtl_deps.erl 3 | %%% @author Roberto Saccon [http://rsaccon.com] 4 | %%% @author Evan Miller 5 | %%% @copyright 2008 Roberto Saccon, Evan Miller 6 | %%% @doc 7 | %%% ErlyDTL helper module 8 | %%% @end 9 | %%% 10 | %%% The MIT License 11 | %%% 12 | %%% Copyright (c) 2007 Roberto Saccon, Evan Miller 13 | %%% 14 | %%% Permission is hereby granted, free of charge, to any person obtaining a copy 15 | %%% of this software and associated documentation files (the "Software"), to deal 16 | %%% in the Software without restriction, including without limitation the rights 17 | %%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | %%% copies of the Software, and to permit persons to whom the Software is 19 | %%% furnished to do so, subject to the following conditions: 20 | %%% 21 | %%% The above copyright notice and this permission notice shall be included in 22 | %%% all copies or substantial portions of the Software. 23 | %%% 24 | %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | %%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | %%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | %%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | %%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 30 | %%% THE SOFTWARE. 31 | %%% 32 | %%% @since 2007-12-16 by Roberto Saccon, Evan Miller 33 | %%%------------------------------------------------------------------- 34 | -module(erlydtl_deps). 35 | -author('rsaccon@gmail.com'). 36 | -author('emmiller@gmail.com'). 37 | 38 | %% API 39 | -export([get_base_dir/0, get_base_dir/1]). 40 | 41 | %%==================================================================== 42 | %% API 43 | %%==================================================================== 44 | %% @spec get_base_dir(Module) -> string() 45 | %% @doc Return the application directory for Module. It assumes Module is in 46 | %% a standard OTP layout application in the ebin or src directory. 47 | get_base_dir(Module) -> 48 | {file, Here} = code:is_loaded(Module), 49 | filename:dirname(filename:dirname(Here)). 50 | 51 | %% @spec get_base_dir() -> string() 52 | %% @doc Return the application directory for this application. Equivalent to 53 | %% get_base_dir(?MODULE). 54 | get_base_dir() -> 55 | get_base_dir(?MODULE). 56 | 57 | %%==================================================================== 58 | %% Internal functions 59 | %%==================================================================== 60 | 61 | -------------------------------------------------------------------------------- /deps/erlydtl/src/erlydtl_filters.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% File: erlydtl_filters.erl 3 | %%% @author Roberto Saccon [http://rsaccon.com] 4 | %%% @author Evan Miller 5 | %%% @copyright 2008 Roberto Saccon, Evan Miller 6 | %%% @doc 7 | %%% Template filters 8 | %%% @end 9 | %%% 10 | %%% The MIT License 11 | %%% 12 | %%% Copyright (c) 2007 Roberto Saccon, Evan Miller 13 | %%% 14 | %%% Permission is hereby granted, free of charge, to any person obtaining a copy 15 | %%% of this software and associated documentation files (the "Software"), to deal 16 | %%% in the Software without restriction, including without limitation the rights 17 | %%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | %%% copies of the Software, and to permit persons to whom the Software is 19 | %%% furnished to do so, subject to the following conditions: 20 | %%% 21 | %%% The above copyright notice and this permission notice shall be included in 22 | %%% all copies or substantial portions of the Software. 23 | %%% 24 | %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | %%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | %%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | %%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | %%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 30 | %%% THE SOFTWARE. 31 | %%% 32 | %%% @since 2007-11-11 by Roberto Saccon, Evan Miller 33 | %%%------------------------------------------------------------------- 34 | -module(erlydtl_filters). 35 | -author('rsaccon@gmail.com'). 36 | -author('emmiller@gmail.com'). 37 | 38 | -compile(export_all). 39 | 40 | -define(NO_ENCODE(C), ((C >= $a andalso C =< $z) orelse 41 | (C >= $A andalso C =< $Z) orelse 42 | (C >= $0 andalso C =< $9) orelse 43 | (C =:= $\. orelse C =:= $- 44 | orelse C =:= $~ orelse C =:= $_))). 45 | 46 | add([Input], Number) when is_list(Input) or is_binary(Input) -> 47 | add(Input, Number); 48 | add(Input, Number) when is_binary(Input) -> 49 | list_to_binary(add(binary_to_list(Input), Number)); 50 | add(Input, Number) when is_list(Input) -> 51 | integer_to_list(add(list_to_integer(Input), Number)); 52 | add(Input, Number) when is_integer(Input) -> 53 | Input + Number. 54 | 55 | capfirst([Input]) when is_list(Input) or is_binary (Input) -> 56 | capfirst(Input); 57 | capfirst([H|T]) when H >= $a andalso H =< $z -> 58 | [H + $A - $a | T]; 59 | capfirst(<>) when Byte >= $a andalso Byte =< $z -> 60 | [<<(Byte + $A - $a)>>, Binary]. 61 | 62 | center([Input], Number) when is_list(Input) or is_binary(Input) -> 63 | center(Input, Number); 64 | center(Input, Number) when is_binary(Input) -> 65 | list_to_binary(center(binary_to_list(Input), Number)); 66 | center(Input, Number) when is_list(Input) -> 67 | string:centre(Input, Number). 68 | 69 | date([Input], FormatStr) when is_list(Input) or is_binary(Input) -> 70 | date(Input, FormatStr); 71 | date(Input, FormatStr) when is_binary(Input) -> 72 | list_to_binary(date(binary_to_list(Input), FormatStr)); 73 | date([{{_,_,_} = Date,{_,_,_} = Time}], FormatStr) -> 74 | erlydtl_dateformat:format({Date, Time}, FormatStr); 75 | date([{_,_,_} = Date], FormatStr) -> 76 | erlydtl_dateformat:format(Date, FormatStr); 77 | date(Input, _FormatStr) when is_list(Input) -> 78 | io:format("Unexpected date parameter : ~p~n", [Input]), 79 | "". 80 | 81 | 82 | escapejs([Input]) when is_list(Input) or is_binary(Input) -> 83 | escapejs(Input); 84 | escapejs(Input) when is_binary(Input) -> 85 | escapejs(Input, 0); 86 | escapejs(Input) when is_list(Input) -> 87 | escapejs(Input, []). 88 | 89 | first([Input]) when is_list(Input) or is_binary(Input) -> 90 | first(Input); 91 | first([First|_Rest]) -> 92 | [First]; 93 | first(<>) -> 94 | <>. 95 | 96 | fix_ampersands([Input]) when is_list(Input) or is_binary(Input) -> 97 | fix_ampersands(Input); 98 | fix_ampersands(Input) when is_binary(Input) -> 99 | fix_ampersands(Input, 0); 100 | fix_ampersands(Input) when is_list(Input) -> 101 | fix_ampersands(Input, []). 102 | 103 | force_escape([Input]) when is_list(Input) or is_binary(Input) -> 104 | force_escape(Input); 105 | force_escape(Input) when is_list(Input) -> 106 | escape(Input, []); 107 | force_escape(Input) when is_binary(Input) -> 108 | escape(Input, 0). 109 | 110 | format_integer(Input) when is_integer(Input) -> 111 | integer_to_list(Input); 112 | format_integer(Input) -> 113 | Input. 114 | 115 | format_number(Input) when is_integer(Input) -> 116 | integer_to_list(Input); 117 | format_number(Input) when is_float(Input) -> 118 | io_lib:format("~p", [Input]); 119 | format_number(Input) -> 120 | Input. 121 | 122 | join([Input], Separator) when is_list(Input) -> 123 | string:join(Input, Separator). 124 | 125 | last([Input]) when is_list(Input) or is_binary(Input) -> 126 | last(Input); 127 | last(Input) when is_binary(Input) -> 128 | case size(Input) of 129 | 0 -> Input; 130 | N -> 131 | Offset = N - 1, 132 | <<_:Offset/binary, Byte/binary>> = Input, 133 | Byte 134 | end; 135 | last(Input) when is_list(Input) -> 136 | [lists:last(Input)]. 137 | 138 | length([]) -> "0"; 139 | length([Input]) when is_list(Input) -> 140 | integer_to_list(erlang:length(Input)); 141 | length([Input]) when is_binary(Input) -> 142 | integer_to_list(size(Input)). 143 | 144 | length_is(Input, Number) when is_list(Input), is_integer(Number) -> 145 | length_is(Input, integer_to_list(Number)); 146 | length_is(Input, Number) when is_list(Input), is_list(Number) -> 147 | ?MODULE:length(Input) =:= Number. 148 | 149 | linebreaksbr([Input]) when is_list(Input) or is_binary(Input) -> 150 | linebreaksbr(Input); 151 | linebreaksbr(Input) when is_binary(Input) -> 152 | linebreaksbr(Input, 0); 153 | linebreaksbr(Input) -> 154 | linebreaksbr(Input, []). 155 | 156 | ljust([Input], Number) when is_list(Input) or is_binary(Input) -> 157 | ljust(Input, Number); 158 | ljust(Input, Number) when is_binary(Input) -> 159 | list_to_binary(ljust(binary_to_list(Input), Number)); 160 | ljust(Input, Number) when is_list(Input) -> 161 | string:left(Input, Number). 162 | 163 | lower([Input]) when is_list(Input) or is_binary(Input) -> 164 | lower(Input); 165 | lower(Input) when is_binary(Input) -> 166 | lower(Input, 0); 167 | lower(Input) -> 168 | string:to_lower(Input). 169 | 170 | rjust([Input], Number) when is_list(Input) or is_binary(Input) -> 171 | rjust(Input, Number); 172 | rjust(Input, Number) when is_binary(Input) -> 173 | list_to_binary(rjust(binary_to_list(Input), Number)); 174 | rjust(Input, Number) -> 175 | string:right(Input, Number). 176 | 177 | upper([Input]) when is_list(Input) or is_binary(Input) -> 178 | upper(Input); 179 | upper(Input) when is_binary(Input) -> 180 | list_to_binary(upper(binary_to_list(Input))); 181 | upper(Input) -> 182 | string:to_upper(Input). 183 | 184 | urlencode([Input]) when is_list(Input) or is_binary(Input) -> 185 | urlencode(Input); 186 | urlencode(Input) when is_binary(Input) -> 187 | urlencode(Input, 0); 188 | urlencode(Input) when is_list(Input) -> 189 | urlencode(Input, []). 190 | 191 | % internal 192 | 193 | escape(Binary, Index) when is_binary(Binary) -> 194 | case Binary of 195 | <> -> 196 | process_binary_match(Pre, <<"<">>, size(Post), escape(Post, 0)); 197 | <, Post/binary>> -> 198 | process_binary_match(Pre, <<">">>, size(Post), escape(Post, 0)); 199 | <> -> 200 | process_binary_match(Pre, <<"&">>, size(Post), escape(Post, 0)); 201 | <> -> 202 | process_binary_match(Pre, <<""">>, size(Post), escape(Post, 0)); 203 | <> -> 204 | process_binary_match(Pre, <<"'">>, size(Post), escape(Post, 0)); 205 | <<_:Index/binary, _, _/binary>> -> 206 | escape(Binary, Index + 1); 207 | Binary -> 208 | Binary 209 | end; 210 | escape([], Acc) -> 211 | lists:reverse(Acc); 212 | escape("<" ++ Rest, Acc) -> 213 | escape(Rest, lists:reverse("<", Acc)); 214 | escape(">" ++ Rest, Acc) -> 215 | escape(Rest, lists:reverse(">", Acc)); 216 | escape("&" ++ Rest, Acc) -> 217 | escape(Rest, lists:reverse("&", Acc)); 218 | escape("\"" ++ Rest, Acc) -> 219 | escape(Rest, lists:reverse(""", Acc)); 220 | escape("'" ++ Rest, Acc) -> 221 | escape(Rest, lists:reverse("'", Acc)); 222 | escape([C | Rest], Acc) -> 223 | escape(Rest, [C | Acc]). 224 | 225 | 226 | escapejs([], Acc) -> 227 | lists:reverse(Acc); 228 | escapejs("'" ++ Rest, Acc) -> 229 | escapejs(Rest, lists:reverse("\\'", Acc)); 230 | escapejs("\"" ++ Rest, Acc) -> 231 | escapejs(Rest, lists:reverse("\\\"", Acc)); 232 | escapejs([C | Rest], Acc) -> 233 | escapejs(Rest, [C | Acc]); 234 | escapejs(Binary, Index) when is_binary(Binary) -> 235 | case Binary of 236 | <> -> 237 | process_binary_match(Pre, <<"\\'">>, size(Post), escapejs(Post, 0)); 238 | <> -> 239 | process_binary_match(Pre, <<"\\\"">>, size(Post), escapejs(Post, 0)); 240 | <<_:Index/binary, _/binary>> -> 241 | escapejs(Binary, Index + 1); 242 | _ -> 243 | Binary 244 | end. 245 | 246 | fix_ampersands(Input, Index) when is_binary(Input) -> 247 | case Input of 248 | <> -> 249 | process_binary_match(Pre, <<"&">>, size(Post), Post); 250 | <<_:Index/binary, _/binary>> -> 251 | fix_ampersands(Input, Index + 1); 252 | _ -> 253 | Input 254 | end; 255 | fix_ampersands([], Acc) -> 256 | lists:reverse(Acc); 257 | fix_ampersands("&" ++ Rest, Acc) -> 258 | fix_ampersands(Rest, lists:reverse("&", Acc)); 259 | fix_ampersands([C | Rest], Acc) -> 260 | fix_ampersands(Rest, [C | Acc]). 261 | 262 | linebreaksbr(Input, Index) when is_binary(Input) -> 263 | Break = <<"
">>, 264 | case Input of 265 | <> -> 266 | process_binary_match(Pre, Break, size(Post), linebreaksbr(Post, 0)); 267 | <> -> 268 | process_binary_match(Pre, Break, size(Post), linebreaksbr(Post, 0)); 269 | <<_:Index/binary, _/binary>> -> 270 | linebreaksbr(Input, Index + 1); 271 | _ -> 272 | Input 273 | end; 274 | linebreaksbr([], Acc) -> 275 | lists:reverse(Acc); 276 | linebreaksbr("\r\n" ++ Rest, Acc) -> 277 | linebreaksbr(Rest, lists:reverse("
", Acc)); 278 | linebreaksbr("\n" ++ Rest, Acc) -> 279 | linebreaksbr(Rest, lists:reverse("
", Acc)); 280 | linebreaksbr([C | Rest], Acc) -> 281 | linebreaksbr(Rest, [C | Acc]). 282 | 283 | lower(Input, Index) -> 284 | case Input of 285 | <> when Byte >= $A andalso Byte =< $Z -> 286 | process_binary_match(Pre, <<(Byte - $A + $a)>>, size(Post), lower(Post, 0)); 287 | <<_:Index/binary, _/binary>> -> 288 | lower(Input, Index + 1); 289 | _ -> 290 | Input 291 | end. 292 | 293 | % Taken from quote_plus of mochiweb_util 294 | 295 | urlencode(Input, Index) when is_binary(Input) -> 296 | case Input of 297 | <<_:Index/binary, Byte, _/binary>> when ?NO_ENCODE(Byte) -> 298 | urlencode(Input, Index + 1); 299 | <> -> 300 | process_binary_match(Pre, <<"+">>, size(Post), urlencode(Post, 0)); 301 | <> -> 302 | HiDigit = hexdigit(Hi), 303 | LoDigit = hexdigit(Lo), 304 | Code = <<$\%, HiDigit, LoDigit>>, 305 | process_binary_match(Pre, Code, size(Post), urlencode(Post, 0)); 306 | Input -> 307 | Input 308 | end; 309 | urlencode([], Acc) -> 310 | lists:reverse(Acc); 311 | urlencode([C | Rest], Acc) when ?NO_ENCODE(C) -> 312 | urlencode(Rest, [C | Acc]); 313 | urlencode([$\s | Rest], Acc) -> 314 | urlencode(Rest, [$+ | Acc]); 315 | urlencode([C | Rest], Acc) -> 316 | <> = <>, 317 | urlencode(Rest, [hexdigit(Lo), hexdigit(Hi), $\% | Acc]). 318 | 319 | hexdigit(C) when C < 10 -> $0 + C; 320 | hexdigit(C) when C < 16 -> $A + (C - 10). 321 | 322 | process_binary_match(Pre, Insertion, SizePost, Post) -> 323 | case {size(Pre), SizePost} of 324 | {0, 0} -> Insertion; 325 | {0, _} -> [Insertion, Post]; 326 | {_, 0} -> [Pre, Insertion]; 327 | _ -> [Pre, Insertion, Post] 328 | end. 329 | -------------------------------------------------------------------------------- /deps/erlydtl/src/erlydtl_parser.yrl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% File: erlydtl_parser.erl 3 | %%% @author Roberto Saccon [http://rsaccon.com] 4 | %%% @author Evan Miller 5 | %%% @copyright 2008 Roberto Saccon, Evan Miller 6 | %%% @doc Template language grammar 7 | %%% @reference See http://erlydtl.googlecode.com for more information 8 | %%% @end 9 | %%% 10 | %%% The MIT License 11 | %%% 12 | %%% Copyright (c) 2007 Roberto Saccon, Evan Miller 13 | %%% 14 | %%% Permission is hereby granted, free of charge, to any person obtaining a copy 15 | %%% of this software and associated documentation files (the "Software"), to deal 16 | %%% in the Software without restriction, including without limitation the rights 17 | %%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | %%% copies of the Software, and to permit persons to whom the Software is 19 | %%% furnished to do so, subject to the following conditions: 20 | %%% 21 | %%% The above copyright notice and this permission notice shall be included in 22 | %%% all copies or substantial portions of the Software. 23 | %%% 24 | %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | %%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | %%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | %%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | %%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 30 | %%% THE SOFTWARE. 31 | %%% 32 | %%% @since 2007-11-11 by Roberto Saccon, Evan Miller 33 | %%%------------------------------------------------------------------- 34 | 35 | Nonterminals 36 | Elements 37 | Literal 38 | 39 | ValueBraced 40 | 41 | ExtendsTag 42 | IncludeTag 43 | NowTag 44 | 45 | BlockBlock 46 | BlockBraced 47 | EndBlockBraced 48 | 49 | CommentBlock 50 | CommentBraced 51 | EndCommentBraced 52 | 53 | CycleTag 54 | CycleNames 55 | CycleNamesCompat 56 | 57 | ForBlock 58 | ForBraced 59 | EndForBraced 60 | ForExpression 61 | ForGroup 62 | 63 | IfBlock 64 | IfBraced 65 | IfExpression 66 | ElseBraced 67 | EndIfBraced 68 | 69 | IfEqualBlock 70 | IfEqualBraced 71 | IfEqualExpression 72 | EndIfEqualBraced 73 | 74 | IfNotEqualBlock 75 | IfNotEqualBraced 76 | IfNotEqualExpression 77 | EndIfNotEqualBraced 78 | 79 | AutoEscapeBlock 80 | AutoEscapeBraced 81 | EndAutoEscapeBraced 82 | 83 | Value 84 | Variable 85 | Filter 86 | 87 | LoadTag 88 | LoadNames 89 | 90 | CustomTag 91 | Args 92 | 93 | CallTag 94 | CallWithTag. 95 | 96 | Terminals 97 | autoescape_keyword 98 | block_keyword 99 | call_keyword 100 | close_tag 101 | close_var 102 | comment_keyword 103 | colon 104 | comma 105 | cycle_keyword 106 | dot 107 | else_keyword 108 | endautoescape_keyword 109 | endblock_keyword 110 | endcomment_keyword 111 | endfor_keyword 112 | endif_keyword 113 | endifequal_keyword 114 | endifnotequal_keyword 115 | equal 116 | extends_keyword 117 | for_keyword 118 | identifier 119 | if_keyword 120 | ifequal_keyword 121 | ifnotequal_keyword 122 | in_keyword 123 | include_keyword 124 | load_keyword 125 | not_keyword 126 | now_keyword 127 | number_literal 128 | open_tag 129 | open_var 130 | pipe 131 | string_literal 132 | text 133 | with_keyword. 134 | 135 | Rootsymbol 136 | Elements. 137 | 138 | Elements -> '$empty' : []. 139 | Elements -> Elements text : '$1' ++ ['$2']. 140 | Elements -> Elements ValueBraced : '$1' ++ ['$2']. 141 | Elements -> Elements ExtendsTag : '$1' ++ ['$2']. 142 | Elements -> Elements IncludeTag : '$1' ++ ['$2']. 143 | Elements -> Elements NowTag : '$1' ++ ['$2']. 144 | Elements -> Elements LoadTag : '$1' ++ ['$2']. 145 | Elements -> Elements CycleTag : '$1' ++ ['$2']. 146 | Elements -> Elements BlockBlock : '$1' ++ ['$2']. 147 | Elements -> Elements ForBlock : '$1' ++ ['$2']. 148 | Elements -> Elements IfBlock : '$1' ++ ['$2']. 149 | Elements -> Elements IfEqualBlock : '$1' ++ ['$2']. 150 | Elements -> Elements IfNotEqualBlock : '$1' ++ ['$2']. 151 | Elements -> Elements AutoEscapeBlock : '$1' ++ ['$2']. 152 | Elements -> Elements CommentBlock : '$1' ++ ['$2']. 153 | Elements -> Elements CustomTag : '$1' ++ ['$2']. 154 | Elements -> Elements CallTag : '$1' ++ ['$2']. 155 | Elements -> Elements CallWithTag : '$1' ++ ['$2']. 156 | 157 | ValueBraced -> open_var Value close_var : '$2'. 158 | 159 | Value -> Value pipe Filter : {apply_filter, '$1', '$3'}. 160 | Value -> Variable : '$1'. 161 | Value -> Literal : '$1'. 162 | 163 | Variable -> identifier : {variable, '$1'}. 164 | Variable -> Value dot identifier : {attribute, {'$3', '$1'}}. 165 | 166 | ExtendsTag -> open_tag extends_keyword string_literal close_tag : {extends, '$3'}. 167 | IncludeTag -> open_tag include_keyword string_literal close_tag : {include, '$3'}. 168 | NowTag -> open_tag now_keyword string_literal close_tag : {date, now, '$3'}. 169 | 170 | LoadTag -> open_tag load_keyword LoadNames close_tag : {load, '$3'}. 171 | LoadNames -> identifier : ['$1']. 172 | LoadNames -> LoadNames identifier : '$1' ++ ['$2']. 173 | 174 | BlockBlock -> BlockBraced Elements EndBlockBraced : {block, '$1', '$2'}. 175 | BlockBraced -> open_tag block_keyword identifier close_tag : '$3'. 176 | EndBlockBraced -> open_tag endblock_keyword close_tag. 177 | 178 | CommentBlock -> CommentBraced Elements EndCommentBraced : {comment, '$2'}. 179 | CommentBraced -> open_tag comment_keyword close_tag. 180 | EndCommentBraced -> open_tag endcomment_keyword close_tag. 181 | 182 | CycleTag -> open_tag cycle_keyword CycleNamesCompat close_tag : {cycle_compat, '$3'}. 183 | CycleTag -> open_tag cycle_keyword CycleNames close_tag : {cycle, '$3'}. 184 | 185 | CycleNames -> Value : ['$1']. 186 | CycleNames -> CycleNames Value : '$1' ++ ['$2']. 187 | 188 | CycleNamesCompat -> identifier comma : ['$1']. 189 | CycleNamesCompat -> CycleNamesCompat identifier comma : '$1' ++ ['$2']. 190 | CycleNamesCompat -> CycleNamesCompat identifier : '$1' ++ ['$2']. 191 | 192 | ForBlock -> ForBraced Elements EndForBraced : {for, '$1', '$2'}. 193 | ForBraced -> open_tag for_keyword ForExpression close_tag : '$3'. 194 | EndForBraced -> open_tag endfor_keyword close_tag. 195 | ForExpression -> ForGroup in_keyword Variable : {'in', '$1', '$3'}. 196 | ForGroup -> identifier : ['$1']. 197 | ForGroup -> ForGroup comma identifier : '$1' ++ ['$3']. 198 | 199 | IfBlock -> IfBraced Elements ElseBraced Elements EndIfBraced : {ifelse, '$1', '$2', '$4'}. 200 | IfBlock -> IfBraced Elements EndIfBraced : {'if', '$1', '$2'}. 201 | IfBraced -> open_tag if_keyword IfExpression close_tag : '$3'. 202 | IfExpression -> not_keyword IfExpression : {'not', '$2'}. 203 | IfExpression -> Value : '$1'. 204 | 205 | ElseBraced -> open_tag else_keyword close_tag. 206 | EndIfBraced -> open_tag endif_keyword close_tag. 207 | 208 | IfEqualBlock -> IfEqualBraced Elements ElseBraced Elements EndIfEqualBraced : {ifequalelse, '$1', '$2', '$4'}. 209 | IfEqualBlock -> IfEqualBraced Elements EndIfEqualBraced : {ifequal, '$1', '$2'}. 210 | IfEqualBraced -> open_tag ifequal_keyword IfEqualExpression Value close_tag : ['$3', '$4']. 211 | IfEqualExpression -> Value : '$1'. 212 | EndIfEqualBraced -> open_tag endifequal_keyword close_tag. 213 | 214 | IfNotEqualBlock -> IfNotEqualBraced Elements ElseBraced Elements EndIfNotEqualBraced : {ifnotequalelse, '$1', '$2', '$4'}. 215 | IfNotEqualBlock -> IfNotEqualBraced Elements EndIfNotEqualBraced : {ifnotequal, '$1', '$2'}. 216 | IfNotEqualBraced -> open_tag ifnotequal_keyword IfNotEqualExpression Value close_tag : ['$3', '$4']. 217 | IfNotEqualExpression -> Value : '$1'. 218 | EndIfNotEqualBraced -> open_tag endifnotequal_keyword close_tag. 219 | 220 | AutoEscapeBlock -> AutoEscapeBraced Elements EndAutoEscapeBraced : {autoescape, '$1', '$2'}. 221 | AutoEscapeBraced -> open_tag autoescape_keyword identifier close_tag : '$3'. 222 | EndAutoEscapeBraced -> open_tag endautoescape_keyword close_tag. 223 | 224 | Filter -> identifier : ['$1']. 225 | Filter -> identifier colon Literal : ['$1', '$3']. 226 | 227 | Literal -> string_literal : '$1'. 228 | Literal -> number_literal : '$1'. 229 | 230 | CustomTag -> open_tag identifier Args close_tag : {tag, '$2', '$3'}. 231 | 232 | Args -> '$empty' : []. 233 | Args -> Args identifier equal Value : '$1' ++ [{'$2', '$4'}]. 234 | 235 | CallTag -> open_tag call_keyword identifier close_tag : {call, '$3'}. 236 | CallWithTag -> open_tag call_keyword identifier with_keyword Value close_tag : {call, '$3', '$5'}. 237 | -------------------------------------------------------------------------------- /deps/erlydtl/src/erlydtl_runtime.erl: -------------------------------------------------------------------------------- 1 | -module(erlydtl_runtime). 2 | 3 | -compile(export_all). 4 | 5 | find_value(Key, L) when is_list(L) -> 6 | proplists:get_value(Key, L); 7 | find_value(Key, {GBSize, GBData}) when is_integer(GBSize) -> 8 | case gb_trees:lookup(Key, {GBSize, GBData}) of 9 | {value, Val} -> 10 | Val; 11 | _ -> 12 | undefined 13 | end; 14 | find_value(Key, Tuple) when is_tuple(Tuple) -> 15 | Module = element(1, Tuple), 16 | case Module of 17 | dict -> 18 | case dict:find(Key, Tuple) of 19 | {ok, Val} -> 20 | Val; 21 | _ -> 22 | undefined 23 | end; 24 | Module -> 25 | case proplists:get_value(Key, Module:module_info(exports)) of 26 | 1 -> 27 | Tuple:Key(); 28 | _ -> 29 | undefined 30 | end 31 | end. 32 | 33 | fetch_value(Key, Data) -> 34 | case find_value(Key, Data) of 35 | undefined -> 36 | throw({undefined_variable, Key}); 37 | Val -> 38 | Val 39 | end. 40 | 41 | are_equal(Arg1, Arg2) when Arg1 =:= Arg2 -> 42 | true; 43 | are_equal(Arg1, Arg2) when is_binary(Arg1) -> 44 | are_equal(binary_to_list(Arg1), Arg2); 45 | are_equal(Arg1, Arg2) when is_binary(Arg2) -> 46 | are_equal(Arg1, binary_to_list(Arg2)); 47 | are_equal(Arg1, Arg2) when is_integer(Arg1) -> 48 | are_equal(integer_to_list(Arg1), Arg2); 49 | are_equal(Arg1, Arg2) when is_integer(Arg2) -> 50 | are_equal(Arg1, integer_to_list(Arg2)); 51 | are_equal([Arg1], Arg2) when is_list(Arg1) -> 52 | are_equal(Arg1, Arg2); 53 | are_equal(Arg1, [Arg2]) when is_list(Arg1) -> 54 | are_equal(Arg1, Arg2); 55 | are_equal(_, _) -> 56 | false. 57 | 58 | is_false("") -> 59 | true; 60 | is_false(false) -> 61 | true; 62 | is_false(undefined) -> 63 | true; 64 | is_false("0") -> 65 | true; 66 | is_false(<<"0">>) -> 67 | true; 68 | is_false(<<>>) -> 69 | true; 70 | is_false(_) -> 71 | false. 72 | 73 | stringify_final(In) -> 74 | stringify_final(In, []). 75 | stringify_final([], Out) -> 76 | lists:reverse(Out); 77 | stringify_final([El | Rest], Out) when is_atom(El) -> 78 | stringify_final(Rest, [atom_to_list(El) | Out]); 79 | stringify_final([El | Rest], Out) -> 80 | stringify_final(Rest, [El | Out]). 81 | 82 | init_counter_stats(List) -> 83 | init_counter_stats(List, undefined). 84 | 85 | init_counter_stats(List, Parent) -> 86 | [{counter, 1}, 87 | {counter0, 0}, 88 | {revcounter, length(List)}, 89 | {revcounter0, length(List) - 1}, 90 | {first, true}, 91 | {last, length(List) =:= 1}, 92 | {parentloop, Parent}]. 93 | 94 | increment_counter_stats([{counter, Counter}, {counter0, Counter0}, {revcounter, RevCounter}, 95 | {revcounter0, RevCounter0}, {first, _}, {last, _}, {parentloop, Parent}]) -> 96 | [{counter, Counter + 1}, 97 | {counter0, Counter0 + 1}, 98 | {revcounter, RevCounter - 1}, 99 | {revcounter0, RevCounter0 - 1}, 100 | {first, false}, {last, RevCounter0 =:= 1}, 101 | {parentloop, Parent}]. 102 | 103 | cycle(NamesTuple, Counters) when is_tuple(NamesTuple) -> 104 | element(fetch_value(counter0, Counters) rem size(NamesTuple) + 1, NamesTuple). 105 | -------------------------------------------------------------------------------- /deps/erlydtl/src/erlydtl_scanner.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% File: erlydtl_scanner.erl 3 | %%% @author Roberto Saccon [http://rsaccon.com] 4 | %%% @author Evan Miller 5 | %%% @copyright 2008 Roberto Saccon, Evan Miller 6 | %%% @doc 7 | %%% Template language scanner 8 | %%% @end 9 | %%% 10 | %%% The MIT License 11 | %%% 12 | %%% Copyright (c) 2007 Roberto Saccon, Evan Miller 13 | %%% 14 | %%% Permission is hereby granted, free of charge, to any person obtaining a copy 15 | %%% of this software and associated documentation files (the "Software"), to deal 16 | %%% in the Software without restriction, including without limitation the rights 17 | %%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | %%% copies of the Software, and to permit persons to whom the Software is 19 | %%% furnished to do so, subject to the following conditions: 20 | %%% 21 | %%% The above copyright notice and this permission notice shall be included in 22 | %%% all copies or substantial portions of the Software. 23 | %%% 24 | %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | %%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | %%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | %%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | %%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 30 | %%% THE SOFTWARE. 31 | %%% 32 | %%% @since 2007-11-11 by Roberto Saccon, Evan Miller 33 | %%%------------------------------------------------------------------- 34 | -module(erlydtl_scanner). 35 | -author('rsaccon@gmail.com'). 36 | -author('emmiller@gmail.com'). 37 | 38 | -export([scan/1]). 39 | 40 | 41 | %%==================================================================== 42 | %% API 43 | %%==================================================================== 44 | %%-------------------------------------------------------------------- 45 | %% @spec scan(T::template()) -> {ok, S::tokens()} | {error, Reason} 46 | %% @type template() = string() | binary(). Template to parse 47 | %% @type tokens() = [tuple()]. 48 | %% @doc Scan the template string T and return the a token list or 49 | %% an error. 50 | %% @end 51 | %%-------------------------------------------------------------------- 52 | scan(Template) -> 53 | scan(Template, [], {1, 1}, in_text). 54 | 55 | scan([], Scanned, _, in_text) -> 56 | {ok, lists:reverse(lists:map( 57 | fun 58 | ({identifier, Pos, String}) -> 59 | RevString = lists:reverse(String), 60 | Keywords = ["for", "endfor", "in", "include", "block", "endblock", 61 | "extends", "autoescape", "endautoescape", "if", "else", "endif", 62 | "not", "or", "and", "comment", "endcomment", "cycle", "firstof", 63 | "ifchanged", "ifequal", "endifequal", "ifnotequal", "endifnotequal", 64 | "now", "regroup", "spaceless", "endspaceless", "ssi", "templatetag", 65 | "load", "call", "with"], 66 | Type = case lists:member(RevString, Keywords) of 67 | true -> 68 | list_to_atom(RevString ++ "_keyword"); 69 | _ -> 70 | identifier 71 | end, 72 | {Type, Pos, RevString}; 73 | ({Type, Pos, String}) -> 74 | {Type, Pos, lists:reverse(String)} 75 | end, Scanned))}; 76 | 77 | scan([], _Scanned, _, {in_comment, _}) -> 78 | {error, "Reached end of file inside a comment."}; 79 | 80 | scan([], _Scanned, _, _) -> 81 | {error, "Reached end of file inside a code block."}; 82 | 83 | scan(""}); 85 | 86 | scan("{{" ++ T, Scanned, {Row, Column}, in_text) -> 87 | scan(T, [{open_var, {Row, Column}, "{{"} | Scanned], {Row, Column + 2}, {in_code, "}}"}); 88 | 89 | scan(""}); 91 | 92 | scan("{#" ++ T, Scanned, {Row, Column}, in_text) -> 93 | scan(T, Scanned, {Row, Column + 2}, {in_comment, "#}"}); 94 | 95 | scan("#}-->" ++ T, Scanned, {Row, Column}, {in_comment, "#}-->"}) -> 96 | scan(T, Scanned, {Row, Column + length("#}-->")}, in_text); 97 | 98 | scan("#}" ++ T, Scanned, {Row, Column}, {in_comment, "#}"}) -> 99 | scan(T, Scanned, {Row, Column + 2}, in_text); 100 | 101 | scan(""}); 104 | 105 | scan("{%" ++ T, Scanned, {Row, Column}, in_text) -> 106 | scan(T, [{open_tag, {Row, Column}, lists:reverse("{%")} | Scanned], 107 | {Row, Column + 2}, {in_code, "%}"}); 108 | 109 | scan([_ | T], Scanned, {Row, Column}, {in_comment, Closer}) -> 110 | scan(T, Scanned, {Row, Column + 1}, {in_comment, Closer}); 111 | 112 | scan("\n" ++ T, Scanned, {Row, Column}, in_text) -> 113 | scan(T, append_text_char(Scanned, {Row, Column}, $\n), {Row + 1, 1}, in_text); 114 | 115 | scan([H | T], Scanned, {Row, Column}, in_text) -> 116 | scan(T, append_text_char(Scanned, {Row, Column}, H), {Row, Column + 1}, in_text); 117 | 118 | scan("\"" ++ T, Scanned, {Row, Column}, {in_code, Closer}) -> 119 | scan(T, [{string_literal, {Row, Column}, "\""} | Scanned], {Row, Column + 1}, {in_double_quote, Closer}); 120 | 121 | scan("\"" ++ T, Scanned, {Row, Column}, {in_identifier, Closer}) -> 122 | scan(T, [{string_literal, {Row, Column}, "\""} | Scanned], {Row, Column + 1}, {in_double_quote, Closer}); 123 | 124 | scan("\'" ++ T, Scanned, {Row, Column}, {in_code, Closer}) -> 125 | scan(T, [{string_literal, {Row, Column}, "\""} | Scanned], {Row, Column + 1}, {in_single_quote, Closer}); 126 | 127 | scan("\'" ++ T, Scanned, {Row, Column}, {in_identifier, Closer}) -> 128 | scan(T, [{string_literal, {Row, Column}, "\""} | Scanned], {Row, Column + 1}, {in_single_quote, Closer}); 129 | 130 | scan([$\\ | T], Scanned, {Row, Column}, {in_double_quote, Closer}) -> 131 | scan(T, append_char(Scanned, $\\), {Row, Column + 1}, {in_double_quote_slash, Closer}); 132 | 133 | scan([H | T], Scanned, {Row, Column}, {in_double_quote_slash, Closer}) -> 134 | scan(T, append_char(Scanned, H), {Row, Column + 1}, {in_double_quote, Closer}); 135 | 136 | scan([$\\ | T], Scanned, {Row, Column}, {in_single_quote, Closer}) -> 137 | scan(T, append_char(Scanned, $\\), {Row, Column + 1}, {in_single_quote_slash, Closer}); 138 | 139 | scan([H | T], Scanned, {Row, Column}, {in_single_quote_slash, Closer}) -> 140 | scan(T, append_char(Scanned, H), {Row, Column + 1}, {in_single_quote, Closer}); 141 | 142 | % end quote 143 | scan("\"" ++ T, Scanned, {Row, Column}, {in_double_quote, Closer}) -> 144 | scan(T, append_char(Scanned, 34), {Row, Column + 1}, {in_code, Closer}); 145 | 146 | % treat single quotes the same as double quotes 147 | scan("\'" ++ T, Scanned, {Row, Column}, {in_single_quote, Closer}) -> 148 | scan(T, append_char(Scanned, 34), {Row, Column + 1}, {in_code, Closer}); 149 | 150 | scan([H | T], Scanned, {Row, Column}, {in_double_quote, Closer}) -> 151 | scan(T, append_char(Scanned, H), {Row, Column + 1}, {in_double_quote, Closer}); 152 | 153 | scan([H | T], Scanned, {Row, Column}, {in_single_quote, Closer}) -> 154 | scan(T, append_char(Scanned, H), {Row, Column + 1}, {in_single_quote, Closer}); 155 | 156 | 157 | scan("," ++ T, Scanned, {Row, Column}, {_, Closer}) -> 158 | scan(T, [{comma, {Row, Column}, ","} | Scanned], {Row, Column + 1}, {in_code, Closer}); 159 | 160 | scan("|" ++ T, Scanned, {Row, Column}, {_, Closer}) -> 161 | scan(T, [{pipe, {Row, Column}, "|"} | Scanned], {Row, Column + 1}, {in_code, Closer}); 162 | 163 | scan("=" ++ T, Scanned, {Row, Column}, {_, Closer}) -> 164 | scan(T, [{equal, {Row, Column}, "="} | Scanned], {Row, Column + 1}, {in_code, Closer}); 165 | 166 | scan(":" ++ T, Scanned, {Row, Column}, {_, Closer}) -> 167 | scan(T, [{colon, {Row, Column}, ":"} | Scanned], {Row, Column + 1}, {in_code, Closer}); 168 | 169 | scan("." ++ T, Scanned, {Row, Column}, {_, Closer}) -> 170 | scan(T, [{dot, {Row, Column}, "."} | Scanned], {Row, Column + 1}, {in_code, Closer}); 171 | 172 | scan(" " ++ T, Scanned, {Row, Column}, {_, Closer}) -> 173 | scan(T, Scanned, {Row, Column + 1}, {in_code, Closer}); 174 | 175 | scan("}}-->" ++ T, Scanned, {Row, Column}, {in_code, "}}-->"}) -> 176 | scan(T, [{close_var, {Row, Column}, lists:reverse("}}-->")} | Scanned], 177 | {Row, Column + 2}, in_text); 178 | 179 | scan("}}" ++ T, Scanned, {Row, Column}, {in_code, "}}"}) -> 180 | scan(T, [{close_var, {Row, Column}, "}}"} | Scanned], {Row, Column + 2}, in_text); 181 | 182 | scan("%}-->" ++ T, Scanned, {Row, Column}, {in_code, "%}-->"}) -> 183 | scan(T, [{close_tag, {Row, Column}, lists:reverse("%}-->")} | Scanned], 184 | {Row, Column + 2}, in_text); 185 | 186 | scan("%}" ++ T, Scanned, {Row, Column}, {in_code, "%}"}) -> 187 | scan(T, [{close_tag, {Row, Column}, lists:reverse("%}")} | Scanned], 188 | {Row, Column + 2}, in_text); 189 | 190 | scan([H | T], Scanned, {Row, Column}, {in_code, Closer}) -> 191 | case char_type(H) of 192 | letter_underscore -> 193 | scan(T, [{identifier, {Row, Column}, [H]} | Scanned], {Row, Column + 1}, {in_identifier, Closer}); 194 | digit -> 195 | scan(T, [{number_literal, {Row, Column}, [H]} | Scanned], {Row, Column + 1}, {in_number, Closer}); 196 | _ -> 197 | {error, io_lib:format("Illegal character line ~p column ~p", [Row, Column])} 198 | end; 199 | 200 | scan([H | T], Scanned, {Row, Column}, {in_number, Closer}) -> 201 | case char_type(H) of 202 | digit -> 203 | scan(T, append_char(Scanned, H), {Row, Column + 1}, {in_number, Closer}); 204 | _ -> 205 | {error, io_lib:format("Illegal character line ~p column ~p", [Row, Column])} 206 | end; 207 | 208 | scan([H | T], Scanned, {Row, Column}, {in_identifier, Closer}) -> 209 | case char_type(H) of 210 | letter_underscore -> 211 | scan(T, append_char(Scanned, H), {Row, Column + 1}, {in_identifier, Closer}); 212 | digit -> 213 | scan(T, append_char(Scanned, H), {Row, Column + 1}, {in_identifier, Closer}); 214 | _ -> 215 | {error, io_lib:format("Illegal character line ~p column ~p", [Row, Column])} 216 | end. 217 | 218 | % internal functions 219 | 220 | append_char(Scanned, Char) -> 221 | [String | Scanned1] = Scanned, 222 | [setelement(3, String, [Char | element(3, String)]) | Scanned1]. 223 | 224 | append_text_char(Scanned, {Row, Column}, Char) -> 225 | case length(Scanned) of 226 | 0 -> 227 | [{text, {Row, Column}, [Char]}]; 228 | _ -> 229 | [Token | Scanned1] = Scanned, 230 | case element(1, Token) of 231 | text -> 232 | [{text, element(2, Token), [Char | element(3, Token)]} | Scanned1]; 233 | _ -> 234 | [{text, element(2, Token), [Char]} | Scanned] 235 | end 236 | end. 237 | 238 | char_type(Char) -> 239 | case Char of 240 | C when ((C >= $a) and (C =< $z)) or ((C >= $A) and (C =< $Z)) or (C == $_) -> 241 | letter_underscore; 242 | C when ((C >= $0) and (C =< $9)) -> 243 | digit; 244 | _ -> 245 | undefined 246 | end. 247 | -------------------------------------------------------------------------------- /deps/mochiweb/LICENSE: -------------------------------------------------------------------------------- 1 | This is the MIT license. 2 | 3 | Copyright (c) 2007 Mochi Media, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /deps/mochiweb/README: -------------------------------------------------------------------------------- 1 | MochiWeb is an Erlang library for building lightweight HTTP servers. 2 | -------------------------------------------------------------------------------- /deps/mochiweb/ebin/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebryson/beepbeep/62db46d268c6cb6ad86345562b3c77f8ff070b27/deps/mochiweb/ebin/.gitignore -------------------------------------------------------------------------------- /deps/mochiweb/src/Makefile: -------------------------------------------------------------------------------- 1 | include ../../../support/include.mk 2 | 3 | all: $(EBIN_FILES_NO_DOCS) 4 | 5 | debug: 6 | $(MAKE) DEBUG=-DDEBUG 7 | 8 | clean: 9 | rm -rf $(EBIN_FILES_NO_DOCS) 10 | 11 | -------------------------------------------------------------------------------- /deps/mochiweb/src/mochifmt_records.erl: -------------------------------------------------------------------------------- 1 | %% @author Bob Ippolito 2 | %% @copyright 2008 Mochi Media, Inc. 3 | 4 | %% @doc Formatter that understands records. 5 | %% 6 | %% Usage: 7 | %% 8 | %% 1> M = mochifmt_records:new([{rec, record_info(fields, rec)}]), 9 | %% M:format("{0.bar}", [#rec{bar=foo}]). 10 | %% foo 11 | 12 | -module(mochifmt_records, [Recs]). 13 | -author('bob@mochimedia.com'). 14 | -export([get_value/2]). 15 | 16 | get_value(Key, Rec) when is_tuple(Rec) and is_atom(element(1, Rec)) -> 17 | try begin 18 | Atom = list_to_existing_atom(Key), 19 | {_, Fields} = proplists:lookup(element(1, Rec), Recs), 20 | element(get_rec_index(Atom, Fields, 2), Rec) 21 | end 22 | catch error:_ -> mochifmt:get_value(Key, Rec) 23 | end; 24 | get_value(Key, Args) -> 25 | mochifmt:get_value(Key, Args). 26 | 27 | get_rec_index(Atom, [Atom | _], Index) -> 28 | Index; 29 | get_rec_index(Atom, [_ | Rest], Index) -> 30 | get_rec_index(Atom, Rest, 1 + Index). 31 | -------------------------------------------------------------------------------- /deps/mochiweb/src/mochifmt_std.erl: -------------------------------------------------------------------------------- 1 | %% @author Bob Ippolito 2 | %% @copyright 2008 Mochi Media, Inc. 3 | 4 | %% @doc Template module for a mochifmt formatter. 5 | 6 | -module(mochifmt_std, []). 7 | -author('bob@mochimedia.com'). 8 | -export([format/2, get_value/2, format_field/2, get_field/2, convert_field/2]). 9 | 10 | format(Format, Args) -> 11 | mochifmt:format(Format, Args, THIS). 12 | 13 | get_field(Key, Args) -> 14 | mochifmt:get_field(Key, Args, THIS). 15 | 16 | convert_field(Key, Args) -> 17 | mochifmt:convert_field(Key, Args). 18 | 19 | get_value(Key, Args) -> 20 | mochifmt:get_value(Key, Args). 21 | 22 | format_field(Arg, Format) -> 23 | mochifmt:format_field(Arg, Format, THIS). 24 | -------------------------------------------------------------------------------- /deps/mochiweb/src/mochihex.erl: -------------------------------------------------------------------------------- 1 | %% @author Bob Ippolito 2 | %% @copyright 2006 Mochi Media, Inc. 3 | 4 | %% @doc Utilities for working with hexadecimal strings. 5 | 6 | -module(mochihex). 7 | -author('bob@mochimedia.com'). 8 | 9 | -export([test/0, to_hex/1, to_bin/1, to_int/1, dehex/1, hexdigit/1]). 10 | 11 | %% @type iolist() = [char() | binary() | iolist()] 12 | %% @type iodata() = iolist() | binary() 13 | 14 | %% @spec to_hex(integer | iolist()) -> string() 15 | %% @doc Convert an iolist to a hexadecimal string. 16 | to_hex(0) -> 17 | "0"; 18 | to_hex(I) when is_integer(I), I > 0 -> 19 | to_hex_int(I, []); 20 | to_hex(B) -> 21 | to_hex(iolist_to_binary(B), []). 22 | 23 | %% @spec to_bin(string()) -> binary() 24 | %% @doc Convert a hexadecimal string to a binary. 25 | to_bin(L) -> 26 | to_bin(L, []). 27 | 28 | %% @spec to_int(string()) -> integer() 29 | %% @doc Convert a hexadecimal string to an integer. 30 | to_int(L) -> 31 | erlang:list_to_integer(L, 16). 32 | 33 | %% @spec dehex(char()) -> integer() 34 | %% @doc Convert a hex digit to its integer value. 35 | dehex(C) when C >= $0, C =< $9 -> 36 | C - $0; 37 | dehex(C) when C >= $a, C =< $f -> 38 | C - $a + 10; 39 | dehex(C) when C >= $A, C =< $F -> 40 | C - $A + 10. 41 | 42 | %% @spec hexdigit(integer()) -> char() 43 | %% @doc Convert an integer less than 16 to a hex digit. 44 | hexdigit(C) when C >= 0, C =< 9 -> 45 | C + $0; 46 | hexdigit(C) when C =< 15 -> 47 | C + $a - 10. 48 | 49 | %% @spec test() -> ok 50 | %% @doc Test this module. 51 | test() -> 52 | "ff000ff1" = to_hex([255, 0, 15, 241]), 53 | <<255, 0, 15, 241>> = to_bin("ff000ff1"), 54 | 16#ff000ff1 = to_int("ff000ff1"), 55 | "ff000ff1" = to_hex(16#ff000ff1), 56 | ok. 57 | 58 | 59 | %% Internal API 60 | 61 | to_hex(<<>>, Acc) -> 62 | lists:reverse(Acc); 63 | to_hex(<>, Acc) -> 64 | to_hex(Rest, [hexdigit(C2), hexdigit(C1) | Acc]). 65 | 66 | to_hex_int(0, Acc) -> 67 | Acc; 68 | to_hex_int(I, Acc) -> 69 | to_hex_int(I bsr 4, [hexdigit(I band 15) | Acc]). 70 | 71 | to_bin([], Acc) -> 72 | iolist_to_binary(lists:reverse(Acc)); 73 | to_bin([C1, C2 | Rest], Acc) -> 74 | to_bin(Rest, [(dehex(C1) bsl 4) bor dehex(C2) | Acc]). 75 | 76 | -------------------------------------------------------------------------------- /deps/mochiweb/src/mochinum.erl: -------------------------------------------------------------------------------- 1 | %% @copyright 2007 Mochi Media, Inc. 2 | %% @author Bob Ippolito 3 | 4 | %% @doc Useful numeric algorithms for floats that cover some deficiencies 5 | %% in the math module. More interesting is digits/1, which implements 6 | %% the algorithm from: 7 | %% http://www.cs.indiana.edu/~burger/fp/index.html 8 | %% See also "Printing Floating-Point Numbers Quickly and Accurately" 9 | %% in Proceedings of the SIGPLAN '96 Conference on Programming Language 10 | %% Design and Implementation. 11 | 12 | -module(mochinum). 13 | -author("Bob Ippolito "). 14 | -export([digits/1, frexp/1, int_pow/2, int_ceil/1, test/0]). 15 | 16 | %% IEEE 754 Float exponent bias 17 | -define(FLOAT_BIAS, 1022). 18 | -define(MIN_EXP, -1074). 19 | -define(BIG_POW, 4503599627370496). 20 | 21 | %% External API 22 | 23 | %% @spec digits(number()) -> string() 24 | %% @doc Returns a string that accurately represents the given integer or float 25 | %% using a conservative amount of digits. Great for generating 26 | %% human-readable output, or compact ASCII serializations for floats. 27 | digits(N) when is_integer(N) -> 28 | integer_to_list(N); 29 | digits(0.0) -> 30 | "0.0"; 31 | digits(Float) -> 32 | {Frac, Exp} = frexp(Float), 33 | Exp1 = Exp - 53, 34 | Frac1 = trunc(abs(Frac) * (1 bsl 53)), 35 | [Place | Digits] = digits1(Float, Exp1, Frac1), 36 | R = insert_decimal(Place, [$0 + D || D <- Digits]), 37 | case Float < 0 of 38 | true -> 39 | [$- | R]; 40 | _ -> 41 | R 42 | end. 43 | 44 | %% @spec frexp(F::float()) -> {Frac::float(), Exp::float()} 45 | %% @doc Return the fractional and exponent part of an IEEE 754 double, 46 | %% equivalent to the libc function of the same name. 47 | %% F = Frac * pow(2, Exp). 48 | frexp(F) -> 49 | frexp1(unpack(F)). 50 | 51 | %% @spec int_pow(X::integer(), N::integer()) -> Y::integer() 52 | %% @doc Moderately efficient way to exponentiate integers. 53 | %% int_pow(10, 2) = 100. 54 | int_pow(_X, 0) -> 55 | 1; 56 | int_pow(X, N) when N > 0 -> 57 | int_pow(X, N, 1). 58 | 59 | %% @spec int_ceil(F::float()) -> integer() 60 | %% @doc Return the ceiling of F as an integer. The ceiling is defined as 61 | %% F when F == trunc(F); 62 | %% trunc(F) when F < 0; 63 | %% trunc(F) + 1 when F > 0. 64 | int_ceil(X) -> 65 | T = trunc(X), 66 | case (X - T) of 67 | Neg when Neg < 0 -> T; 68 | Pos when Pos > 0 -> T + 1; 69 | _ -> T 70 | end. 71 | 72 | 73 | %% Internal API 74 | 75 | int_pow(X, N, R) when N < 2 -> 76 | R * X; 77 | int_pow(X, N, R) -> 78 | int_pow(X * X, N bsr 1, case N band 1 of 1 -> R * X; 0 -> R end). 79 | 80 | insert_decimal(0, S) -> 81 | "0." ++ S; 82 | insert_decimal(Place, S) when Place > 0 -> 83 | L = length(S), 84 | case Place - L of 85 | 0 -> 86 | S ++ ".0"; 87 | N when N < 0 -> 88 | {S0, S1} = lists:split(L + N, S), 89 | S0 ++ "." ++ S1; 90 | N when N < 6 -> 91 | %% More places than digits 92 | S ++ lists:duplicate(N, $0) ++ ".0"; 93 | _ -> 94 | insert_decimal_exp(Place, S) 95 | end; 96 | insert_decimal(Place, S) when Place > -6 -> 97 | "0." ++ lists:duplicate(abs(Place), $0) ++ S; 98 | insert_decimal(Place, S) -> 99 | insert_decimal_exp(Place, S). 100 | 101 | insert_decimal_exp(Place, S) -> 102 | [C | S0] = S, 103 | S1 = case S0 of 104 | [] -> 105 | "0"; 106 | _ -> 107 | S0 108 | end, 109 | Exp = case Place < 0 of 110 | true -> 111 | "e-"; 112 | false -> 113 | "e+" 114 | end, 115 | [C] ++ "." ++ S1 ++ Exp ++ integer_to_list(abs(Place - 1)). 116 | 117 | 118 | digits1(Float, Exp, Frac) -> 119 | Round = ((Frac band 1) =:= 0), 120 | case Exp >= 0 of 121 | true -> 122 | BExp = 1 bsl Exp, 123 | case (Frac /= ?BIG_POW) of 124 | true -> 125 | scale((Frac * BExp * 2), 2, BExp, BExp, 126 | Round, Round, Float); 127 | false -> 128 | scale((Frac * BExp * 4), 4, (BExp * 2), BExp, 129 | Round, Round, Float) 130 | end; 131 | false -> 132 | case (Exp == ?MIN_EXP) orelse (Frac /= ?BIG_POW) of 133 | true -> 134 | scale((Frac * 2), 1 bsl (1 - Exp), 1, 1, 135 | Round, Round, Float); 136 | false -> 137 | scale((Frac * 4), 1 bsl (2 - Exp), 2, 1, 138 | Round, Round, Float) 139 | end 140 | end. 141 | 142 | scale(R, S, MPlus, MMinus, LowOk, HighOk, Float) -> 143 | Est = int_ceil(math:log10(abs(Float)) - 1.0e-10), 144 | %% Note that the scheme implementation uses a 326 element look-up table 145 | %% for int_pow(10, N) where we do not. 146 | case Est >= 0 of 147 | true -> 148 | fixup(R, S * int_pow(10, Est), MPlus, MMinus, Est, 149 | LowOk, HighOk); 150 | false -> 151 | Scale = int_pow(10, -Est), 152 | fixup(R * Scale, S, MPlus * Scale, MMinus * Scale, Est, 153 | LowOk, HighOk) 154 | end. 155 | 156 | fixup(R, S, MPlus, MMinus, K, LowOk, HighOk) -> 157 | TooLow = case HighOk of 158 | true -> 159 | (R + MPlus) >= S; 160 | false -> 161 | (R + MPlus) > S 162 | end, 163 | case TooLow of 164 | true -> 165 | [(K + 1) | generate(R, S, MPlus, MMinus, LowOk, HighOk)]; 166 | false -> 167 | [K | generate(R * 10, S, MPlus * 10, MMinus * 10, LowOk, HighOk)] 168 | end. 169 | 170 | generate(R0, S, MPlus, MMinus, LowOk, HighOk) -> 171 | D = R0 div S, 172 | R = R0 rem S, 173 | TC1 = case LowOk of 174 | true -> 175 | R =< MMinus; 176 | false -> 177 | R < MMinus 178 | end, 179 | TC2 = case HighOk of 180 | true -> 181 | (R + MPlus) >= S; 182 | false -> 183 | (R + MPlus) > S 184 | end, 185 | case TC1 of 186 | false -> 187 | case TC2 of 188 | false -> 189 | [D | generate(R * 10, S, MPlus * 10, MMinus * 10, 190 | LowOk, HighOk)]; 191 | true -> 192 | [D + 1] 193 | end; 194 | true -> 195 | case TC2 of 196 | false -> 197 | [D]; 198 | true -> 199 | case R * 2 < S of 200 | true -> 201 | [D]; 202 | false -> 203 | [D + 1] 204 | end 205 | end 206 | end. 207 | 208 | unpack(Float) -> 209 | <> = <>, 210 | {Sign, Exp, Frac}. 211 | 212 | frexp1({_Sign, 0, 0}) -> 213 | {0.0, 0}; 214 | frexp1({Sign, 0, Frac}) -> 215 | Exp = log2floor(Frac), 216 | <> = <>, 217 | {Frac1, -(?FLOAT_BIAS) - 52 + Exp}; 218 | frexp1({Sign, Exp, Frac}) -> 219 | <> = <>, 220 | {Frac1, Exp - ?FLOAT_BIAS}. 221 | 222 | log2floor(Int) -> 223 | log2floor(Int, 0). 224 | 225 | log2floor(0, N) -> 226 | N; 227 | log2floor(Int, N) -> 228 | log2floor(Int bsr 1, 1 + N). 229 | 230 | 231 | test() -> 232 | ok = test_frexp(), 233 | ok = test_int_ceil(), 234 | ok = test_int_pow(), 235 | ok = test_digits(), 236 | ok. 237 | 238 | test_int_ceil() -> 239 | 1 = int_ceil(0.0001), 240 | 0 = int_ceil(0.0), 241 | 1 = int_ceil(0.99), 242 | 1 = int_ceil(1.0), 243 | -1 = int_ceil(-1.5), 244 | -2 = int_ceil(-2.0), 245 | ok. 246 | 247 | test_int_pow() -> 248 | 1 = int_pow(1, 1), 249 | 1 = int_pow(1, 0), 250 | 1 = int_pow(10, 0), 251 | 10 = int_pow(10, 1), 252 | 100 = int_pow(10, 2), 253 | 1000 = int_pow(10, 3), 254 | ok. 255 | 256 | test_digits() -> 257 | "0" = digits(0), 258 | "0.0" = digits(0.0), 259 | "1.0" = digits(1.0), 260 | "-1.0" = digits(-1.0), 261 | "0.1" = digits(0.1), 262 | "0.01" = digits(0.01), 263 | "0.001" = digits(0.001), 264 | ok. 265 | 266 | test_frexp() -> 267 | %% zero 268 | {0.0, 0} = frexp(0.0), 269 | %% one 270 | {0.5, 1} = frexp(1.0), 271 | %% negative one 272 | {-0.5, 1} = frexp(-1.0), 273 | %% small denormalized number 274 | %% 4.94065645841246544177e-324 275 | <> = <<0,0,0,0,0,0,0,1>>, 276 | {0.5, -1073} = frexp(SmallDenorm), 277 | %% large denormalized number 278 | %% 2.22507385850720088902e-308 279 | <> = <<0,15,255,255,255,255,255,255>>, 280 | {0.99999999999999978, -1022} = frexp(BigDenorm), 281 | %% small normalized number 282 | %% 2.22507385850720138309e-308 283 | <> = <<0,16,0,0,0,0,0,0>>, 284 | {0.5, -1021} = frexp(SmallNorm), 285 | %% large normalized number 286 | %% 1.79769313486231570815e+308 287 | <> = <<127,239,255,255,255,255,255,255>>, 288 | {0.99999999999999989, 1024} = frexp(LargeNorm), 289 | ok. 290 | -------------------------------------------------------------------------------- /deps/mochiweb/src/mochiweb.app: -------------------------------------------------------------------------------- 1 | {application, mochiweb, 2 | [{description, "MochiMedia Web Server"}, 3 | {vsn, "0.01"}, 4 | {modules, [ 5 | mochihex, 6 | mochijson, 7 | mochijson2, 8 | mochinum, 9 | mochiweb, 10 | mochiweb_app, 11 | mochiweb_charref, 12 | mochiweb_cookies, 13 | mochiweb_echo, 14 | mochiweb_headers, 15 | mochiweb_html, 16 | mochiweb_http, 17 | mochiweb_multipart, 18 | mochiweb_request, 19 | mochiweb_response, 20 | mochiweb_skel, 21 | mochiweb_socket_server, 22 | mochiweb_sup, 23 | mochiweb_util, 24 | reloader 25 | ]}, 26 | {registered, []}, 27 | {mod, {mochiweb_app, []}}, 28 | {env, []}, 29 | {applications, [kernel, stdlib]}]}. 30 | -------------------------------------------------------------------------------- /deps/mochiweb/src/mochiweb.erl: -------------------------------------------------------------------------------- 1 | %% @author Bob Ippolito 2 | %% @copyright 2007 Mochi Media, Inc. 3 | 4 | %% @doc Start and stop the MochiWeb server. 5 | 6 | -module(mochiweb). 7 | -author('bob@mochimedia.com'). 8 | 9 | -export([start/0, stop/0]). 10 | -export([new_request/1, new_response/1]). 11 | -export([all_loaded/0, all_loaded/1, reload/0]). 12 | -export([test/0]). 13 | 14 | %% @spec start() -> ok 15 | %% @doc Start the MochiWeb server. 16 | start() -> 17 | ensure_started(crypto), 18 | application:start(mochiweb). 19 | 20 | %% @spec stop() -> ok 21 | %% @doc Stop the MochiWeb server. 22 | stop() -> 23 | Res = application:stop(mochiweb), 24 | application:stop(crypto), 25 | Res. 26 | 27 | %% @spec test() -> ok 28 | %% @doc Run all of the tests for MochiWeb. 29 | test() -> 30 | mochiweb_util:test(), 31 | mochiweb_headers:test(), 32 | mochiweb_cookies:test(), 33 | mochihex:test(), 34 | mochinum:test(), 35 | mochijson:test(), 36 | mochiweb_charref:test(), 37 | mochiweb_html:test(), 38 | mochifmt:test(), 39 | test_request(), 40 | ok. 41 | 42 | reload() -> 43 | [c:l(Module) || Module <- all_loaded()]. 44 | 45 | all_loaded() -> 46 | all_loaded(filename:dirname(code:which(?MODULE))). 47 | 48 | all_loaded(Base) when is_atom(Base) -> 49 | []; 50 | all_loaded(Base) -> 51 | FullBase = Base ++ "/", 52 | F = fun ({_Module, Loaded}, Acc) when is_atom(Loaded) -> 53 | Acc; 54 | ({Module, Loaded}, Acc) -> 55 | case lists:prefix(FullBase, Loaded) of 56 | true -> 57 | [Module | Acc]; 58 | false -> 59 | Acc 60 | end 61 | end, 62 | lists:foldl(F, [], code:all_loaded()). 63 | 64 | 65 | %% @spec new_request({Socket, Request, Headers}) -> MochiWebRequest 66 | %% @doc Return a mochiweb_request data structure. 67 | new_request({Socket, {Method, {abs_path, Uri}, Version}, Headers}) -> 68 | mochiweb_request:new(Socket, 69 | Method, 70 | Uri, 71 | Version, 72 | mochiweb_headers:make(Headers)); 73 | % this case probably doesn't "exist". 74 | new_request({Socket, {Method, {absoluteURI, _Protocol, _Host, _Port, Uri}, 75 | Version}, Headers}) -> 76 | mochiweb_request:new(Socket, 77 | Method, 78 | Uri, 79 | Version, 80 | mochiweb_headers:make(Headers)); 81 | %% Request-URI is "*" 82 | %% From http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2 83 | new_request({Socket, {Method, '*'=Uri, Version}, Headers}) -> 84 | mochiweb_request:new(Socket, 85 | Method, 86 | Uri, 87 | Version, 88 | mochiweb_headers:make(Headers)). 89 | 90 | %% @spec new_response({Request, integer(), Headers}) -> MochiWebResponse 91 | %% @doc Return a mochiweb_response data structure. 92 | new_response({Request, Code, Headers}) -> 93 | mochiweb_response:new(Request, 94 | Code, 95 | mochiweb_headers:make(Headers)). 96 | 97 | %% Internal API 98 | 99 | test_request() -> 100 | R = mochiweb_request:new(z, z, "/foo/bar/baz%20wibble+quux?qs=2", z, []), 101 | "/foo/bar/baz wibble quux" = R:get(path), 102 | ok. 103 | 104 | ensure_started(App) -> 105 | case application:start(App) of 106 | ok -> 107 | ok; 108 | {error, {already_started, App}} -> 109 | ok 110 | end. 111 | -------------------------------------------------------------------------------- /deps/mochiweb/src/mochiweb_app.erl: -------------------------------------------------------------------------------- 1 | %% @author Bob Ippolito 2 | %% @copyright 2007 Mochi Media, Inc. 3 | 4 | %% @doc Callbacks for the mochiweb application. 5 | 6 | -module(mochiweb_app). 7 | -author('bob@mochimedia.com'). 8 | 9 | -behaviour(application). 10 | -export([start/2,stop/1]). 11 | 12 | %% @spec start(_Type, _StartArgs) -> ServerRet 13 | %% @doc application start callback for mochiweb. 14 | start(_Type, _StartArgs) -> 15 | mochiweb_sup:start_link(). 16 | 17 | %% @spec stop(_State) -> ServerRet 18 | %% @doc application stop callback for mochiweb. 19 | stop(_State) -> 20 | ok. 21 | -------------------------------------------------------------------------------- /deps/mochiweb/src/mochiweb_charref.erl: -------------------------------------------------------------------------------- 1 | %% @author Bob Ippolito 2 | %% @copyright 2007 Mochi Media, Inc. 3 | 4 | %% @doc Converts HTML 4 charrefs and entities to codepoints. 5 | -module(mochiweb_charref). 6 | -export([charref/1, test/0]). 7 | 8 | %% External API. 9 | 10 | %% @spec charref(S) -> integer() | undefined 11 | %% @doc Convert a decimal charref, hex charref, or html entity to a unicode 12 | %% codepoint, or return undefined on failure. 13 | %% The input should not include an ampersand or semicolon. 14 | %% charref("#38") = 38, charref("#x26") = 38, charref("amp") = 38. 15 | charref(B) when is_binary(B) -> 16 | charref(binary_to_list(B)); 17 | charref([$#, C | L]) when C =:= $x orelse C =:= $X -> 18 | try erlang:list_to_integer(L, 16) 19 | catch 20 | error:badarg -> undefined 21 | end; 22 | charref([$# | L]) -> 23 | try list_to_integer(L) 24 | catch 25 | error:badarg -> undefined 26 | end; 27 | charref(L) -> 28 | entity(L). 29 | 30 | %% @spec test() -> ok 31 | %% @doc Run tests for mochiweb_charref. 32 | test() -> 33 | 1234 = charref("#1234"), 34 | 255 = charref("#xfF"), 35 | 255 = charref("#XFf"), 36 | 38 = charref("amp"), 37 | undefined = charref("not_an_entity"), 38 | ok. 39 | 40 | %% Internal API. 41 | 42 | entity("nbsp") -> 160; 43 | entity("iexcl") -> 161; 44 | entity("cent") -> 162; 45 | entity("pound") -> 163; 46 | entity("curren") -> 164; 47 | entity("yen") -> 165; 48 | entity("brvbar") -> 166; 49 | entity("sect") -> 167; 50 | entity("uml") -> 168; 51 | entity("copy") -> 169; 52 | entity("ordf") -> 170; 53 | entity("laquo") -> 171; 54 | entity("not") -> 172; 55 | entity("shy") -> 173; 56 | entity("reg") -> 174; 57 | entity("macr") -> 175; 58 | entity("deg") -> 176; 59 | entity("plusmn") -> 177; 60 | entity("sup2") -> 178; 61 | entity("sup3") -> 179; 62 | entity("acute") -> 180; 63 | entity("micro") -> 181; 64 | entity("para") -> 182; 65 | entity("middot") -> 183; 66 | entity("cedil") -> 184; 67 | entity("sup1") -> 185; 68 | entity("ordm") -> 186; 69 | entity("raquo") -> 187; 70 | entity("frac14") -> 188; 71 | entity("frac12") -> 189; 72 | entity("frac34") -> 190; 73 | entity("iquest") -> 191; 74 | entity("Agrave") -> 192; 75 | entity("Aacute") -> 193; 76 | entity("Acirc") -> 194; 77 | entity("Atilde") -> 195; 78 | entity("Auml") -> 196; 79 | entity("Aring") -> 197; 80 | entity("AElig") -> 198; 81 | entity("Ccedil") -> 199; 82 | entity("Egrave") -> 200; 83 | entity("Eacute") -> 201; 84 | entity("Ecirc") -> 202; 85 | entity("Euml") -> 203; 86 | entity("Igrave") -> 204; 87 | entity("Iacute") -> 205; 88 | entity("Icirc") -> 206; 89 | entity("Iuml") -> 207; 90 | entity("ETH") -> 208; 91 | entity("Ntilde") -> 209; 92 | entity("Ograve") -> 210; 93 | entity("Oacute") -> 211; 94 | entity("Ocirc") -> 212; 95 | entity("Otilde") -> 213; 96 | entity("Ouml") -> 214; 97 | entity("times") -> 215; 98 | entity("Oslash") -> 216; 99 | entity("Ugrave") -> 217; 100 | entity("Uacute") -> 218; 101 | entity("Ucirc") -> 219; 102 | entity("Uuml") -> 220; 103 | entity("Yacute") -> 221; 104 | entity("THORN") -> 222; 105 | entity("szlig") -> 223; 106 | entity("agrave") -> 224; 107 | entity("aacute") -> 225; 108 | entity("acirc") -> 226; 109 | entity("atilde") -> 227; 110 | entity("auml") -> 228; 111 | entity("aring") -> 229; 112 | entity("aelig") -> 230; 113 | entity("ccedil") -> 231; 114 | entity("egrave") -> 232; 115 | entity("eacute") -> 233; 116 | entity("ecirc") -> 234; 117 | entity("euml") -> 235; 118 | entity("igrave") -> 236; 119 | entity("iacute") -> 237; 120 | entity("icirc") -> 238; 121 | entity("iuml") -> 239; 122 | entity("eth") -> 240; 123 | entity("ntilde") -> 241; 124 | entity("ograve") -> 242; 125 | entity("oacute") -> 243; 126 | entity("ocirc") -> 244; 127 | entity("otilde") -> 245; 128 | entity("ouml") -> 246; 129 | entity("divide") -> 247; 130 | entity("oslash") -> 248; 131 | entity("ugrave") -> 249; 132 | entity("uacute") -> 250; 133 | entity("ucirc") -> 251; 134 | entity("uuml") -> 252; 135 | entity("yacute") -> 253; 136 | entity("thorn") -> 254; 137 | entity("yuml") -> 255; 138 | entity("fnof") -> 402; 139 | entity("Alpha") -> 913; 140 | entity("Beta") -> 914; 141 | entity("Gamma") -> 915; 142 | entity("Delta") -> 916; 143 | entity("Epsilon") -> 917; 144 | entity("Zeta") -> 918; 145 | entity("Eta") -> 919; 146 | entity("Theta") -> 920; 147 | entity("Iota") -> 921; 148 | entity("Kappa") -> 922; 149 | entity("Lambda") -> 923; 150 | entity("Mu") -> 924; 151 | entity("Nu") -> 925; 152 | entity("Xi") -> 926; 153 | entity("Omicron") -> 927; 154 | entity("Pi") -> 928; 155 | entity("Rho") -> 929; 156 | entity("Sigma") -> 931; 157 | entity("Tau") -> 932; 158 | entity("Upsilon") -> 933; 159 | entity("Phi") -> 934; 160 | entity("Chi") -> 935; 161 | entity("Psi") -> 936; 162 | entity("Omega") -> 937; 163 | entity("alpha") -> 945; 164 | entity("beta") -> 946; 165 | entity("gamma") -> 947; 166 | entity("delta") -> 948; 167 | entity("epsilon") -> 949; 168 | entity("zeta") -> 950; 169 | entity("eta") -> 951; 170 | entity("theta") -> 952; 171 | entity("iota") -> 953; 172 | entity("kappa") -> 954; 173 | entity("lambda") -> 955; 174 | entity("mu") -> 956; 175 | entity("nu") -> 957; 176 | entity("xi") -> 958; 177 | entity("omicron") -> 959; 178 | entity("pi") -> 960; 179 | entity("rho") -> 961; 180 | entity("sigmaf") -> 962; 181 | entity("sigma") -> 963; 182 | entity("tau") -> 964; 183 | entity("upsilon") -> 965; 184 | entity("phi") -> 966; 185 | entity("chi") -> 967; 186 | entity("psi") -> 968; 187 | entity("omega") -> 969; 188 | entity("thetasym") -> 977; 189 | entity("upsih") -> 978; 190 | entity("piv") -> 982; 191 | entity("bull") -> 8226; 192 | entity("hellip") -> 8230; 193 | entity("prime") -> 8242; 194 | entity("Prime") -> 8243; 195 | entity("oline") -> 8254; 196 | entity("frasl") -> 8260; 197 | entity("weierp") -> 8472; 198 | entity("image") -> 8465; 199 | entity("real") -> 8476; 200 | entity("trade") -> 8482; 201 | entity("alefsym") -> 8501; 202 | entity("larr") -> 8592; 203 | entity("uarr") -> 8593; 204 | entity("rarr") -> 8594; 205 | entity("darr") -> 8595; 206 | entity("harr") -> 8596; 207 | entity("crarr") -> 8629; 208 | entity("lArr") -> 8656; 209 | entity("uArr") -> 8657; 210 | entity("rArr") -> 8658; 211 | entity("dArr") -> 8659; 212 | entity("hArr") -> 8660; 213 | entity("forall") -> 8704; 214 | entity("part") -> 8706; 215 | entity("exist") -> 8707; 216 | entity("empty") -> 8709; 217 | entity("nabla") -> 8711; 218 | entity("isin") -> 8712; 219 | entity("notin") -> 8713; 220 | entity("ni") -> 8715; 221 | entity("prod") -> 8719; 222 | entity("sum") -> 8721; 223 | entity("minus") -> 8722; 224 | entity("lowast") -> 8727; 225 | entity("radic") -> 8730; 226 | entity("prop") -> 8733; 227 | entity("infin") -> 8734; 228 | entity("ang") -> 8736; 229 | entity("and") -> 8743; 230 | entity("or") -> 8744; 231 | entity("cap") -> 8745; 232 | entity("cup") -> 8746; 233 | entity("int") -> 8747; 234 | entity("there4") -> 8756; 235 | entity("sim") -> 8764; 236 | entity("cong") -> 8773; 237 | entity("asymp") -> 8776; 238 | entity("ne") -> 8800; 239 | entity("equiv") -> 8801; 240 | entity("le") -> 8804; 241 | entity("ge") -> 8805; 242 | entity("sub") -> 8834; 243 | entity("sup") -> 8835; 244 | entity("nsub") -> 8836; 245 | entity("sube") -> 8838; 246 | entity("supe") -> 8839; 247 | entity("oplus") -> 8853; 248 | entity("otimes") -> 8855; 249 | entity("perp") -> 8869; 250 | entity("sdot") -> 8901; 251 | entity("lceil") -> 8968; 252 | entity("rceil") -> 8969; 253 | entity("lfloor") -> 8970; 254 | entity("rfloor") -> 8971; 255 | entity("lang") -> 9001; 256 | entity("rang") -> 9002; 257 | entity("loz") -> 9674; 258 | entity("spades") -> 9824; 259 | entity("clubs") -> 9827; 260 | entity("hearts") -> 9829; 261 | entity("diams") -> 9830; 262 | entity("quot") -> 34; 263 | entity("amp") -> 38; 264 | entity("lt") -> 60; 265 | entity("gt") -> 62; 266 | entity("OElig") -> 338; 267 | entity("oelig") -> 339; 268 | entity("Scaron") -> 352; 269 | entity("scaron") -> 353; 270 | entity("Yuml") -> 376; 271 | entity("circ") -> 710; 272 | entity("tilde") -> 732; 273 | entity("ensp") -> 8194; 274 | entity("emsp") -> 8195; 275 | entity("thinsp") -> 8201; 276 | entity("zwnj") -> 8204; 277 | entity("zwj") -> 8205; 278 | entity("lrm") -> 8206; 279 | entity("rlm") -> 8207; 280 | entity("ndash") -> 8211; 281 | entity("mdash") -> 8212; 282 | entity("lsquo") -> 8216; 283 | entity("rsquo") -> 8217; 284 | entity("sbquo") -> 8218; 285 | entity("ldquo") -> 8220; 286 | entity("rdquo") -> 8221; 287 | entity("bdquo") -> 8222; 288 | entity("dagger") -> 8224; 289 | entity("Dagger") -> 8225; 290 | entity("permil") -> 8240; 291 | entity("lsaquo") -> 8249; 292 | entity("rsaquo") -> 8250; 293 | entity("euro") -> 8364; 294 | entity(_) -> undefined. 295 | 296 | -------------------------------------------------------------------------------- /deps/mochiweb/src/mochiweb_cookies.erl: -------------------------------------------------------------------------------- 1 | %% @author Emad El-Haraty 2 | %% @copyright 2007 Mochi Media, Inc. 3 | 4 | %% @doc HTTP Cookie parsing and generating (RFC 2109, RFC 2965). 5 | 6 | -module(mochiweb_cookies). 7 | -export([parse_cookie/1, cookie/3, cookie/2, test/0]). 8 | 9 | -define(QUOTE, $\"). 10 | 11 | -define(IS_WHITESPACE(C), 12 | (C =:= $\s orelse C =:= $\t orelse C =:= $\r orelse C =:= $\n)). 13 | 14 | %% RFC 2616 separators (called tspecials in RFC 2068) 15 | -define(IS_SEPARATOR(C), 16 | (C < 32 orelse 17 | C =:= $\s orelse C =:= $\t orelse 18 | C =:= $( orelse C =:= $) orelse C =:= $< orelse C =:= $> orelse 19 | C =:= $@ orelse C =:= $, orelse C =:= $; orelse C =:= $: orelse 20 | C =:= $\\ orelse C =:= $\" orelse C =:= $/ orelse 21 | C =:= $[ orelse C =:= $] orelse C =:= $? orelse C =:= $= orelse 22 | C =:= ${ orelse C =:= $})). 23 | 24 | %% @type proplist() = [{Key::string(), Value::string()}]. 25 | %% @type header() = {Name::string(), Value::string()}. 26 | 27 | %% @spec cookie(Key::string(), Value::string()) -> header() 28 | %% @doc Short-hand for cookie(Key, Value, []). 29 | cookie(Key, Value) -> 30 | cookie(Key, Value, []). 31 | 32 | %% @spec cookie(Key::string(), Value::string(), Options::[Option]) -> header() 33 | %% where Option = {max_age, integer()} | {local_time, {date(), time()}} 34 | %% | {domain, string()} | {path, string()} 35 | %% | {secure, true | false} 36 | %% 37 | %% @doc Generate a Set-Cookie header field tuple. 38 | cookie(Key, Value, Options) -> 39 | Cookie = [any_to_list(Key), "=", quote(Value), "; Version=1"], 40 | %% Set-Cookie: 41 | %% Comment, Domain, Max-Age, Path, Secure, Version 42 | %% Set-Cookie2: 43 | %% Comment, CommentURL, Discard, Domain, Max-Age, Path, Port, Secure, 44 | %% Version 45 | ExpiresPart = 46 | case proplists:get_value(max_age, Options) of 47 | undefined -> 48 | ""; 49 | RawAge -> 50 | When = case proplists:get_value(local_time, Options) of 51 | undefined -> 52 | calendar:local_time(); 53 | LocalTime -> 54 | LocalTime 55 | end, 56 | Age = case RawAge < 0 of 57 | true -> 58 | 0; 59 | false -> 60 | RawAge 61 | end, 62 | ["; Expires=", age_to_cookie_date(Age, When), 63 | "; Max-Age=", quote(Age)] 64 | end, 65 | SecurePart = 66 | case proplists:get_value(secure, Options) of 67 | true -> 68 | "; Secure"; 69 | _ -> 70 | "" 71 | end, 72 | DomainPart = 73 | case proplists:get_value(domain, Options) of 74 | undefined -> 75 | ""; 76 | Domain -> 77 | ["; Domain=", quote(Domain)] 78 | end, 79 | PathPart = 80 | case proplists:get_value(path, Options) of 81 | undefined -> 82 | ""; 83 | Path -> 84 | ["; Path=", quote(Path)] 85 | end, 86 | CookieParts = [Cookie, ExpiresPart, SecurePart, DomainPart, PathPart], 87 | {"Set-Cookie", lists:flatten(CookieParts)}. 88 | 89 | 90 | %% Every major browser incorrectly handles quoted strings in a 91 | %% different and (worse) incompatible manner. Instead of wasting time 92 | %% writing redundant code for each browser, we restrict cookies to 93 | %% only contain characters that browsers handle compatibly. 94 | %% 95 | %% By replacing the definition of quote with this, we generate 96 | %% RFC-compliant cookies: 97 | %% 98 | %% quote(V) -> 99 | %% Fun = fun(?QUOTE, Acc) -> [$\\, ?QUOTE | Acc]; 100 | %% (Ch, Acc) -> [Ch | Acc] 101 | %% end, 102 | %% [?QUOTE | lists:foldr(Fun, [?QUOTE], V)]. 103 | 104 | %% Convert to a string and raise an error if quoting is required. 105 | quote(V0) -> 106 | V = any_to_list(V0), 107 | lists:all(fun(Ch) -> Ch =:= $/ orelse not ?IS_SEPARATOR(Ch) end, V) 108 | orelse erlang:error({cookie_quoting_required, V}), 109 | V. 110 | 111 | add_seconds(Secs, LocalTime) -> 112 | Greg = calendar:datetime_to_gregorian_seconds(LocalTime), 113 | calendar:gregorian_seconds_to_datetime(Greg + Secs). 114 | 115 | age_to_cookie_date(Age, LocalTime) -> 116 | httpd_util:rfc1123_date(add_seconds(Age, LocalTime)). 117 | 118 | %% @spec parse_cookie(string()) -> [{K::string(), V::string()}] 119 | %% @doc Parse the contents of a Cookie header field, ignoring cookie 120 | %% attributes, and return a simple property list. 121 | parse_cookie("") -> 122 | []; 123 | parse_cookie(Cookie) -> 124 | parse_cookie(Cookie, []). 125 | 126 | %% @spec test() -> ok 127 | %% @doc Run tests for mochiweb_cookies. 128 | test() -> 129 | parse_cookie_test(), 130 | cookie_test(), 131 | ok. 132 | 133 | %% Internal API 134 | 135 | parse_cookie([], Acc) -> 136 | lists:reverse(Acc); 137 | parse_cookie(String, Acc) -> 138 | {{Token, Value}, Rest} = read_pair(String), 139 | Acc1 = case Token of 140 | "" -> 141 | Acc; 142 | "$" ++ _ -> 143 | Acc; 144 | _ -> 145 | [{Token, Value} | Acc] 146 | end, 147 | parse_cookie(Rest, Acc1). 148 | 149 | read_pair(String) -> 150 | {Token, Rest} = read_token(skip_whitespace(String)), 151 | {Value, Rest1} = read_value(skip_whitespace(Rest)), 152 | {{Token, Value}, skip_past_separator(Rest1)}. 153 | 154 | read_value([$= | Value]) -> 155 | Value1 = skip_whitespace(Value), 156 | case Value1 of 157 | [?QUOTE | _] -> 158 | read_quoted(Value1); 159 | _ -> 160 | read_token(Value1) 161 | end; 162 | read_value(String) -> 163 | {"", String}. 164 | 165 | read_quoted([?QUOTE | String]) -> 166 | read_quoted(String, []). 167 | 168 | read_quoted([], Acc) -> 169 | {lists:reverse(Acc), []}; 170 | read_quoted([?QUOTE | Rest], Acc) -> 171 | {lists:reverse(Acc), Rest}; 172 | read_quoted([$\\, Any | Rest], Acc) -> 173 | read_quoted(Rest, [Any | Acc]); 174 | read_quoted([C | Rest], Acc) -> 175 | read_quoted(Rest, [C | Acc]). 176 | 177 | skip_whitespace(String) -> 178 | F = fun (C) -> ?IS_WHITESPACE(C) end, 179 | lists:dropwhile(F, String). 180 | 181 | read_token(String) -> 182 | F = fun (C) -> not ?IS_SEPARATOR(C) end, 183 | lists:splitwith(F, String). 184 | 185 | skip_past_separator([]) -> 186 | []; 187 | skip_past_separator([$; | Rest]) -> 188 | Rest; 189 | skip_past_separator([$, | Rest]) -> 190 | Rest; 191 | skip_past_separator([_ | Rest]) -> 192 | skip_past_separator(Rest). 193 | 194 | parse_cookie_test() -> 195 | %% RFC example 196 | C1 = "$Version=\"1\"; Customer=\"WILE_E_COYOTE\"; $Path=\"/acme\"; 197 | Part_Number=\"Rocket_Launcher_0001\"; $Path=\"/acme\"; 198 | Shipping=\"FedEx\"; $Path=\"/acme\"", 199 | [ 200 | {"Customer","WILE_E_COYOTE"}, 201 | {"Part_Number","Rocket_Launcher_0001"}, 202 | {"Shipping","FedEx"} 203 | ] = parse_cookie(C1), 204 | %% Potential edge cases 205 | [{"foo", "x"}] = parse_cookie("foo=\"\\x\""), 206 | [] = parse_cookie("="), 207 | [{"foo", ""}, {"bar", ""}] = parse_cookie(" foo ; bar "), 208 | [{"foo", ""}, {"bar", ""}] = parse_cookie("foo=;bar="), 209 | [{"foo", "\";"}, {"bar", ""}] = parse_cookie("foo = \"\\\";\";bar "), 210 | [{"foo", "\";bar"}] = parse_cookie("foo=\"\\\";bar"). 211 | 212 | any_to_list(V) when is_list(V) -> 213 | V; 214 | any_to_list(V) when is_atom(V) -> 215 | atom_to_list(V); 216 | any_to_list(V) when is_binary(V) -> 217 | binary_to_list(V); 218 | any_to_list(V) when is_integer(V) -> 219 | integer_to_list(V). 220 | 221 | 222 | cookie_test() -> 223 | C1 = {"Set-Cookie", 224 | "Customer=WILE_E_COYOTE; " 225 | "Version=1; " 226 | "Path=/acme"}, 227 | C1 = cookie("Customer", "WILE_E_COYOTE", [{path, "/acme"}]), 228 | C1 = cookie("Customer", "WILE_E_COYOTE", 229 | [{path, "/acme"}, {badoption, "negatory"}]), 230 | C1 = cookie('Customer', 'WILE_E_COYOTE', [{path, '/acme'}]), 231 | C1 = cookie(<<"Customer">>, <<"WILE_E_COYOTE">>, [{path, <<"/acme">>}]), 232 | 233 | {"Set-Cookie","=NoKey; Version=1"} = cookie("", "NoKey", []), 234 | 235 | LocalTime = calendar:universal_time_to_local_time({{2007, 5, 15}, {13, 45, 33}}), 236 | C2 = {"Set-Cookie", 237 | "Customer=WILE_E_COYOTE; " 238 | "Version=1; " 239 | "Expires=Tue, 15 May 2007 13:45:33 GMT; " 240 | "Max-Age=0"}, 241 | C2 = cookie("Customer", "WILE_E_COYOTE", 242 | [{max_age, -111}, {local_time, LocalTime}]), 243 | C3 = {"Set-Cookie", 244 | "Customer=WILE_E_COYOTE; " 245 | "Version=1; " 246 | "Expires=Wed, 16 May 2007 13:45:50 GMT; " 247 | "Max-Age=86417"}, 248 | C3 = cookie("Customer", "WILE_E_COYOTE", 249 | [{max_age, 86417}, {local_time, LocalTime}]), 250 | ok. 251 | -------------------------------------------------------------------------------- /deps/mochiweb/src/mochiweb_echo.erl: -------------------------------------------------------------------------------- 1 | %% @author Bob Ippolito 2 | %% @copyright 2007 Mochi Media, Inc. 3 | 4 | %% @doc Simple and stupid echo server to demo mochiweb_socket_server. 5 | 6 | -module(mochiweb_echo). 7 | -author('bob@mochimedia.com'). 8 | -export([start/0, stop/0, loop/1]). 9 | 10 | stop() -> 11 | mochiweb_socket_server:stop(?MODULE). 12 | 13 | start() -> 14 | mochiweb_socket_server:start([{name, ?MODULE}, 15 | {port, 6789}, 16 | {ip, "127.0.0.1"}, 17 | {max, 1}, 18 | {loop, {?MODULE, loop}}]). 19 | 20 | loop(Socket) -> 21 | case gen_tcp:recv(Socket, 0, 30000) of 22 | {ok, Data} -> 23 | case gen_tcp:send(Socket, Data) of 24 | ok -> 25 | loop(Socket); 26 | _ -> 27 | exit(normal) 28 | end; 29 | _Other -> 30 | exit(normal) 31 | end. 32 | -------------------------------------------------------------------------------- /deps/mochiweb/src/mochiweb_headers.erl: -------------------------------------------------------------------------------- 1 | %% @author Bob Ippolito 2 | %% @copyright 2007 Mochi Media, Inc. 3 | 4 | %% @doc Case preserving (but case insensitive) HTTP Header dictionary. 5 | 6 | -module(mochiweb_headers). 7 | -author('bob@mochimedia.com'). 8 | -export([empty/0, from_list/1, insert/3, enter/3, get_value/2, lookup/2]). 9 | -export([get_primary_value/2]). 10 | -export([default/3, enter_from_list/2, default_from_list/2]). 11 | -export([to_list/1, make/1]). 12 | -export([test/0]). 13 | 14 | %% @type headers(). 15 | %% @type key() = atom() | binary() | string(). 16 | %% @type value() = atom() | binary() | string() | integer(). 17 | 18 | %% @spec test() -> ok 19 | %% @doc Run tests for this module. 20 | test() -> 21 | H = ?MODULE:make([{hdr, foo}, {"Hdr", "bar"}, {'Hdr', 2}]), 22 | [{hdr, "foo, bar, 2"}] = ?MODULE:to_list(H), 23 | H1 = ?MODULE:insert(taco, grande, H), 24 | [{hdr, "foo, bar, 2"}, {taco, "grande"}] = ?MODULE:to_list(H1), 25 | H2 = ?MODULE:make([{"Set-Cookie", "foo"}]), 26 | [{"Set-Cookie", "foo"}] = ?MODULE:to_list(H2), 27 | H3 = ?MODULE:insert("Set-Cookie", "bar", H2), 28 | [{"Set-Cookie", "foo"}, {"Set-Cookie", "bar"}] = ?MODULE:to_list(H3), 29 | "foo, bar" = ?MODULE:get_value("set-cookie", H3), 30 | {value, {"Set-Cookie", "foo, bar"}} = ?MODULE:lookup("set-cookie", H3), 31 | undefined = ?MODULE:get_value("shibby", H3), 32 | none = ?MODULE:lookup("shibby", H3), 33 | H4 = ?MODULE:insert("content-type", 34 | "application/x-www-form-urlencoded; charset=utf8", 35 | H3), 36 | "application/x-www-form-urlencoded" = ?MODULE:get_primary_value( 37 | "content-type", H4), 38 | ok. 39 | 40 | %% @spec empty() -> headers() 41 | %% @doc Create an empty headers structure. 42 | empty() -> 43 | gb_trees:empty(). 44 | 45 | %% @spec make(headers() | [{key(), value()}]) -> headers() 46 | %% @doc Construct a headers() from the given list. 47 | make(L) when is_list(L) -> 48 | from_list(L); 49 | %% assume a tuple is already mochiweb_headers. 50 | make(T) when is_tuple(T) -> 51 | T. 52 | 53 | %% @spec from_list([{key(), value()}]) -> headers() 54 | %% @doc Construct a headers() from the given list. 55 | from_list(List) -> 56 | lists:foldl(fun ({K, V}, T) -> insert(K, V, T) end, empty(), List). 57 | 58 | %% @spec enter_from_list([{key(), value()}], headers()) -> headers() 59 | %% @doc Insert pairs into the headers, replace any values for existing keys. 60 | enter_from_list(List, T) -> 61 | lists:foldl(fun ({K, V}, T1) -> enter(K, V, T1) end, T, List). 62 | 63 | %% @spec default_from_list([{key(), value()}], headers()) -> headers() 64 | %% @doc Insert pairs into the headers for keys that do not already exist. 65 | default_from_list(List, T) -> 66 | lists:foldl(fun ({K, V}, T1) -> default(K, V, T1) end, T, List). 67 | 68 | %% @spec to_list(headers()) -> [{key(), string()}] 69 | %% @doc Return the contents of the headers. The keys will be the exact key 70 | %% that was first inserted (e.g. may be an atom or binary, case is 71 | %% preserved). 72 | to_list(T) -> 73 | F = fun ({K, {array, L}}, Acc) -> 74 | L1 = lists:reverse(L), 75 | lists:foldl(fun (V, Acc1) -> [{K, V} | Acc1] end, Acc, L1); 76 | (Pair, Acc) -> 77 | [Pair | Acc] 78 | end, 79 | lists:reverse(lists:foldl(F, [], gb_trees:values(T))). 80 | 81 | %% @spec get_value(key(), headers()) -> string() | undefined 82 | %% @doc Return the value of the given header using a case insensitive search. 83 | %% undefined will be returned for keys that are not present. 84 | get_value(K, T) -> 85 | case lookup(K, T) of 86 | {value, {_, V}} -> 87 | expand(V); 88 | none -> 89 | undefined 90 | end. 91 | 92 | %% @spec get_primary_value(key(), headers()) -> string() | undefined 93 | %% @doc Return the value of the given header up to the first semicolon using 94 | %% a case insensitive search. undefined will be returned for keys 95 | %% that are not present. 96 | get_primary_value(K, T) -> 97 | case get_value(K, T) of 98 | undefined -> 99 | undefined; 100 | V -> 101 | lists:takewhile(fun (C) -> C =/= $; end, V) 102 | end. 103 | 104 | %% @spec lookup(key(), headers()) -> {value, {key(), string()}} | none 105 | %% @doc Return the case preserved key and value for the given header using 106 | %% a case insensitive search. none will be returned for keys that are 107 | %% not present. 108 | lookup(K, T) -> 109 | case gb_trees:lookup(normalize(K), T) of 110 | {value, {K0, V}} -> 111 | {value, {K0, expand(V)}}; 112 | none -> 113 | none 114 | end. 115 | 116 | %% @spec default(key(), value(), headers()) -> headers() 117 | %% @doc Insert the pair into the headers if it does not already exist. 118 | default(K, V, T) -> 119 | K1 = normalize(K), 120 | V1 = any_to_list(V), 121 | try gb_trees:insert(K1, {K, V1}, T) 122 | catch 123 | error:{key_exists, _} -> 124 | T 125 | end. 126 | 127 | %% @spec enter(key(), value(), headers()) -> headers() 128 | %% @doc Insert the pair into the headers, replacing any pre-existing key. 129 | enter(K, V, T) -> 130 | K1 = normalize(K), 131 | V1 = any_to_list(V), 132 | gb_trees:enter(K1, {K, V1}, T). 133 | 134 | %% @spec insert(key(), value(), headers()) -> headers() 135 | %% @doc Insert the pair into the headers, merging with any pre-existing key. 136 | %% A merge is done with Value = V0 ++ ", " ++ V1. 137 | insert(K, V, T) -> 138 | K1 = normalize(K), 139 | V1 = any_to_list(V), 140 | try gb_trees:insert(K1, {K, V1}, T) 141 | catch 142 | error:{key_exists, _} -> 143 | {K0, V0} = gb_trees:get(K1, T), 144 | V2 = merge(K1, V1, V0), 145 | gb_trees:update(K1, {K0, V2}, T) 146 | end. 147 | 148 | %% Internal API 149 | 150 | expand({array, L}) -> 151 | mochiweb_util:join(lists:reverse(L), ", "); 152 | expand(V) -> 153 | V. 154 | 155 | merge("set-cookie", V1, {array, L}) -> 156 | {array, [V1 | L]}; 157 | merge("set-cookie", V1, V0) -> 158 | {array, [V1, V0]}; 159 | merge(_, V1, V0) -> 160 | V0 ++ ", " ++ V1. 161 | 162 | normalize(K) when is_list(K) -> 163 | string:to_lower(K); 164 | normalize(K) when is_atom(K) -> 165 | normalize(atom_to_list(K)); 166 | normalize(K) when is_binary(K) -> 167 | normalize(binary_to_list(K)). 168 | 169 | any_to_list(V) when is_list(V) -> 170 | V; 171 | any_to_list(V) when is_atom(V) -> 172 | atom_to_list(V); 173 | any_to_list(V) when is_binary(V) -> 174 | binary_to_list(V); 175 | any_to_list(V) when is_integer(V) -> 176 | integer_to_list(V). 177 | 178 | 179 | -------------------------------------------------------------------------------- /deps/mochiweb/src/mochiweb_http.erl: -------------------------------------------------------------------------------- 1 | %% @author Bob Ippolito 2 | %% @copyright 2007 Mochi Media, Inc. 3 | 4 | %% @doc HTTP server. 5 | 6 | -module(mochiweb_http). 7 | -author('bob@mochimedia.com'). 8 | -export([start/0, start/1, stop/0, stop/1]). 9 | -export([loop/2, default_body/1]). 10 | 11 | -define(IDLE_TIMEOUT, 30000). 12 | 13 | -define(DEFAULTS, [{name, ?MODULE}, 14 | {port, 8888}]). 15 | 16 | set_default({Prop, Value}, PropList) -> 17 | case proplists:is_defined(Prop, PropList) of 18 | true -> 19 | PropList; 20 | false -> 21 | [{Prop, Value} | PropList] 22 | end. 23 | 24 | set_defaults(Defaults, PropList) -> 25 | lists:foldl(fun set_default/2, PropList, Defaults). 26 | 27 | parse_options(Options) -> 28 | {loop, HttpLoop} = proplists:lookup(loop, Options), 29 | Loop = fun (S) -> 30 | ?MODULE:loop(S, HttpLoop) 31 | end, 32 | Options1 = [{loop, Loop} | proplists:delete(loop, Options)], 33 | set_defaults(?DEFAULTS, Options1). 34 | 35 | stop() -> 36 | mochiweb_socket_server:stop(?MODULE). 37 | 38 | stop(Name) -> 39 | mochiweb_socket_server:stop(Name). 40 | 41 | start() -> 42 | start([{ip, "127.0.0.1"}, 43 | {loop, {?MODULE, default_body}}]). 44 | 45 | start(Options) -> 46 | mochiweb_socket_server:start(parse_options(Options)). 47 | 48 | frm(Body) -> 49 | ["" 50 | "
" 51 | "" 52 | "" 53 | "
" 54 | "
" 55 | "
" 57 | "" 58 | "" 59 | "" 60 | "
" 61 | "
", Body, "
" 62 | ""]. 63 | 64 | default_body(Req, M, "/chunked") when M =:= 'GET'; M =:= 'HEAD' -> 65 | Res = Req:ok({"text/plain", [], chunked}), 66 | Res:write_chunk("First chunk\r\n"), 67 | timer:sleep(5000), 68 | Res:write_chunk("Last chunk\r\n"), 69 | Res:write_chunk(""); 70 | default_body(Req, M, _Path) when M =:= 'GET'; M =:= 'HEAD' -> 71 | Body = io_lib:format("~p~n", [[{parse_qs, Req:parse_qs()}, 72 | {parse_cookie, Req:parse_cookie()}, 73 | Req:dump()]]), 74 | Req:ok({"text/html", 75 | [mochiweb_cookies:cookie("mochiweb_http", "test_cookie")], 76 | frm(Body)}); 77 | default_body(Req, 'POST', "/multipart") -> 78 | Body = io_lib:format("~p~n", [[{parse_qs, Req:parse_qs()}, 79 | {parse_cookie, Req:parse_cookie()}, 80 | {body, Req:recv_body()}, 81 | Req:dump()]]), 82 | Req:ok({"text/html", [], frm(Body)}); 83 | default_body(Req, 'POST', _Path) -> 84 | Body = io_lib:format("~p~n", [[{parse_qs, Req:parse_qs()}, 85 | {parse_cookie, Req:parse_cookie()}, 86 | {parse_post, Req:parse_post()}, 87 | Req:dump()]]), 88 | Req:ok({"text/html", [], frm(Body)}); 89 | default_body(Req, _Method, _Path) -> 90 | Req:respond({501, [], []}). 91 | 92 | default_body(Req) -> 93 | default_body(Req, Req:get(method), Req:get(path)). 94 | 95 | loop(Socket, Body) -> 96 | inet:setopts(Socket, [{packet, http}]), 97 | request(Socket, Body). 98 | 99 | request(Socket, Body) -> 100 | case gen_tcp:recv(Socket, 0, ?IDLE_TIMEOUT) of 101 | {ok, {http_request, Method, Path, Version}} -> 102 | headers(Socket, {Method, Path, Version}, [], Body); 103 | {error, {http_error, "\r\n"}} -> 104 | request(Socket, Body); 105 | {error, {http_error, "\n"}} -> 106 | request(Socket, Body); 107 | _Other -> 108 | gen_tcp:close(Socket), 109 | exit(normal) 110 | end. 111 | 112 | headers(Socket, Request, Headers, Body) -> 113 | case gen_tcp:recv(Socket, 0, ?IDLE_TIMEOUT) of 114 | {ok, http_eoh} -> 115 | inet:setopts(Socket, [{packet, raw}]), 116 | Req = mochiweb:new_request({Socket, Request, 117 | lists:reverse(Headers)}), 118 | Body(Req), 119 | case Req:should_close() of 120 | true -> 121 | gen_tcp:close(Socket), 122 | exit(normal); 123 | false -> 124 | Req:cleanup(), 125 | ?MODULE:loop(Socket, Body) 126 | end; 127 | {ok, {http_header, _, Name, _, Value}} -> 128 | headers(Socket, Request, [{Name, Value} | Headers], Body); 129 | _Other -> 130 | gen_tcp:close(Socket), 131 | exit(normal) 132 | end. 133 | -------------------------------------------------------------------------------- /deps/mochiweb/src/mochiweb_response.erl: -------------------------------------------------------------------------------- 1 | %% @author Bob Ippolito 2 | %% @copyright 2007 Mochi Media, Inc. 3 | 4 | %% @doc Response abstraction. 5 | 6 | -module(mochiweb_response, [Request, Code, Headers]). 7 | -author('bob@mochimedia.com'). 8 | 9 | -define(QUIP, "Any of you quaids got a smint?"). 10 | 11 | -export([get_header_value/1, get/1, dump/0]). 12 | -export([send/1, write_chunk/1]). 13 | 14 | %% @spec get_header_value(string() | atom() | binary()) -> string() | undefined 15 | %% @doc Get the value of the given response header. 16 | get_header_value(K) -> 17 | mochiweb_headers:get_value(K, Headers). 18 | 19 | %% @spec get(request | code | headers) -> term() 20 | %% @doc Return the internal representation of the given field. 21 | get(request) -> 22 | Request; 23 | get(code) -> 24 | Code; 25 | get(headers) -> 26 | Headers. 27 | 28 | %% @spec dump() -> {mochiweb_request, [{atom(), term()}]} 29 | %% @doc Dump the internal representation to a "human readable" set of terms 30 | %% for debugging/inspection purposes. 31 | dump() -> 32 | [{request, Request:dump()}, 33 | {code, Code}, 34 | {headers, mochiweb_headers:to_list(Headers)}]. 35 | 36 | %% @spec send(iodata()) -> ok 37 | %% @doc Send data over the socket if the method is not HEAD. 38 | send(Data) -> 39 | case Request:get(method) of 40 | 'HEAD' -> 41 | ok; 42 | _ -> 43 | Request:send(Data) 44 | end. 45 | 46 | %% @spec write_chunk(iodata()) -> ok 47 | %% @doc Write a chunk of a HTTP chunked response. If Data is zero length, 48 | %% then the chunked response will be finished. 49 | write_chunk(Data) -> 50 | case Request:get(version) of 51 | Version when Version >= {1, 1} -> 52 | Length = iolist_size(Data), 53 | send([io_lib:format("~.16b\r\n", [Length]), Data, <<"\r\n">>]); 54 | _ -> 55 | send(Data) 56 | end. 57 | -------------------------------------------------------------------------------- /deps/mochiweb/src/mochiweb_skel.erl: -------------------------------------------------------------------------------- 1 | -module(mochiweb_skel). 2 | -export([skelcopy/2]). 3 | 4 | -include_lib("kernel/include/file.hrl"). 5 | 6 | %% External API 7 | 8 | skelcopy(DestDir, Name) -> 9 | ok = ensuredir(DestDir), 10 | LDst = case length(filename:dirname(DestDir)) of 11 | 1 -> %% handle case when dirname returns "/" 12 | 0; 13 | N -> 14 | N + 1 15 | end, 16 | skelcopy(src(), DestDir, Name, LDst), 17 | ok = file:make_symlink( 18 | filename:join(filename:dirname(code:which(?MODULE)), ".."), 19 | filename:join([DestDir, Name, "deps", "mochiweb-src"])). 20 | 21 | 22 | %% Internal API 23 | 24 | src() -> 25 | Dir = filename:dirname(code:which(?MODULE)), 26 | filename:join(Dir, "../priv/skel"). 27 | 28 | skel() -> 29 | "skel". 30 | 31 | skelcopy(Src, DestDir, Name, LDst) -> 32 | {ok, Dest, _} = regexp:gsub(filename:basename(Src), skel(), Name), 33 | case file:read_file_info(Src) of 34 | {ok, #file_info{type=directory, mode=Mode}} -> 35 | Dir = DestDir ++ "/" ++ Dest, 36 | EDst = lists:nthtail(LDst, Dir), 37 | ok = ensuredir(Dir), 38 | ok = file:write_file_info(Dir, #file_info{mode=Mode}), 39 | {ok, Files} = file:list_dir(Src), 40 | io:format("~s/~n", [EDst]), 41 | lists:foreach(fun ("." ++ _) -> ok; 42 | (F) -> 43 | skelcopy(filename:join(Src, F), 44 | Dir, 45 | Name, 46 | LDst) 47 | end, 48 | Files), 49 | ok; 50 | {ok, #file_info{type=regular, mode=Mode}} -> 51 | OutFile = filename:join(DestDir, Dest), 52 | {ok, B} = file:read_file(Src), 53 | {ok, S, _} = regexp:gsub(binary_to_list(B), skel(), Name), 54 | ok = file:write_file(OutFile, list_to_binary(S)), 55 | ok = file:write_file_info(OutFile, #file_info{mode=Mode}), 56 | io:format(" ~s~n", [filename:basename(Src)]), 57 | ok; 58 | {ok, _} -> 59 | io:format("ignored source file: ~p~n", [Src]), 60 | ok 61 | end. 62 | 63 | ensuredir(Dir) -> 64 | case file:make_dir(Dir) of 65 | ok -> 66 | ok; 67 | {error, eexist} -> 68 | ok; 69 | E -> 70 | E 71 | end. 72 | -------------------------------------------------------------------------------- /deps/mochiweb/src/mochiweb_socket_server.erl: -------------------------------------------------------------------------------- 1 | %% @author Bob Ippolito 2 | %% @copyright 2007 Mochi Media, Inc. 3 | 4 | %% @doc MochiWeb socket server. 5 | 6 | -module(mochiweb_socket_server). 7 | -author('bob@mochimedia.com'). 8 | -behaviour(gen_server). 9 | 10 | -export([start/1, stop/1]). 11 | -export([init/1, handle_call/3, handle_cast/2, terminate/2, code_change/3, 12 | handle_info/2]). 13 | -export([get/2]). 14 | 15 | -export([acceptor_loop/1]). 16 | 17 | -record(mochiweb_socket_server, 18 | {port, 19 | loop, 20 | name=undefined, 21 | max=2048, 22 | ip=any, 23 | listen=null, 24 | acceptor=null, 25 | backlog=30}). 26 | 27 | start(State=#mochiweb_socket_server{}) -> 28 | start_server(State); 29 | start(Options) -> 30 | start(parse_options(Options)). 31 | 32 | get(Name, Property) -> 33 | gen_server:call(Name, {get, Property}). 34 | 35 | stop(Name) when is_atom(Name) -> 36 | gen_server:cast(Name, stop); 37 | stop(Pid) when is_pid(Pid) -> 38 | gen_server:cast(Pid, stop); 39 | stop({local, Name}) -> 40 | stop(Name); 41 | stop({global, Name}) -> 42 | stop(Name); 43 | stop(Options) -> 44 | State = parse_options(Options), 45 | stop(State#mochiweb_socket_server.name). 46 | 47 | %% Internal API 48 | 49 | parse_options(Options) -> 50 | parse_options(Options, #mochiweb_socket_server{}). 51 | 52 | parse_options([], State) -> 53 | State; 54 | parse_options([{name, L} | Rest], State) when is_list(L) -> 55 | Name = {local, list_to_atom(L)}, 56 | parse_options(Rest, State#mochiweb_socket_server{name=Name}); 57 | parse_options([{name, A} | Rest], State) when is_atom(A) -> 58 | Name = {local, A}, 59 | parse_options(Rest, State#mochiweb_socket_server{name=Name}); 60 | parse_options([{name, Name} | Rest], State) -> 61 | parse_options(Rest, State#mochiweb_socket_server{name=Name}); 62 | parse_options([{port, L} | Rest], State) when is_list(L) -> 63 | Port = list_to_integer(L), 64 | parse_options(Rest, State#mochiweb_socket_server{port=Port}); 65 | parse_options([{port, Port} | Rest], State) -> 66 | parse_options(Rest, State#mochiweb_socket_server{port=Port}); 67 | parse_options([{ip, Ip} | Rest], State) -> 68 | ParsedIp = case Ip of 69 | any -> 70 | any; 71 | Ip when is_tuple(Ip) -> 72 | Ip; 73 | Ip when is_list(Ip) -> 74 | {ok, IpTuple} = inet_parse:address(Ip), 75 | IpTuple 76 | end, 77 | parse_options(Rest, State#mochiweb_socket_server{ip=ParsedIp}); 78 | parse_options([{loop, Loop} | Rest], State) -> 79 | parse_options(Rest, State#mochiweb_socket_server{loop=Loop}); 80 | parse_options([{backlog, Backlog} | Rest], State) -> 81 | parse_options(Rest, State#mochiweb_socket_server{backlog=Backlog}); 82 | parse_options([{max, Max} | Rest], State) -> 83 | MaxInt = case Max of 84 | Max when is_list(Max) -> 85 | list_to_integer(Max); 86 | Max when is_integer(Max) -> 87 | Max 88 | end, 89 | parse_options(Rest, State#mochiweb_socket_server{max=MaxInt}). 90 | 91 | start_server(State=#mochiweb_socket_server{name=Name}) -> 92 | case Name of 93 | undefined -> 94 | gen_server:start_link(?MODULE, State, []); 95 | _ -> 96 | gen_server:start_link(Name, ?MODULE, State, []) 97 | end. 98 | 99 | ipv6_supported() -> 100 | case (catch inet:getaddr("localhost", inet6)) of 101 | {ok, _Addr} -> 102 | true; 103 | {error, _} -> 104 | false 105 | end. 106 | 107 | init(State=#mochiweb_socket_server{ip=Ip, port=Port, backlog=Backlog}) -> 108 | process_flag(trap_exit, true), 109 | BaseOpts = [binary, 110 | {reuseaddr, true}, 111 | {packet, 0}, 112 | {backlog, Backlog}, 113 | {recbuf, 8192}, 114 | {active, false}, 115 | {nodelay, true}], 116 | Opts = case Ip of 117 | any -> 118 | case ipv6_supported() of % IPv4, and IPv6 if supported 119 | true -> [inet, inet6 | BaseOpts]; 120 | _ -> BaseOpts 121 | end; 122 | {_, _, _, _} -> % IPv4 123 | [inet, {ip, Ip} | BaseOpts]; 124 | {_, _, _, _, _, _, _, _} -> % IPv6 125 | [inet6, {ip, Ip} | BaseOpts] 126 | end, 127 | case gen_tcp_listen(Port, Opts, State) of 128 | {stop, eacces} -> 129 | case Port < 1024 of 130 | true -> 131 | case fdsrv:start() of 132 | {ok, _} -> 133 | case fdsrv:bind_socket(tcp, Port) of 134 | {ok, Fd} -> 135 | gen_tcp_listen(Port, [{fd, Fd} | Opts], State); 136 | _ -> 137 | {stop, fdsrv_bind_failed} 138 | end; 139 | _ -> 140 | {stop, fdsrv_start_failed} 141 | end; 142 | false -> 143 | {stop, eacces} 144 | end; 145 | Other -> 146 | Other 147 | end. 148 | 149 | gen_tcp_listen(Port, Opts, State) -> 150 | case gen_tcp:listen(Port, Opts) of 151 | {ok, Listen} -> 152 | {ok, ListenPort} = inet:port(Listen), 153 | {ok, new_acceptor(State#mochiweb_socket_server{listen=Listen, 154 | port=ListenPort})}; 155 | {error, Reason} -> 156 | {stop, Reason} 157 | end. 158 | 159 | new_acceptor(State=#mochiweb_socket_server{max=0}) -> 160 | io:format("Not accepting new connections~n"), 161 | State#mochiweb_socket_server{acceptor=null}; 162 | new_acceptor(State=#mochiweb_socket_server{listen=Listen,loop=Loop}) -> 163 | Pid = proc_lib:spawn_link(?MODULE, acceptor_loop, 164 | [{self(), Listen, Loop}]), 165 | State#mochiweb_socket_server{acceptor=Pid}. 166 | 167 | call_loop({M, F}, Socket) -> 168 | M:F(Socket); 169 | call_loop(Loop, Socket) -> 170 | Loop(Socket). 171 | 172 | acceptor_loop({Server, Listen, Loop}) -> 173 | case catch gen_tcp:accept(Listen) of 174 | {ok, Socket} -> 175 | gen_server:cast(Server, {accepted, self()}), 176 | call_loop(Loop, Socket); 177 | {error, closed} -> 178 | exit({error, closed}); 179 | Other -> 180 | error_logger:error_report( 181 | [{application, mochiweb}, 182 | "Accept failed error", 183 | lists:flatten(io_lib:format("~p", [Other]))]), 184 | exit({error, accept_failed}) 185 | end. 186 | 187 | 188 | do_get(port, #mochiweb_socket_server{port=Port}) -> 189 | Port. 190 | 191 | handle_call({get, Property}, _From, State) -> 192 | Res = do_get(Property, State), 193 | {reply, Res, State}; 194 | handle_call(_Message, _From, State) -> 195 | Res = error, 196 | {reply, Res, State}. 197 | 198 | handle_cast({accepted, Pid}, 199 | State=#mochiweb_socket_server{acceptor=Pid, max=Max}) -> 200 | % io:format("accepted ~p~n", [Pid]), 201 | State1 = State#mochiweb_socket_server{max=Max - 1}, 202 | {noreply, new_acceptor(State1)}; 203 | handle_cast(stop, State) -> 204 | {stop, normal, State}. 205 | 206 | terminate(_Reason, #mochiweb_socket_server{listen=Listen, port=Port}) -> 207 | gen_tcp:close(Listen), 208 | case Port < 1024 of 209 | true -> 210 | catch fdsrv:stop(), 211 | ok; 212 | false -> 213 | ok 214 | end. 215 | 216 | code_change(_OldVsn, State, _Extra) -> 217 | State. 218 | 219 | handle_info({'EXIT', Pid, normal}, 220 | State=#mochiweb_socket_server{acceptor=Pid}) -> 221 | % io:format("normal acceptor down~n"), 222 | {noreply, new_acceptor(State)}; 223 | handle_info({'EXIT', Pid, Reason}, 224 | State=#mochiweb_socket_server{acceptor=Pid}) -> 225 | error_logger:error_report({?MODULE, ?LINE, 226 | {acceptor_error, Reason}}), 227 | timer:sleep(100), 228 | {noreply, new_acceptor(State)}; 229 | handle_info({'EXIT', _LoopPid, Reason}, 230 | State=#mochiweb_socket_server{acceptor=Pid, max=Max}) -> 231 | case Reason of 232 | normal -> 233 | ok; 234 | _ -> 235 | error_logger:error_report({?MODULE, ?LINE, 236 | {child_error, Reason}}) 237 | end, 238 | State1 = State#mochiweb_socket_server{max=Max + 1}, 239 | State2 = case Pid of 240 | null -> 241 | new_acceptor(State1); 242 | _ -> 243 | State1 244 | end, 245 | {noreply, State2}; 246 | handle_info(Info, State) -> 247 | error_logger:info_report([{'INFO', Info}, {'State', State}]), 248 | {noreply, State}. 249 | -------------------------------------------------------------------------------- /deps/mochiweb/src/mochiweb_sup.erl: -------------------------------------------------------------------------------- 1 | %% @author Bob Ippolito 2 | %% @copyright 2007 Mochi Media, Inc. 3 | 4 | %% @doc Supervisor for the mochiweb application. 5 | 6 | -module(mochiweb_sup). 7 | -author('bob@mochimedia.com'). 8 | 9 | -behaviour(supervisor). 10 | 11 | %% External exports 12 | -export([start_link/0, upgrade/0]). 13 | 14 | %% supervisor callbacks 15 | -export([init/1]). 16 | 17 | %% @spec start_link() -> ServerRet 18 | %% @doc API for starting the supervisor. 19 | start_link() -> 20 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 21 | 22 | %% @spec upgrade() -> ok 23 | %% @doc Add processes if necessary. 24 | upgrade() -> 25 | {ok, {_, Specs}} = init([]), 26 | [supervisor:start_child(?MODULE, Spec) || Spec <- Specs], 27 | ok. 28 | 29 | %% @spec init([]) -> SupervisorTree 30 | %% @doc supervisor callback, ensures yaws is in embedded mode and then 31 | %% returns the supervisor tree. 32 | init([]) -> 33 | Processes = [], 34 | {ok, {{one_for_one, 10, 10}, Processes}}. 35 | -------------------------------------------------------------------------------- /deps/mochiweb/src/reloader.erl: -------------------------------------------------------------------------------- 1 | %% @copyright 2007 Mochi Media, Inc. 2 | %% @author Matthew Dempsky 3 | %% 4 | %% @doc Erlang module for automatically reloading modified modules 5 | %% during development. 6 | 7 | -module(reloader). 8 | -author("Matthew Dempsky "). 9 | 10 | -include_lib("kernel/include/file.hrl"). 11 | 12 | -behaviour(gen_server). 13 | -export([start/0, start_link/0]). 14 | -export([stop/0]). 15 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). 16 | 17 | -record(state, {last, tref}). 18 | 19 | %% External API 20 | 21 | %% @spec start() -> ServerRet 22 | %% @doc Start the reloader. 23 | start() -> 24 | gen_server:start({local, ?MODULE}, ?MODULE, [], []). 25 | 26 | %% @spec start_link() -> ServerRet 27 | %% @doc Start the reloader. 28 | start_link() -> 29 | gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). 30 | 31 | %% @spec stop() -> ok 32 | %% @doc Stop the reloader. 33 | stop() -> 34 | gen_server:call(?MODULE, stop). 35 | 36 | %% gen_server callbacks 37 | 38 | %% @spec init([]) -> {ok, State} 39 | %% @doc gen_server init, opens the server in an initial state. 40 | init([]) -> 41 | {ok, TRef} = timer:send_interval(timer:seconds(1), doit), 42 | {ok, #state{last = stamp(), tref = TRef}}. 43 | 44 | %% @spec handle_call(Args, From, State) -> tuple() 45 | %% @doc gen_server callback. 46 | handle_call(stop, _From, State) -> 47 | {stop, shutdown, stopped, State}; 48 | handle_call(_Req, _From, State) -> 49 | {reply, {error, badrequest}, State}. 50 | 51 | %% @spec handle_cast(Cast, State) -> tuple() 52 | %% @doc gen_server callback. 53 | handle_cast(_Req, State) -> 54 | {noreply, State}. 55 | 56 | %% @spec handle_info(Info, State) -> tuple() 57 | %% @doc gen_server callback. 58 | handle_info(doit, State) -> 59 | Now = stamp(), 60 | doit(State#state.last, Now), 61 | {noreply, State#state{last = Now}}; 62 | handle_info(_Info, State) -> 63 | {noreply, State}. 64 | 65 | %% @spec terminate(Reason, State) -> ok 66 | %% @doc gen_server termination callback. 67 | terminate(_Reason, State) -> 68 | {ok, cancel} = timer:cancel(State#state.tref), 69 | ok. 70 | 71 | 72 | %% @spec code_change(_OldVsn, State, _Extra) -> State 73 | %% @doc gen_server code_change callback (trivial). 74 | code_change(_Vsn, State, _Extra) -> 75 | {ok, State}. 76 | 77 | %% Internal API 78 | 79 | doit(From, To) -> 80 | [case file:read_file_info(Filename) of 81 | {ok, FileInfo} when FileInfo#file_info.mtime >= From, 82 | FileInfo#file_info.mtime < To -> 83 | reload(Module); 84 | {ok, _} -> 85 | unmodified; 86 | {error, enoent} -> 87 | %% The Erlang compiler deletes existing .beam files if 88 | %% recompiling fails. Maybe it's worth spitting out a 89 | %% warning here, but I'd want to limit it to just once. 90 | gone; 91 | {error, Reason} -> 92 | io:format("Error reading ~s's file info: ~p~n", 93 | [Filename, Reason]), 94 | error 95 | end || {Module, Filename} <- code:all_loaded(), is_list(Filename)]. 96 | 97 | reload(Module) -> 98 | io:format("Reloading ~p ...", [Module]), 99 | code:purge(Module), 100 | case code:load_file(Module) of 101 | {module, Module} -> 102 | io:format(" ok.~n"), 103 | case erlang:function_exported(Module, test, 0) of 104 | true -> 105 | io:format(" - Calling ~p:test() ...", [Module]), 106 | case catch Module:test() of 107 | ok -> 108 | io:format(" ok.~n"), 109 | reload; 110 | Reason -> 111 | io:format(" fail: ~p.~n", [Reason]), 112 | reload_but_test_failed 113 | end; 114 | false -> 115 | reload 116 | end; 117 | {error, Reason} -> 118 | io:format(" fail: ~p.~n", [Reason]), 119 | error 120 | end. 121 | 122 | 123 | stamp() -> 124 | erlang:localtime(). 125 | -------------------------------------------------------------------------------- /doc/beepbeep.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebryson/beepbeep/62db46d268c6cb6ad86345562b3c77f8ff070b27/doc/beepbeep.html -------------------------------------------------------------------------------- /doc/beepbeep_args.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebryson/beepbeep/62db46d268c6cb6ad86345562b3c77f8ff070b27/doc/beepbeep_args.html -------------------------------------------------------------------------------- /doc/beepbeep_router.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebryson/beepbeep/62db46d268c6cb6ad86345562b3c77f8ff070b27/doc/beepbeep_router.html -------------------------------------------------------------------------------- /doc/edoc-info: -------------------------------------------------------------------------------- 1 | {application,'BeepBeep'}. 2 | {packages,[]}. 3 | {modules,[beepbeep,beepbeep_args,beepbeep_router]}. 4 | -------------------------------------------------------------------------------- /doc/erlang.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebryson/beepbeep/62db46d268c6cb6ad86345562b3c77f8ff070b27/doc/erlang.png -------------------------------------------------------------------------------- /doc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The BeepBeep application 5 | 6 | 7 | 8 | 9 | 10 | 11 | <h2>This page uses frames</h2> 12 | <p>Your browser does not accept frames. 13 | <br>You should go to the <a href="overview-summary.html">non-frame version</a> instead. 14 | </p> 15 | 16 | 17 | -------------------------------------------------------------------------------- /doc/modules-frame.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The BeepBeep application 5 | 6 | 7 | 8 |

Modules

9 | 10 | 11 | 12 |
beepbeep
beepbeep_args
beepbeep_router
13 | 14 | -------------------------------------------------------------------------------- /doc/overview-summary.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebryson/beepbeep/62db46d268c6cb6ad86345562b3c77f8ff070b27/doc/overview-summary.html -------------------------------------------------------------------------------- /doc/overview.edoc: -------------------------------------------------------------------------------- 1 | @title BeepBeep 2 | @author Dave Bryson 3 | @copyright Dave Bryson 2008-2009 4 | 5 | @doc 6 | == Contents == 7 | {@section Introduction}
8 | {@section Creating a new application}
9 | {@section Controllers}
10 | {@section Return values}
11 | {@section Parameters and Session}
12 | 13 | == Introduction == 14 | BeepBeep is a simple web application designed to provide a familiar environment for developers 15 | coming from Rails. Like Rails, if you follow a few conventions, BeepBeep will automatically 16 | map requests to your controllers and views leaving you to focus on the core of your application. 17 | 18 | Features: 19 |
    20 |
  • Server side session store
  • 21 |
  • Automatic mapping of requests to controllers and views
  • 22 |
  • Automatic binding of extra path arguments to controller variables
  • 23 |
  • Before filter on your controllers
  • 24 |
  • Django templates provided by erlydtl
  • 25 |
26 | 27 | == Creating a new application == 28 | 29 | BeepBeep includes a simple script you can use to generate an application. The script will create the base code, 30 | directory structure, and a default controller you can use as a starting point. 31 | 32 | To create a new application, make sure you are in the root directory of BeepBeep and run: 33 | 34 | ``` 35 | ./script/new_beepbeep.erl [name of app] [destination directory] 36 | ''' 37 | 38 | For example to create the app 'blog' in the directory '/home/dave/' you'd enter: 39 | 40 | ``` 41 | ./script/new_beepbeep.erl blog /home/dave 42 | ''' 43 | 44 | If you look in the '/home/dave/blog' directory you'll see the application structure along with 45 | a default controller called 'home_controller.erl' in the src directory. Any controllers you create should end with 46 | '_controller.erl'. BeepBeep uses to this convention to map requests to your code. 47 | 48 | Templates are located in the 'views' directory and any static content such as images, stylesheets, etc... are located 49 | in the 'www' directory. 50 | 51 | Now, to run the default application run first run 'make' and then start the server with: 52 | 53 | ``` 54 | ./start_server.sh 55 | ''' 56 | 57 | This will start the webserver and application on port 8000. You can change the port in the '*_sup.erl' file if 58 | desired. 59 | 60 | Once the server starts go to: 61 | 62 | ``` 63 | http://localhost:8000/ 64 | ''' 65 | 66 | to see the default application. 67 | 68 | 69 | == Controllers == 70 | 71 | Most of the code you write will be in controllers. Controllers are regular Erlang modules. The exception is they 72 | need to follow a few rules: 73 | 74 |
    75 |
  1. The name of the module must end in '_controller.erl'
  2. 76 |
  3. The module must export and implement two functions: 'handler_request/2' and 'before_filter/0'
  4. 77 |
78 | 79 | The functions 'handle_request/2' are the 'actions' in a controller. For example, a request to: 80 | 81 | ``` 82 | /login/new 83 | 84 | /[controller name]/[function] 85 | ''' 86 | 87 | would map to the function: 88 | 89 | ``` 90 | handle_request("new",[]) -> 91 | ... 92 | ''' 93 | 94 | The first parameter 'new' is a String used for matching and the second parameter is an array that will automatically 95 | map any extra components in the path to an Erlang variable. For example: 96 | 97 | home_controller.erl: 98 | 99 | ``` 100 | handle_request("show",[Year]) -> 101 | error_logger:info_msg("The year sent is: ~s",[Year]), 102 | ... 103 | 104 | ''' 105 | 106 | would map to the request: 107 | 108 | ``` 109 | /home/show/2009 110 | ''' 111 | 112 | and bind '2009' to the 'Year' variable. 113 | 114 | 115 | The 'before_filter' will run any code you specify 'before' the requested 'handle_request' is run. This is and 116 | ideal place for authentication. The before returns two types of results: 117 | 118 |
    119 |
  1. the atom 'ok'. Which means the filter passed or
  2. 120 |
  3. a BeepBeep response tuple
  4. 121 |
122 | 123 | See the Blog example included with the source code in 'example/blog/src/home_controller.erl'. 124 | 125 | 126 | == Return values == 127 | 128 | Here are the possible return values from 'handle_request' and 'before_filter': 129 | 130 | ``` 131 | {render,View,Data} 132 | ''' 133 | View is a string for the template to use: "/home/index.html" 134 | Data is an array of tuples to bind in your template: [{name,"dave"}]. This make the key name, and the value "dave" 135 | available in you Django template. 136 | 137 | ``` 138 | {render,View,Data,Options} 139 | ''' 140 | Same as above, except Options is an array of tuples to include in the header. For example: 141 | 142 | ``` 143 | [{status,201}] 144 | ''' 145 | 146 | ``` 147 | {text,Content} 148 | ''' 149 | 150 | Returns plain text where 'Content' is a String or Binary 151 | 152 | ``` 153 | {redirect, Url} 154 | ''' 155 | 156 | Redirects (302) to the given Url: {redirect,"/"} 157 | 158 | ``` 159 | {static,File} 160 | ''' 161 | Render a static file from the 'www' directory: {static,"images/hello.jpg"} 162 | 163 | ``` 164 | {error,Reason} 165 | ''' 166 | Sends a status code 500 (server error) 167 | 168 | 169 | Future support will include a JSON response. 170 | 171 | == Parameters and Session == 172 | 173 | See the beepbeep_args for the api to work with sessions and parameters. 174 | 175 | 176 | 177 | 178 | 179 | 180 | -------------------------------------------------------------------------------- /doc/packages-frame.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The BeepBeep application 5 | 6 | 7 | 8 |

Packages

9 |
10 | 11 | -------------------------------------------------------------------------------- /doc/stylesheet.css: -------------------------------------------------------------------------------- 1 | /* standard EDoc style sheet */ 2 | body { 3 | font-family: Verdana, Arial, Helvetica, sans-serif; 4 | margin-left: .25in; 5 | margin-right: .2in; 6 | margin-top: 0.2in; 7 | margin-bottom: 0.2in; 8 | color: #000000; 9 | background-color: #ffffff; 10 | } 11 | h1,h2 { 12 | margin-left: -0.2in; 13 | } 14 | div.navbar { 15 | background-color: #add8e6; 16 | padding: 0.2em; 17 | } 18 | h2.indextitle { 19 | padding: 0.4em; 20 | background-color: #add8e6; 21 | } 22 | h3.function,h3.typedecl { 23 | background-color: #add8e6; 24 | padding-left: 1em; 25 | } 26 | div.spec { 27 | margin-left: 2em; 28 | background-color: #eeeeee; 29 | } 30 | a.module,a.package { 31 | text-decoration:none 32 | } 33 | a.module:hover,a.package:hover { 34 | background-color: #eeeeee; 35 | } 36 | ul.definitions { 37 | list-style-type: none; 38 | } 39 | ul.index { 40 | list-style-type: none; 41 | background-color: #eeeeee; 42 | } 43 | 44 | /* 45 | * Minor style tweaks 46 | */ 47 | ul { 48 | list-style-type: square; 49 | } 50 | table { 51 | border-collapse: collapse; 52 | } 53 | td { 54 | padding: 3 55 | } 56 | -------------------------------------------------------------------------------- /ebin/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebryson/beepbeep/62db46d268c6cb6ad86345562b3c77f8ff070b27/ebin/.gitignore -------------------------------------------------------------------------------- /example/blog/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | (cd src;$(MAKE)) 3 | 4 | clean: 5 | rm -rf ./ebin/* 6 | -------------------------------------------------------------------------------- /example/blog/ebin/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebryson/beepbeep/62db46d268c6cb6ad86345562b3c77f8ff070b27/example/blog/ebin/.gitignore -------------------------------------------------------------------------------- /example/blog/src/Makefile: -------------------------------------------------------------------------------- 1 | include ../support/include.mk 2 | 3 | all: $(EBIN_FILES_NO_DOCS) 4 | 5 | debug: 6 | $(MAKE) DEBUG=-DDEBUG 7 | 8 | -------------------------------------------------------------------------------- /example/blog/src/blog_database.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% Description : Simple in-memory database for demo purposes 3 | %%%------------------------------------------------------------------- 4 | -module(blog_database). 5 | 6 | -behaviour(gen_server). 7 | 8 | %% API 9 | -export([start/0,insert/2,latest/0]). 10 | 11 | %% gen_server callbacks 12 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, 13 | terminate/2, code_change/3]). 14 | 15 | %%==================================================================== 16 | %% API 17 | %%==================================================================== 18 | %%-------------------------------------------------------------------- 19 | %% Function: start_link() -> {ok,Pid} | ignore | {error,Error} 20 | %% Description: Starts the server 21 | %%-------------------------------------------------------------------- 22 | start() -> 23 | gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). 24 | 25 | insert(Title,Body) -> 26 | gen_server:call(?MODULE,{insert,Title,Body}). 27 | 28 | latest() -> 29 | gen_server:call(?MODULE,latest). 30 | 31 | %%==================================================================== 32 | %% gen_server callbacks 33 | %%==================================================================== 34 | 35 | %%-------------------------------------------------------------------- 36 | %% Function: init(Args) -> {ok, State} | 37 | %% {ok, State, Timeout} | 38 | %% ignore | 39 | %% {stop, Reason} 40 | %% Description: Initiates the server 41 | %%-------------------------------------------------------------------- 42 | init([]) -> 43 | {ok, []}. 44 | 45 | %%-------------------------------------------------------------------- 46 | %% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | 47 | %% {reply, Reply, State, Timeout} | 48 | %% {noreply, State} | 49 | %% {noreply, State, Timeout} | 50 | %% {stop, Reason, Reply, State} | 51 | %% {stop, Reason, State} 52 | %% Description: Handling call messages 53 | %%-------------------------------------------------------------------- 54 | handle_call({insert,Title,Body}, _From, State) -> 55 | {Y,M,D} = date(), 56 | Posted = lists:flatten(io_lib:format("~p/~p/~p",[Y,M,D])), 57 | Record = [{title,Title},{body,Body},{posted,Posted}], 58 | State1 = [Record|State], 59 | {reply, ok, State1}; 60 | 61 | handle_call(latest,_From,State) -> 62 | Reply = lists:sort(fun(A,B) -> 63 | proplists:get_value(posted,A) >= proplists:get_value(posted,B) 64 | end,State), 65 | {reply,Reply,State}. 66 | 67 | %%-------------------------------------------------------------------- 68 | %% Function: handle_cast(Msg, State) -> {noreply, State} | 69 | %% {noreply, State, Timeout} | 70 | %% {stop, Reason, State} 71 | %% Description: Handling cast messages 72 | %%-------------------------------------------------------------------- 73 | handle_cast(_Msg, State) -> 74 | {noreply, State}. 75 | 76 | %%-------------------------------------------------------------------- 77 | %% Function: handle_info(Info, State) -> {noreply, State} | 78 | %% {noreply, State, Timeout} | 79 | %% {stop, Reason, State} 80 | %% Description: Handling all non call/cast messages 81 | %%-------------------------------------------------------------------- 82 | handle_info(_Info, State) -> 83 | {noreply, State}. 84 | 85 | %%-------------------------------------------------------------------- 86 | %% Function: terminate(Reason, State) -> void() 87 | %% Description: This function is called by a gen_server when it is about to 88 | %% terminate. It should be the opposite of Module:init/1 and do any necessary 89 | %% cleaning up. When it returns, the gen_server terminates with Reason. 90 | %% The return value is ignored. 91 | %%-------------------------------------------------------------------- 92 | terminate(_Reason, _State) -> 93 | ok. 94 | 95 | %%-------------------------------------------------------------------- 96 | %% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} 97 | %% Description: Convert process state when code is changed 98 | %%-------------------------------------------------------------------- 99 | code_change(_OldVsn, State, _Extra) -> 100 | {ok, State}. 101 | 102 | %%-------------------------------------------------------------------- 103 | %%% Internal functions 104 | %%-------------------------------------------------------------------- 105 | -------------------------------------------------------------------------------- /example/blog/src/blog_example.app: -------------------------------------------------------------------------------- 1 | {application, blog_example, 2 | [{description, "blog_example"}, 3 | {vsn, "0.01"}, 4 | {modules, [ 5 | blog_example, 6 | blog_example_app, 7 | blog_example_sup, 8 | blog_example_web, 9 | blog_example_deps 10 | ]}, 11 | {registered, []}, 12 | {mod, {blog_example_app, []}}, 13 | {env, []}, 14 | {applications, [kernel, stdlib, crypto]}]}. 15 | -------------------------------------------------------------------------------- /example/blog/src/blog_example.erl: -------------------------------------------------------------------------------- 1 | -module(blog_example). 2 | -export([start/0, stop/0]). 3 | 4 | ensure_started(App) -> 5 | case application:start(App) of 6 | ok -> 7 | ok; 8 | {error, {already_started, App}} -> 9 | ok 10 | end. 11 | 12 | %% @spec start() -> ok 13 | %% @doc Start the blog_example server. 14 | start() -> 15 | blog_example_deps:ensure(), 16 | ensure_started(crypto), 17 | application:start(blog_example). 18 | 19 | %% @spec stop() -> ok 20 | %% @doc Stop the blog_example server. 21 | stop() -> 22 | Res = application:stop(blog_example), 23 | application:stop(crypto), 24 | Res. 25 | -------------------------------------------------------------------------------- /example/blog/src/blog_example_app.erl: -------------------------------------------------------------------------------- 1 | -module(blog_example_app). 2 | 3 | -behaviour(application). 4 | -export([start/2,stop/1]). 5 | 6 | 7 | %% @spec start(_Type, _StartArgs) -> ServerRet 8 | %% @doc application start callback for blog_example. 9 | start(_Type, _StartArgs) -> 10 | blog_example_deps:ensure(), 11 | blog_example_sup:start_link(). 12 | 13 | %% @spec stop(_State) -> ServerRet 14 | %% @doc application stop callback for blog_example. 15 | stop(_State) -> 16 | ok. 17 | -------------------------------------------------------------------------------- /example/blog/src/blog_example_deps.erl: -------------------------------------------------------------------------------- 1 | -module(blog_example_deps). 2 | 3 | -export([ensure/0, ensure/1]). 4 | -export([get_base_dir/0, get_base_dir/1]). 5 | -export([local_path/1, local_path/2]). 6 | -export([deps_on_path/0, new_siblings/1]). 7 | 8 | %% @spec deps_on_path() -> [ProjNameAndVers] 9 | %% @doc List of project dependencies on the path. 10 | deps_on_path() -> 11 | F = fun (X, Acc) -> 12 | ProjDir = filename:dirname(X), 13 | case {filename:basename(X), 14 | filename:basename(filename:dirname(ProjDir))} of 15 | {"ebin", "deps"} -> 16 | [filename:basename(ProjDir) | Acc]; 17 | _ -> 18 | Acc 19 | end 20 | end, 21 | ordsets:from_list(lists:foldl(F, [], code:get_path())). 22 | 23 | %% @spec new_siblings(Module) -> [Dir] 24 | %% @doc Find new siblings paths relative to Module that aren't already on the 25 | %% code path. 26 | new_siblings(Module) -> 27 | Existing = deps_on_path(), 28 | SiblingEbin = filelib:wildcard(local_path(["deps", "*", "ebin"], Module)), 29 | Siblings = [filename:dirname(X) || X <- SiblingEbin, 30 | ordsets:is_element( 31 | filename:basename(filename:dirname(X)), 32 | Existing) =:= false], 33 | lists:filter(fun filelib:is_dir/1, 34 | lists:append([[filename:join([X, "ebin"]), 35 | filename:join([X, "include"])] || 36 | X <- Siblings])). 37 | 38 | 39 | %% @spec ensure(Module) -> ok 40 | %% @doc Ensure that all ebin and include paths for dependencies 41 | %% of the application for Module are on the code path. 42 | ensure(Module) -> 43 | code:add_paths(new_siblings(Module)), 44 | code:clash(), 45 | ok. 46 | 47 | %% @spec ensure() -> ok 48 | %% @doc Ensure that the ebin and include paths for dependencies of 49 | %% this application are on the code path. Equivalent to 50 | %% ensure(?Module). 51 | ensure() -> 52 | ensure(?MODULE). 53 | 54 | %% @spec get_base_dir(Module) -> string() 55 | %% @doc Return the application directory for Module. It assumes Module is in 56 | %% a standard OTP layout application in the ebin or src directory. 57 | get_base_dir(Module) -> 58 | {file, Here} = code:is_loaded(Module), 59 | filename:dirname(filename:dirname(Here)). 60 | 61 | %% @spec get_base_dir() -> string() 62 | %% @doc Return the application directory for this application. Equivalent to 63 | %% get_base_dir(?MODULE). 64 | get_base_dir() -> 65 | get_base_dir(?MODULE). 66 | 67 | %% @spec local_path([string()], Module) -> string() 68 | %% @doc Return an application-relative directory from Module's application. 69 | local_path(Components, Module) -> 70 | filename:join([get_base_dir(Module) | Components]). 71 | 72 | %% @spec local_path(Components) -> string() 73 | %% @doc Return an application-relative directory for this application. 74 | %% Equivalent to local_path(Components, ?MODULE). 75 | local_path(Components) -> 76 | local_path(Components, ?MODULE). 77 | -------------------------------------------------------------------------------- /example/blog/src/blog_example_sup.erl: -------------------------------------------------------------------------------- 1 | -module(blog_example_sup). 2 | 3 | -behaviour(supervisor). 4 | 5 | %% External exports 6 | -export([start_link/0, upgrade/0]). 7 | 8 | %% supervisor callbacks 9 | -export([init/1]). 10 | 11 | %% @spec start_link() -> ServerRet 12 | %% @doc API for starting the supervisor. 13 | start_link() -> 14 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 15 | 16 | %% @spec upgrade() -> ok 17 | %% @doc Add processes if necessary. 18 | upgrade() -> 19 | {ok, {_, Specs}} = init([]), 20 | 21 | Old = sets:from_list( 22 | [Name || {Name, _, _, _} <- supervisor:which_children(?MODULE)]), 23 | New = sets:from_list([Name || {Name, _, _, _, _, _} <- Specs]), 24 | Kill = sets:subtract(Old, New), 25 | 26 | sets:fold(fun (Id, ok) -> 27 | supervisor:terminate_child(?MODULE, Id), 28 | supervisor:delete_child(?MODULE, Id), 29 | ok 30 | end, ok, Kill), 31 | 32 | [supervisor:start_child(?MODULE, Spec) || Spec <- Specs], 33 | ok. 34 | 35 | %% @spec init([]) -> SupervisorTree 36 | %% @doc supervisor callback. 37 | init([]) -> 38 | Ip = case os:getenv("MOCHIWEB_IP") of false -> "0.0.0.0"; Any -> Any end, 39 | WebConfig = [ 40 | {ip, Ip}, 41 | {port, 8000} 42 | ], 43 | 44 | %% Sets up the BeepBeep environment. Removing any of the below 45 | %% (except the BlogDb) will cause something to break. 46 | BaseDir = blog_example_deps:get_base_dir(), 47 | 48 | Web = {blog_example_web, 49 | {blog_example_web, start, [WebConfig]}, 50 | permanent, 5000, worker, dynamic}, 51 | Router = {beepbeep_router, 52 | {beepbeep_router, start, [BaseDir]}, 53 | permanent, 5000, worker, dynamic}, 54 | SessionServer = {beepbeep_session_server, 55 | {beepbeep_session_server,start,[]}, 56 | permanent, 5000, worker, dynamic}, 57 | 58 | %% Setup the database here so it's supervised 59 | BlogDb = {blog_database,{blog_database,start,[]}, 60 | permanent, 5000, worker, dynamic}, 61 | 62 | Processes = [Router,SessionServer,Web,BlogDb], 63 | {ok, {{one_for_one, 10, 10}, Processes}}. 64 | -------------------------------------------------------------------------------- /example/blog/src/blog_example_web.erl: -------------------------------------------------------------------------------- 1 | %% @author Dave Bryson [http://weblog.miceda.org] 2 | %% @copyright Dave Bryson 2008-2009 3 | %% 4 | %% Creates a MochiWeb Server with the BeepBeep hook 5 | %% 6 | -module(blog_example_web). 7 | -author('Dave Bryson '). 8 | 9 | -export([start/1, stop/0, loop/1]). 10 | -include("beepbeep.hrl"). 11 | 12 | start(Options) -> 13 | Loop = fun (Req) -> 14 | ?MODULE:loop(Req) 15 | end, 16 | mochiweb_http:start([{name, ?MODULE}, {loop, Loop} | Options]). 17 | 18 | stop() -> 19 | mochiweb_http:stop(?MODULE). 20 | 21 | loop(Req) -> 22 | %% Setup env... 23 | InitialEnv = mochiweb_env:setup_environment(Req), 24 | Env = setup_session(Req,InitialEnv), 25 | 26 | 27 | %% Possible return values 28 | %% {render,View,Data} 29 | %% {render,View,Data,Options} 30 | %% {text,Data} 31 | %% {json,Data} 32 | %% {redirect,Url} 33 | %% {static,File} 34 | %% {error,_} 35 | case beepbeep:dispatch(Env) of 36 | {render,View,Data} -> 37 | {ok,Content} = render_template(View,Data,Env), 38 | Req:respond({200, 39 | [{"Content-Type","text/html"}|[get_cookie(Env)]], 40 | Content}); 41 | {render,View,Data,Options} -> 42 | {Status,ContentType,Headers} = extract_options(Options), 43 | {ok,Content} = render_template(View,Data,Env), 44 | Req:respond({Status, 45 | [{"Content-Type",ContentType}|[get_cookie(Env)|Headers]], 46 | Content}); 47 | {text,Content} -> 48 | Req:respond({200, 49 | [{"Content-Type","text/plain"}|[get_cookie(Env)]], 50 | Content}); 51 | {redirect,Url} -> 52 | Req:respond({302, 53 | [{"Location", Url}, 54 | {"Content-Type", "text/html; charset=UTF-8"}], 55 | ""}); 56 | {static, File} -> 57 | "/" ++ StaticFile = File, 58 | Req:serve_file(StaticFile,blog_example_deps:local_path(["www"])); 59 | {error,_} -> 60 | Req:respond({500,[],"Server Error"}) 61 | end. 62 | 63 | render_template(ViewFile,Data,Env) -> 64 | %% Copy flash into Data and clear from Session 65 | Data1 = set_and_clear_flash(Data,Env), 66 | FullPathToFile = filename:join([blog_example_deps:local_path(["views"]),ViewFile]), 67 | beepbeep:render_template(FullPathToFile,ViewFile,Data1). 68 | 69 | extract_options(Options) -> 70 | {proplists:get_value(status,Options,200), 71 | proplists:get_value(content_type,Options,"text/html"), 72 | proplists:get_value(headers,Options,[])}. 73 | 74 | get_cookie(Env) -> 75 | mochiweb_cookies:cookie(?BEEPBEEP_SID,beepbeep_args:get_session_id(Env),[{path, "/"}]). 76 | 77 | set_and_clear_flash(Data,Env) -> 78 | case beepbeep_args:get_flash(Env) of 79 | none -> Data; 80 | Flash -> 81 | [{flash,Flash}|Data] 82 | end. 83 | 84 | setup_session(Req,Env) -> 85 | SessionKey = beepbeep_session_server:new_session(Req:get_cookie_value(?BEEPBEEP_SID)), 86 | beepbeep_args:set_session_id(SessionKey,Env). 87 | -------------------------------------------------------------------------------- /example/blog/src/home_controller.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Entry point of the app 3 | %% 4 | -module(home_controller,[Env]). 5 | 6 | -export([handle_request/2,before_filter/0]). 7 | 8 | %% Index page, pulls data from the DB and sends to index.html 9 | handle_request("index",[]) -> 10 | Posts = blog_database:latest(), 11 | %% Put the list of posts and bind to 'data' see the index.html 12 | {render,"home/index.html",[{data,Posts}]}; 13 | 14 | %% Simple return the form 15 | handle_request("new",[]) -> 16 | {render,"home/new.html",[]}; 17 | 18 | %% Pull post parameters from the form, insert into the db and 19 | %% redirect to the front page 20 | handle_request("create",[]) -> 21 | Title = beepbeep_args:get_param("post_title",Env), 22 | Body = beepbeep_args:get_param("post_body",Env), 23 | 24 | %% Example of validation. Require a Title 25 | if 26 | Title =:= undefined orelse length(Title) =:= 0 -> 27 | %% Blank 28 | %% Set flash message for the template 29 | beepbeep_args:flash({notice,"Title Required!"},Env), 30 | {redirect,"/home/new"}; 31 | true -> 32 | %% Set flash message for the template 33 | beepbeep_args:flash({notice,"Post Created!"},Env), 34 | blog_database:insert(Title,Body), 35 | {redirect,"/"} 36 | end. 37 | 38 | %% Shows how to filter on certain actions in this controller 39 | before_filter() -> 40 | %% Specify that we only want to filter on 'new' and 'create' 41 | FilterOnly = ["new","create"], 42 | %% Check what the current action is. BeepBeep stores the requested 43 | %% action for access 44 | case lists:member(beepbeep_args:get_action(Env),FilterOnly) of 45 | true -> 46 | %% Check the session for the key 'user_id' 47 | %% if it's there, you are already logged in 48 | %% otherwise send you to the login page 49 | case beepbeep_args:get_session_data("user_id",Env) of 50 | undefined -> 51 | {redirect,"/login/new"}; 52 | _A -> ok 53 | end; 54 | false -> 55 | ok 56 | end. 57 | 58 | 59 | -------------------------------------------------------------------------------- /example/blog/src/login_controller.erl: -------------------------------------------------------------------------------- 1 | %% Controls login 2 | -module(login_controller,[Env]). 3 | 4 | -export([handle_request/2,before_filter/0]). 5 | 6 | %% Return the login form 7 | handle_request("new",[]) -> 8 | {render,"login/new.html",[]}; 9 | 10 | %% Get the post params from the form and verify 11 | handle_request("create",[]) -> 12 | Un = beepbeep_args:get_param("un",Env), 13 | Pw = beepbeep_args:get_param("pw",Env), 14 | 15 | %% Check if the user entered the proper Username and Password. 16 | %% In this case it's hard code - foo:foobar 17 | case Un =:= "foo" andalso Pw =:= "foobar" of 18 | true -> 19 | %% If the user entered the correct Un/Pw 20 | %% Set the user_id in the session and redirect 21 | beepbeep_args:set_session_data("user_id","dave",Env), 22 | 23 | {redirect,"/home/new"}; 24 | false -> 25 | %% Bad username and password - redirect back to the login page 26 | {redirect,"/login/new"} 27 | end. 28 | 29 | %% No filter used 30 | before_filter() -> 31 | ok. 32 | -------------------------------------------------------------------------------- /example/blog/start-server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cd `dirname $0` 3 | 4 | exec erl -pa $PWD/ebin $PWD/../../deps/*/ebin $PWD/../../ebin -boot start_sasl -s reloader -s blog_example 5 | -------------------------------------------------------------------------------- /example/blog/support/include.mk: -------------------------------------------------------------------------------- 1 | ## -*- makefile -*- 2 | 3 | ###################################################################### 4 | ## Erlang 5 | 6 | ERL := erl 7 | ERLC := $(ERL)c 8 | 9 | INCLUDE_DIRS := ../../../include $(wildcard ../../../deps/*/include) 10 | EBIN_DIRS := $(wildcard ../../../deps/*/ebin) $(wildcard ../../../ebin/*) 11 | ERLC_FLAGS := -W $(INCLUDE_DIRS:../%=-I ../%) $(EBIN_DIRS:%=-pa %) 12 | 13 | ifndef no_debug_info 14 | ERLC_FLAGS += +debug_info 15 | endif 16 | 17 | ifdef debug 18 | ERLC_FLAGS += -Ddebug 19 | endif 20 | 21 | EBIN_DIR := ../ebin 22 | DOC_DIR := ../doc 23 | EMULATOR := beam 24 | 25 | ERL_SOURCES := $(wildcard *.erl) 26 | ERL_HEADERS := $(wildcard *.hrl) $(wildcard ../../../include/*.hrl) 27 | ERL_OBJECTS := $(ERL_SOURCES:%.erl=$(EBIN_DIR)/%.$(EMULATOR)) 28 | ERL_DOCUMENTS := $(ERL_SOURCES:%.erl=$(DOC_DIR)/%.html) 29 | ERL_OBJECTS_LOCAL := $(ERL_SOURCES:%.erl=./%.$(EMULATOR)) 30 | APP_FILES := $(wildcard *.app) 31 | EBIN_FILES = $(ERL_OBJECTS) $(ERL_DOCUMENTS) $(APP_FILES:%.app=../ebin/%.app) 32 | EBIN_FILES_NO_DOCS = $(ERL_OBJECTS) $(APP_FILES:%.app=../ebin/%.app) 33 | MODULES = $(ERL_SOURCES:%.erl=%) 34 | 35 | ../ebin/%.app: %.app 36 | cp $< $@ 37 | 38 | $(EBIN_DIR)/%.$(EMULATOR): %.erl 39 | $(ERLC) $(ERLC_FLAGS) -o $(EBIN_DIR) $< 40 | 41 | ./%.$(EMULATOR): %.erl 42 | $(ERLC) $(ERLC_FLAGS) -o . $< 43 | 44 | $(DOC_DIR)/%.html: %.erl 45 | $(ERL) -noshell -run edoc file $< -run init stop 46 | mv *.html $(DOC_DIR) 47 | -------------------------------------------------------------------------------- /example/blog/views/base.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | BeepBeep blog example 7 | 8 | 9 | 10 |

BeepBeep Blog

11 |

A simple example using BeepBeep

12 | {% block content %}{% endblock %} 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/blog/views/flash.html: -------------------------------------------------------------------------------- 1 | {% if flash %} 2 | {% for k,message in flash %} 3 |

{{ message }}

4 | {% endfor %} 5 | {% endif %} 6 | -------------------------------------------------------------------------------- /example/blog/views/home/index.html: -------------------------------------------------------------------------------- 1 | {% extends "../base.html" %} 2 | {% block content %} 3 | 4 | {% include "flash.html" %} 5 | 6 | {% if data %} 7 | {% for i in data %} 8 |

{{ i.title }}

9 | {{ i.body }} 10 |

11 | posted: {{ i.posted }} 12 |
13 | {% endfor %} 14 |
15 | Add a Post 16 | {% else %} 17 | No posts Add one! 18 | {% endif %} 19 | 20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /example/blog/views/home/new.html: -------------------------------------------------------------------------------- 1 | {% extends "../base.html" %} 2 | {% block content %} 3 | 4 | {% include "flash.html" %} 5 | 6 |

Add a Post

7 |
8 | Title:
9 | 10 |
11 | 12 |
13 |  | Cancel 14 |
15 | 16 | {% endblock %} 17 | -------------------------------------------------------------------------------- /example/blog/views/login/new.html: -------------------------------------------------------------------------------- 1 | {% extends "../base.html" %} 2 | {% block content %} 3 | 4 |

Please login

5 | (shhh... login with Username: 'foo' Password: 'foobar') 6 | 7 |
8 | Un: 9 |
10 | Pw: 11 |

12 | 13 |
14 | 15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /example/blog/www/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body 2 | { 3 | background-color: #fff; color: #333; 4 | font-family: verdana, arial, helvetica, sans-serif; 5 | font-size: 13px; 6 | margin-left: 50px; 7 | margin-top: 50px; 8 | margin-right: 200px; 9 | } -------------------------------------------------------------------------------- /include/beepbeep.hrl: -------------------------------------------------------------------------------- 1 | %% Session Id key 2 | -define(BEEPBEEP_SID, "_beepbeep_session_id_"). 3 | 4 | %% Environment data 5 | -define(BEEPBEEP_ENV_DATA, [{server_sw, "SERVER_SOFTWARE"}, 6 | {server_name, "SERVER_NAME"}, 7 | {server_protocol, "SERVER_PROTOCOL"}, 8 | {server_port, "SERVER_PORT"}, 9 | {method, "REQUEST_METHOD"}, 10 | {content_type, "CONTENT_TYPE"}, 11 | {content_length,"CONTENT_LENGTH"}, 12 | {path_info, "PATH_INFO"}, 13 | {remote_addr, "REMOTE_ADDR"}, 14 | {beepbeep_params, "beepbeep.data"}]). 15 | -------------------------------------------------------------------------------- /priv/skel/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | (cd src;$(MAKE)) 3 | 4 | clean: 5 | (cd src;$(MAKE) clean) 6 | -------------------------------------------------------------------------------- /priv/skel/deps/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebryson/beepbeep/62db46d268c6cb6ad86345562b3c77f8ff070b27/priv/skel/deps/.gitignore -------------------------------------------------------------------------------- /priv/skel/ebin/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebryson/beepbeep/62db46d268c6cb6ad86345562b3c77f8ff070b27/priv/skel/ebin/.gitignore -------------------------------------------------------------------------------- /priv/skel/src/Makefile: -------------------------------------------------------------------------------- 1 | include ../support/include.mk 2 | 3 | all: $(EBIN_FILES_NO_DOCS) 4 | 5 | debug: 6 | $(MAKE) DEBUG=-DDEBUG 7 | 8 | clean: 9 | rm -rf $(EBIN_FILES_NO_DOCS) 10 | -------------------------------------------------------------------------------- /priv/skel/src/home_controller.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Sample default "/" controller, implement this to your needs 3 | %% 4 | -module(home_controller,[Env]). 5 | 6 | -export([handle_request/2,before_filter/0]). 7 | 8 | handle_request("index",[]) -> 9 | {render,"home/index.html",[{data,"Hello There From BeepBeep!"}]}; 10 | 11 | handle_request("show",[Year]) -> 12 | Sid = beepbeep_args:get_session_id(Env), 13 | Name = beepbeep_args:get_param("name",Env), 14 | 15 | beepbeep_args:flash({notice,"Example of a flash message between actions. See flash.html!"},Env), 16 | 17 | {render,"home/show.html",[{year,Year},{sid,Sid},{name,Name}]}. 18 | 19 | 20 | before_filter() -> 21 | %% Shows how to potentially filter on certain actions in this 22 | %% controller 23 | FilterOnly = ["show"], 24 | case lists:member(beepbeep_args:get_action(Env),FilterOnly) of 25 | true -> 26 | error_logger:info_report("Doing the filter for SHOW~n"), 27 | ok; 28 | false -> 29 | ok 30 | end. 31 | 32 | 33 | -------------------------------------------------------------------------------- /priv/skel/src/skel.app: -------------------------------------------------------------------------------- 1 | {application, skel, 2 | [{description, "skel"}, 3 | {vsn, "0.01"}, 4 | {modules, [ 5 | skel, 6 | skel_app, 7 | skel_sup, 8 | skel_web, 9 | skel_deps 10 | ]}, 11 | {registered, []}, 12 | {mod, {skel_app, []}}, 13 | {env, []}, 14 | {applications, [kernel, stdlib, crypto]}]}. 15 | -------------------------------------------------------------------------------- /priv/skel/src/skel.erl: -------------------------------------------------------------------------------- 1 | -module(skel). 2 | -export([start/0, stop/0]). 3 | 4 | ensure_started(App) -> 5 | case application:start(App) of 6 | ok -> 7 | ok; 8 | {error, {already_started, App}} -> 9 | ok 10 | end. 11 | 12 | %% @spec start() -> ok 13 | %% @doc Start the skel server. 14 | start() -> 15 | skel_deps:ensure(), 16 | ensure_started(crypto), 17 | application:start(skel). 18 | 19 | %% @spec stop() -> ok 20 | %% @doc Stop the skel server. 21 | stop() -> 22 | Res = application:stop(skel), 23 | application:stop(crypto), 24 | Res. 25 | -------------------------------------------------------------------------------- /priv/skel/src/skel_app.erl: -------------------------------------------------------------------------------- 1 | -module(skel_app). 2 | 3 | -behaviour(application). 4 | -export([start/2,stop/1]). 5 | 6 | 7 | %% @spec start(_Type, _StartArgs) -> ServerRet 8 | %% @doc application start callback for skel. 9 | start(_Type, _StartArgs) -> 10 | skel_deps:ensure(), 11 | skel_sup:start_link(). 12 | 13 | %% @spec stop(_State) -> ServerRet 14 | %% @doc application stop callback for skel. 15 | stop(_State) -> 16 | ok. 17 | -------------------------------------------------------------------------------- /priv/skel/src/skel_deps.erl: -------------------------------------------------------------------------------- 1 | -module(skel_deps). 2 | 3 | -export([ensure/0, ensure/1]). 4 | -export([get_base_dir/0, get_base_dir/1]). 5 | -export([local_path/1, local_path/2]). 6 | -export([deps_on_path/0, new_siblings/1]). 7 | 8 | %% @spec deps_on_path() -> [ProjNameAndVers] 9 | %% @doc List of project dependencies on the path. 10 | deps_on_path() -> 11 | F = fun (X, Acc) -> 12 | ProjDir = filename:dirname(X), 13 | case {filename:basename(X), 14 | filename:basename(filename:dirname(ProjDir))} of 15 | {"ebin", "deps"} -> 16 | [filename:basename(ProjDir) | Acc]; 17 | _ -> 18 | Acc 19 | end 20 | end, 21 | ordsets:from_list(lists:foldl(F, [], code:get_path())). 22 | 23 | %% @spec new_siblings(Module) -> [Dir] 24 | %% @doc Find new siblings paths relative to Module that aren't already on the 25 | %% code path. 26 | new_siblings(Module) -> 27 | Existing = deps_on_path(), 28 | SiblingEbin = filelib:wildcard(local_path(["deps", "*", "ebin"], Module)), 29 | Siblings = [filename:dirname(X) || X <- SiblingEbin, 30 | ordsets:is_element( 31 | filename:basename(filename:dirname(X)), 32 | Existing) =:= false], 33 | lists:filter(fun filelib:is_dir/1, 34 | lists:append([[filename:join([X, "ebin"]), 35 | filename:join([X, "include"])] || 36 | X <- Siblings])). 37 | 38 | 39 | %% @spec ensure(Module) -> ok 40 | %% @doc Ensure that all ebin and include paths for dependencies 41 | %% of the application for Module are on the code path. 42 | ensure(Module) -> 43 | code:add_paths(new_siblings(Module)), 44 | code:clash(), 45 | ok. 46 | 47 | %% @spec ensure() -> ok 48 | %% @doc Ensure that the ebin and include paths for dependencies of 49 | %% this application are on the code path. Equivalent to 50 | %% ensure(?Module). 51 | ensure() -> 52 | ensure(?MODULE). 53 | 54 | %% @spec get_base_dir(Module) -> string() 55 | %% @doc Return the application directory for Module. It assumes Module is in 56 | %% a standard OTP layout application in the ebin or src directory. 57 | get_base_dir(Module) -> 58 | {file, Here} = code:is_loaded(Module), 59 | filename:dirname(filename:dirname(Here)). 60 | 61 | %% @spec get_base_dir() -> string() 62 | %% @doc Return the application directory for this application. Equivalent to 63 | %% get_base_dir(?MODULE). 64 | get_base_dir() -> 65 | get_base_dir(?MODULE). 66 | 67 | %% @spec local_path([string()], Module) -> string() 68 | %% @doc Return an application-relative directory from Module's application. 69 | local_path(Components, Module) -> 70 | filename:join([get_base_dir(Module) | Components]). 71 | 72 | %% @spec local_path(Components) -> string() 73 | %% @doc Return an application-relative directory for this application. 74 | %% Equivalent to local_path(Components, ?MODULE). 75 | local_path(Components) -> 76 | local_path(Components, ?MODULE). 77 | -------------------------------------------------------------------------------- /priv/skel/src/skel_sup.erl: -------------------------------------------------------------------------------- 1 | -module(skel_sup). 2 | 3 | -behaviour(supervisor). 4 | 5 | %% External exports 6 | -export([start_link/0, upgrade/0]). 7 | 8 | %% supervisor callbacks 9 | -export([init/1]). 10 | 11 | %% @spec start_link() -> ServerRet 12 | %% @doc API for starting the supervisor. 13 | start_link() -> 14 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 15 | 16 | %% @spec upgrade() -> ok 17 | %% @doc Add processes if necessary. 18 | upgrade() -> 19 | {ok, {_, Specs}} = init([]), 20 | 21 | Old = sets:from_list( 22 | [Name || {Name, _, _, _} <- supervisor:which_children(?MODULE)]), 23 | New = sets:from_list([Name || {Name, _, _, _, _, _} <- Specs]), 24 | Kill = sets:subtract(Old, New), 25 | 26 | sets:fold(fun (Id, ok) -> 27 | supervisor:terminate_child(?MODULE, Id), 28 | supervisor:delete_child(?MODULE, Id), 29 | ok 30 | end, ok, Kill), 31 | 32 | [supervisor:start_child(?MODULE, Spec) || Spec <- Specs], 33 | ok. 34 | 35 | %% @spec init([]) -> SupervisorTree 36 | %% @doc supervisor callback. 37 | init([]) -> 38 | Ip = case os:getenv("MOCHIWEB_IP") of false -> "0.0.0.0"; Any -> Any end, 39 | WebConfig = [ 40 | {ip, Ip}, 41 | {port, 8000} 42 | ], 43 | 44 | %% Sets up the BeepBeep environment. Removing any of the below 45 | %% will cause something to break. 46 | BaseDir = skel_deps:get_base_dir(), 47 | 48 | Web = {skel_web, 49 | {skel_web, start, [WebConfig]}, 50 | permanent, 5000, worker, dynamic}, 51 | Router = {beepbeep_router, 52 | {beepbeep_router, start, [BaseDir]}, 53 | permanent, 5000, worker, dynamic}, 54 | SessionServer = {beepbeep_session_server, 55 | {beepbeep_session_server,start,[]}, 56 | permanent, 5000, worker, dynamic}, 57 | 58 | Processes = [Router,SessionServer,Web], 59 | {ok, {{one_for_one, 10, 10}, Processes}}. 60 | -------------------------------------------------------------------------------- /priv/skel/src/skel_web.erl: -------------------------------------------------------------------------------- 1 | %% @author Dave Bryson [http://weblog.miceda.org] 2 | %% @copyright Dave Bryson 2008-2009 3 | %% 4 | %% Creates a MochiWeb Server with the BeepBeep hook 5 | %% 6 | -module(skel_web). 7 | -author('Dave Bryson '). 8 | 9 | -export([start/1, stop/0, loop/1]). 10 | -include("beepbeep.hrl"). 11 | 12 | start(Options) -> 13 | Loop = fun (Req) -> 14 | ?MODULE:loop(Req) 15 | end, 16 | mochiweb_http:start([{name, ?MODULE}, {loop, Loop} | Options]). 17 | 18 | stop() -> 19 | mochiweb_http:stop(?MODULE). 20 | 21 | loop(Req) -> 22 | %% Setup env... 23 | InitialEnv = mochiweb_env:setup_environment(Req), 24 | Env = setup_session(Req,InitialEnv), 25 | 26 | 27 | %% Possible return values 28 | %% {render,View,Data} 29 | %% {render,View,Data,Options} 30 | %% {text,Data} 31 | %% {json,Data} 32 | %% {redirect,Url} 33 | %% {static,File} 34 | %% {error,_} 35 | case beepbeep:dispatch(Env) of 36 | {render,View,Data} -> 37 | {ok,Content} = render_template(View,Data,Env), 38 | Req:respond({200, 39 | [{"Content-Type","text/html"}|[get_cookie(Env)]], 40 | Content}); 41 | {render,View,Data,Options} -> 42 | {Status,ContentType,Headers} = extract_options(Options), 43 | {ok,Content} = render_template(View,Data,Env), 44 | Req:respond({Status, 45 | [{"Content-Type",ContentType}|[get_cookie(Env)|Headers]], 46 | Content}); 47 | {text,Content} -> 48 | Req:respond({200, 49 | [{"Content-Type","text/plain"}|[get_cookie(Env)]], 50 | Content}); 51 | {redirect,Url} -> 52 | Req:respond({302, 53 | [{"Location", Url}, 54 | {"Content-Type", "text/html; charset=UTF-8"}], 55 | ""}); 56 | {static, File} -> 57 | "/" ++ StaticFile = File, 58 | Req:serve_file(StaticFile,skel_deps:local_path(["www"])); 59 | {error,_} -> 60 | Req:respond({500,[],"Server Error"}) 61 | end. 62 | 63 | render_template(ViewFile,Data,Env) -> 64 | %% Copy flash into Data and clear from Session 65 | Data1 = set_and_clear_flash(Data,Env), 66 | FullPathToFile = filename:join([skel_deps:local_path(["views"]),ViewFile]), 67 | beepbeep:render_template(FullPathToFile,ViewFile,Data1). 68 | 69 | extract_options(Options) -> 70 | {proplists:get_value(status,Options,200), 71 | proplists:get_value(content_type,Options,"text/html"), 72 | proplists:get_value(headers,Options,[])}. 73 | 74 | get_cookie(Env) -> 75 | mochiweb_cookies:cookie(?BEEPBEEP_SID,beepbeep_args:get_session_id(Env),[{path, "/"}]). 76 | 77 | set_and_clear_flash(Data,Env) -> 78 | case beepbeep_args:get_flash(Env) of 79 | none -> Data; 80 | Flash -> 81 | [{flash,Flash}|Data] 82 | end. 83 | 84 | setup_session(Req,Env) -> 85 | SessionKey = beepbeep_session_server:new_session(Req:get_cookie_value(?BEEPBEEP_SID)), 86 | beepbeep_args:set_session_id(SessionKey,Env). 87 | -------------------------------------------------------------------------------- /priv/skel/start-server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cd `dirname $0` 3 | 4 | exec erl -pa $PWD/ebin $PWD/deps/*/ebin -boot start_sasl -s reloader -s skel 5 | -------------------------------------------------------------------------------- /priv/skel/support/include.mk: -------------------------------------------------------------------------------- 1 | ## -*- makefile -*- 2 | 3 | ###################################################################### 4 | ## Erlang 5 | 6 | ERL := erl 7 | ERLC := $(ERL)c 8 | 9 | INCLUDE_DIRS := ../include $(wildcard ../deps/*/include) 10 | EBIN_DIRS := $(wildcard ../deps/*/ebin) 11 | ERLC_FLAGS := -W $(INCLUDE_DIRS:../%=-I ../%) $(EBIN_DIRS:%=-pa %) 12 | 13 | ifndef no_debug_info 14 | ERLC_FLAGS += +debug_info 15 | endif 16 | 17 | ifdef debug 18 | ERLC_FLAGS += -Ddebug 19 | endif 20 | 21 | EBIN_DIR := ../ebin 22 | DOC_DIR := ../doc 23 | EMULATOR := beam 24 | 25 | ERL_SOURCES := $(wildcard *.erl) 26 | ERL_HEADERS := $(wildcard *.hrl) $(wildcard ../include/*.hrl) 27 | ERL_OBJECTS := $(ERL_SOURCES:%.erl=$(EBIN_DIR)/%.$(EMULATOR)) 28 | ERL_DOCUMENTS := $(ERL_SOURCES:%.erl=$(DOC_DIR)/%.html) 29 | ERL_OBJECTS_LOCAL := $(ERL_SOURCES:%.erl=./%.$(EMULATOR)) 30 | APP_FILES := $(wildcard *.app) 31 | EBIN_FILES = $(ERL_OBJECTS) $(ERL_DOCUMENTS) $(APP_FILES:%.app=../ebin/%.app) 32 | EBIN_FILES_NO_DOCS = $(ERL_OBJECTS) $(APP_FILES:%.app=../ebin/%.app) 33 | MODULES = $(ERL_SOURCES:%.erl=%) 34 | 35 | ../ebin/%.app: %.app 36 | cp $< $@ 37 | 38 | $(EBIN_DIR)/%.$(EMULATOR): %.erl 39 | $(ERLC) $(ERLC_FLAGS) -o $(EBIN_DIR) $< 40 | 41 | ./%.$(EMULATOR): %.erl 42 | $(ERLC) $(ERLC_FLAGS) -o . $< 43 | 44 | $(DOC_DIR)/%.html: %.erl 45 | $(ERL) -noshell -run edoc file $< -run init stop 46 | mv *.html $(DOC_DIR) 47 | -------------------------------------------------------------------------------- /priv/skel/views/base.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | Welcome to BeepBeep 7 | 8 | 9 | 10 |

BeepBeep

11 |

A simple example using BeepBeep

12 | {% block content %}{% endblock %} 13 | 14 | 15 | -------------------------------------------------------------------------------- /priv/skel/views/flash.html: -------------------------------------------------------------------------------- 1 | {% if flash %} 2 | {% for k,message in flash %} 3 |

{{ message }}

4 | {% endfor %} 5 | {% endif %} 6 | -------------------------------------------------------------------------------- /priv/skel/views/home/index.html: -------------------------------------------------------------------------------- 1 | {% extends "../base.html" %} 2 | {% block content %} 3 | Message is: {{ data }} 4 |
5 |
6 | Show more... 7 | {% endblock %} 8 | -------------------------------------------------------------------------------- /priv/skel/views/home/show.html: -------------------------------------------------------------------------------- 1 | {% extends "../base.html" %} 2 | {% block content %} 3 | 4 | {% include "../flash.html" %} 5 | 6 | You choose year: {{ year }} 7 |

8 | Session id: {{ sid }} 9 |

10 |

11 | Name: {{ name }} 12 |

13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /priv/skel/www/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body 2 | { 3 | background-color: #fff; color: #333; 4 | font-family: verdana, arial, helvetica, sans-serif; 5 | font-size: 13px; 6 | margin-left: 50px; 7 | margin-top: 50px; 8 | margin-right: 200px; 9 | } -------------------------------------------------------------------------------- /script/new_beep.erl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env escript 2 | 3 | -export([main/1]). 4 | 5 | %% Borrowed from the excellent MochiWeb 6 | 7 | %% External API 8 | 9 | main([Name]) -> 10 | main([Name, "."]); 11 | main([Name, Dest]) -> 12 | ensure(), 13 | DestDir = filename:absname(Dest), 14 | ok = beepbeep_skel:skelcopy(DestDir, Name); 15 | main(_) -> 16 | usage(). 17 | 18 | %% Internal API 19 | 20 | ensure() -> 21 | code:add_patha(filename:join(filename:dirname(escript:script_name()), 22 | "../ebin")). 23 | 24 | usage() -> 25 | io:format("usage: ~s name [destdir]~n", 26 | [filename:basename(escript:script_name())]), 27 | halt(1). 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | include ../support/include.mk 2 | 3 | all: $(EBIN_FILES_NO_DOCS) 4 | 5 | debug: 6 | $(MAKE) DEBUG=-DDEBUG 7 | 8 | clean: 9 | rm -rf $(EBIN_FILES_NO_DOCS) 10 | -------------------------------------------------------------------------------- /src/beepbeep.erl: -------------------------------------------------------------------------------- 1 | %% @author Dave Bryson [http://weblog.miceda.org] 2 | %% @copyright Dave Bryson 2008-2009 3 | %% 4 | %% @doc BeepBeep Dispatcher 5 | %% 6 | %% This is called from the MochiWeb server to dispatch 7 | %% requests. Requests are mapped to modules (controllers) and 8 | %% functions (actions) based on path components in the Url. For 9 | %% example, the request: 10 | %% 11 | %% '/feed/show' 12 | %% 13 | %% would get mapped to the module (controller) 'feed' and the function 14 | %% (action) 'show'. 15 | %% By default the root request: 16 | %% 17 | %% '/' 18 | %% 19 | %% is automatically mapped to the module 'home' and the function 'index' 20 | %% Maps Urls to controllers and their views 21 | %% 22 | -module(beepbeep). 23 | -author('Dave Bryson '). 24 | 25 | -export([dispatch/1,render_template/3]). 26 | 27 | %% 28 | %% @doc Dispatches the incoming request to the proper module 29 | %% and function and returns the tuple() from the controller. 30 | %% 31 | %% @spec dispatch(Env::record()) -> tuple() 32 | %% 33 | dispatch(Env) -> 34 | PathComponents = beepbeep_args:path_components(Env), 35 | {ControllerName,ActionName,Args} = case PathComponents of 36 | [] -> 37 | {"home","index",[]}; 38 | [C] -> 39 | {C,"index",[]}; 40 | [C,A | Params] -> 41 | {C,A,Params} 42 | end, 43 | case beepbeep_router:get_controller(ControllerName) of 44 | {ok,Controller} -> 45 | process_request(Env,Controller,ActionName,Args); 46 | no_controller -> 47 | %% Try static content using the PATH_INFO 48 | F = beepbeep_args:path(Env), 49 | {static, F} 50 | end. 51 | 52 | 53 | process_request(Env,ControllerName,ActionName,Args) -> 54 | Env1 = beepbeep_args:set_action(Env,ActionName), 55 | Controller = ControllerName:new(Env1), 56 | %% 57 | %% First try the before_filter. If the call returns 'ok', then try the 58 | %% action call. If the action call returns any kind of exit, return 59 | %% {error,no_action}. Otherwise return the response from the Controller 60 | %% 61 | case try_filter(Controller) of 62 | ok -> 63 | case catch(Controller:handle_request(ActionName,Args)) of 64 | {'EXIT',_} -> 65 | {error,no_action}; 66 | Response -> Response 67 | end; 68 | Any -> 69 | Any 70 | end. 71 | 72 | try_filter(ControllerName) -> 73 | case catch(ControllerName:before_filter()) of 74 | {'EXIT', {undef,_}} -> ok; 75 | Any -> Any 76 | end. 77 | 78 | %% 79 | %% @doc Render the template with the given data. 80 | %% This is called from YOUR_APP_web.erl automatically. 81 | %% 82 | render_template(FullPathToFile,ViewFile,Data) -> 83 | Pieces = string:tokens(ViewFile,"/"), 84 | Name = string:join(Pieces,"_"), 85 | Name1 = filename:basename(Name,".html"), 86 | ModName = list_to_atom(Name1 ++ "_view"), 87 | 88 | erlydtl:compile(FullPathToFile,ModName), 89 | ModName:render(Data). 90 | 91 | 92 | -------------------------------------------------------------------------------- /src/beepbeep_args.erl: -------------------------------------------------------------------------------- 1 | %% @author Dave Bryson [http://weblog.miceda.org] 2 | %% @copyright Dave Bryson 2008-2009 3 | %% 4 | %% @doc Helper functions for accessing request and session information 5 | %% in your controllers. 6 | %% 7 | -module(beepbeep_args). 8 | -export([path/1, 9 | path_components/1, 10 | server_software/1, 11 | server_name/1, 12 | server_protocol/1, 13 | server_port/1, 14 | method/1, 15 | content_type/1, 16 | content_length/1, 17 | remote_addr/1, 18 | get_all_headers/1, 19 | get_param/2, 20 | get_session_id/1, 21 | set_session_id/2, 22 | set_session_data/3, 23 | get_session_data/2, 24 | remove_session_data/2, 25 | get_all_session_data/1, 26 | get_action/1, 27 | set_action/2, 28 | flash/2, 29 | get_flash/1]). 30 | 31 | -author('Dave Bryson '). 32 | 33 | %% 34 | %% @doc Return the Path 35 | %% 36 | path(Env) -> 37 | proplists:get_value("PATH_INFO",Env). 38 | 39 | %% 40 | %% @doc Return the Path as an array parsed on the "/" 41 | %% 42 | path_components(Env) -> 43 | string:tokens(path(Env),"/"). 44 | 45 | %% 46 | %% @doc Return the name of the server 47 | %% 48 | server_software(Env) -> 49 | proplists:get_value("SERVER_SOFTWARE", Env). 50 | 51 | %% 52 | %% @doc Return the hostname of the server 53 | %% 54 | server_name(Env) -> 55 | proplists:get_value("SERVER_NAME", Env). 56 | 57 | %% 58 | %% @doc Return the protocol 59 | %% 60 | server_protocol(Env) -> 61 | proplists:get_value("SERVER_PROTOCOL", Env). 62 | 63 | %% 64 | %% @doc Return the Server port 65 | %% 66 | server_port(Env) -> 67 | proplists:get_value("SERVER_PORT", Env). 68 | 69 | %% 70 | %% @doc Return the request method: GET,PUT,POST,DELETE 71 | %% 72 | method(Env) -> 73 | proplists:get_value("REQUEST_METHOD", Env). 74 | 75 | %% 76 | %% @doc Return the content-type 77 | %% 78 | content_type(Env) -> 79 | proplists:get_value("CONTENT_TYPE", Env). 80 | 81 | %% 82 | %% @doc Return the content_length 83 | %% 84 | content_length(Env) -> 85 | proplists:get_value("CONTENT_LENGTH", Env). 86 | 87 | %% 88 | %% @doc Return the Remote address of the client 89 | %% 90 | remote_addr(Env) -> 91 | proplists:get_value("REMOTE_ADDR", Env). 92 | 93 | %% 94 | %% @doc Return all Headers 95 | %% 96 | get_all_headers(Env) -> 97 | lists:foldl(fun({"HTTP_" ++ _, _}=Pair, Hdrs) -> 98 | [Pair|Hdrs]; 99 | (_, Hdrs) -> 100 | Hdrs 101 | end, [], Env). 102 | 103 | %% 104 | %% @doc Return a request Value for a given Key. This contains information 105 | %% from a form POST OR GET query string 106 | %% 107 | get_param(Key,Env) -> 108 | Params = proplists:get_value("beepbeep.data",Env), 109 | proplists:get_value(Key,Params). 110 | 111 | %% 112 | %% @doc Set a 'flash' message for use in your template. All flash message are wrapped in a List 113 | %% 114 | flash(Term,Env) -> 115 | Flash = case get_session_data(beepbeep_flash,Env) of 116 | undefined -> 117 | [Term]; 118 | ExistingFlash -> 119 | [Term|ExistingFlash] 120 | end, 121 | set_session_data(beepbeep_flash,Flash,Env). 122 | 123 | 124 | %% Get and clear the flash 125 | get_flash(Env) -> 126 | Sid = get_session_id(Env), 127 | case get_session_data(beepbeep_flash,Env) of 128 | undefined -> 129 | %% No flash data 130 | none; 131 | Data -> 132 | beepbeep_session_server:remove_session_data(Sid,beepbeep_flash), 133 | Data 134 | end. 135 | 136 | 137 | %% 138 | %% @doc Get the current session id 139 | %% 140 | get_session_id(Env) -> 141 | proplists:get_value("beepbeep_sid",Env). 142 | 143 | %% 144 | %% Sets the session id. This is done internally 145 | %% and should not be used manually 146 | %% @hidden 147 | %% 148 | set_session_id(Value,Env) -> 149 | case lists:keysearch("beepbeep_sid",1,Env) of 150 | {value,_} -> 151 | set_value("beepbeep_sid",Value,Env); 152 | false -> 153 | [proplists:property({"beepbeep_sid", Value})|Env] 154 | end. 155 | 156 | %% 157 | %% @doc Set a Key,Value in the session 158 | %% 159 | set_session_data(Key,Value,Env) -> 160 | Sid = get_session_id(Env), 161 | beepbeep_session_server:set_session_data(Sid,Key,Value). 162 | 163 | %% 164 | %% @doc Return all session data 165 | %% 166 | get_all_session_data(Env) -> 167 | Sid = get_session_id(Env), 168 | beepbeep_session_server:get_session_data(Sid). 169 | 170 | %% 171 | %% @doc Get the session data for a given key 172 | %% 173 | get_session_data(Key,Env) -> 174 | proplists:get_value(Key,get_all_session_data(Env)). 175 | 176 | 177 | %% 178 | %% @doc Remove a Key,Value in the session 179 | %% 180 | remove_session_data(Key,Env) -> 181 | Sid = get_session_id(Env), 182 | beepbeep_session_server:remove_session_data(Sid,Key). 183 | 184 | %% 185 | %% @doc Return the current requested action 186 | %% 187 | get_action(Env) -> 188 | proplists:get_value("action_name",Env). 189 | 190 | %% 191 | %% @doc Warning! Should not be set manually. This is 192 | %% done automatically in the dispather. 193 | %% @hidden 194 | %% 195 | set_action(Env,Value) -> 196 | case lists:keysearch("action_name",1,Env) of 197 | {value,_} -> 198 | set_value("action_name",Value,Env); 199 | false -> 200 | [proplists:property({"action_name", Value})|Env] 201 | end. 202 | 203 | %% 204 | %% @doc Set an Key,Value in the environment. 205 | %% Used internally 206 | %% @hidden 207 | set_value(Key, Val, Env) -> 208 | lists:keyreplace(Key, 1, Env, {Key, Val}). 209 | 210 | %% get_value(Key, Env) -> 211 | %% proplists:get_value(Key, Env). 212 | 213 | %% get_value(Key, Env, Default) -> 214 | %% proplists:get_value(Key, Env, Default). 215 | 216 | %% get_all_values(Key, Env) -> 217 | %% proplists:get_all_values(Key, Env). 218 | 219 | -------------------------------------------------------------------------------- /src/beepbeep_router.erl: -------------------------------------------------------------------------------- 1 | %% @author Dave Bryson [http://weblog.miceda.org] 2 | %% @copyright Dave Bryson 2008-2009 3 | %% 4 | %% @doc On start up, this module builds a mapping of the legal Controller 5 | %% requests based on the actually files with names ending in '_controller' 6 | %% in the src directory. For example, if you created two controllers: 7 | %% 8 | %% 'feed_controller' and 'login_controller' 9 | %% 10 | %% the mapping: 11 | %% 12 | %% feed -> feed_controller 13 | %% 14 | %% login -> login_controller 15 | %% 16 | %% is created. This prevents creating an atom() for every incoming 17 | %% request which could lead to a potential DoS attack by filling the global 18 | %% atom() table. 19 | %% 20 | -module(beepbeep_router). 21 | -author('Dave Bryson '). 22 | 23 | -behaviour(gen_server). 24 | 25 | %% API 26 | -export([start/1,get_controller/1,controller_map/0]). 27 | 28 | %% gen_server callbacks 29 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, 30 | terminate/2, code_change/3]). 31 | 32 | %% 33 | %% @doc Start the app with the Basedir of the application. 34 | %% Basedir is determined in the supervisor 35 | %% 36 | start(BaseDir) -> 37 | gen_server:start_link({local, ?MODULE}, ?MODULE,BaseDir,[]). 38 | 39 | %% 40 | %% @doc Given the name of the controller in the URL 41 | %% return the module name in the mapping 42 | %% 43 | get_controller(Controller) -> 44 | gen_server:call(?MODULE,{get_controller,Controller}). 45 | 46 | %% 47 | %% @doc Simple helper to view the name-controller mapping 48 | %% 49 | controller_map() -> 50 | gen_server:call(?MODULE,view). 51 | 52 | %% @hidden 53 | init(BaseDir) -> 54 | {ok, load_controllers(BaseDir)}. 55 | 56 | %% ---------Callbacks------------------------------ 57 | 58 | %% @hidden 59 | handle_call({get_controller,Controller},_From, State) -> 60 | Reply = case lists:keysearch(Controller,1,State) of 61 | {value,{_,C}} -> 62 | {ok,C}; 63 | false -> 64 | no_controller 65 | end, 66 | {reply, Reply, State}; 67 | 68 | %% @hidden 69 | handle_call(view,_From,State) -> 70 | error_logger:info_msg("Controller Map:~n~p~n",[State]), 71 | {reply,ok,State}. 72 | 73 | %% @hidden 74 | handle_cast(_Msg, State) -> 75 | {noreply, State}. 76 | %% @hidden 77 | handle_info(_Info, State) -> 78 | {noreply, State}. 79 | %% @hidden 80 | terminate(_Reason, _State) -> 81 | ok. 82 | %% @hidden 83 | code_change(_OldVsn, State, _Extra) -> 84 | {ok, State}. 85 | 86 | %%-------------------------------------------------------------------- 87 | %%% Internal functions 88 | %%-------------------------------------------------------------------- 89 | list_controllers(BaseDir) -> 90 | Path = filename:join([BaseDir,"src","*_controller.erl"]), 91 | filelib:wildcard(Path). 92 | 93 | load_controllers(BaseDir) -> 94 | lists:foldl(fun(File,Acc) -> 95 | OrgName = filename:basename(File,".erl"), 96 | {ok,KeyName,_} = regexp:gsub(OrgName,"_controller",""), 97 | AtomName = list_to_atom(OrgName), 98 | [{KeyName,AtomName}|Acc] 99 | end, 100 | [], 101 | list_controllers(BaseDir)). 102 | 103 | 104 | -------------------------------------------------------------------------------- /src/beepbeep_session_server.erl: -------------------------------------------------------------------------------- 1 | %% @author Dave Bryson [http://weblog.miceda.org] 2 | %% @copyright Dave Bryson 2008-2009 3 | %% @hidden 4 | %%------------------------------------------------------------------- 5 | %% Description : Maintains session information for the client. All data is stored 6 | %% on the server. Only a unique session id is exchanged with the client. 7 | %% Inspired by the Yaws Session Server. 8 | %% 9 | %%------------------------------------------------------------------- 10 | -module(beepbeep_session_server). 11 | -author('Dave Bryson '). 12 | 13 | 14 | -behaviour(gen_server). 15 | 16 | -export([start/0,new_session/1,get_session_data/1,set_session_data/3,delete_session/1,remove_session_data/2]). 17 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, 18 | terminate/2, code_change/3]). 19 | 20 | 21 | -record(beep_session, {sid,data,ttl}). 22 | 23 | 24 | %%% API 25 | start() -> 26 | gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). 27 | 28 | init([]) -> 29 | ets:new(?MODULE,[set,named_table,{keypos, 2}]), 30 | {A1, A2, A3} = now(), 31 | random:seed(A1,A2,A3), 32 | {ok, undefined}. 33 | 34 | new_session(Data) -> 35 | gen_server:call(?MODULE,{new_session,Data}). 36 | 37 | get_session_data(Sid) -> 38 | gen_server:call(?MODULE,{get_session_data,Sid}). 39 | 40 | set_session_data(Sid,Key,Value) -> 41 | gen_server:call(?MODULE,{set_session_data,Sid,Key,Value}). 42 | 43 | delete_session(Sid) -> 44 | gen_server:call(?MODULE,{delete_session,Sid}). 45 | 46 | remove_session_data(Sid,Key) -> 47 | gen_server:call(?MODULE,{remove_session_data,Sid,Key}). 48 | 49 | 50 | %%% Callbacks 51 | handle_call({new_session,Cookie}, _From, _State) -> 52 | NewId = case Cookie of 53 | undefined -> 54 | make_session(); 55 | Any -> 56 | case ets:member(?MODULE, Any) of 57 | true -> Any; 58 | false -> make_session() 59 | end 60 | end, 61 | {reply,NewId,undefined}; 62 | 63 | handle_call({get_session_data,Sid},_From,_State) -> 64 | Data = case ets:lookup(?MODULE, Sid) of 65 | [S] -> 66 | S#beep_session.data; 67 | [] -> 68 | [] 69 | end, 70 | {reply,Data,undefined}; 71 | 72 | handle_call({set_session_data,Sid,Key,Value},_From,_State) -> 73 | Data = case ets:lookup(?MODULE,Sid) of 74 | [S] -> 75 | S#beep_session.data; 76 | [] -> [] 77 | end, 78 | Data1 = case proplists:is_defined(Key,Data) of 79 | true -> 80 | Rest = proplists:delete(Key,Data), 81 | [{Key,Value}|Rest]; 82 | false -> 83 | [{Key,Value}|Data] 84 | end, 85 | 86 | ets:insert(?MODULE,#beep_session{sid=Sid,data=Data1,ttl=0}), 87 | 88 | {reply,ok,undefined}; 89 | 90 | 91 | handle_call({delete_session,Sid},_From,_State) -> 92 | ets:delete(?MODULE,Sid), 93 | {reply,ok,undefined}; 94 | 95 | 96 | handle_call({remove_session_data,Sid,Key},_From,_State) -> 97 | Data = case ets:lookup(?MODULE,Sid) of 98 | [S] -> 99 | S#beep_session.data; 100 | [] -> [] 101 | end, 102 | Data1 = case proplists:is_defined(Key,Data) of 103 | true -> 104 | proplists:delete(Key,Data); 105 | false -> 106 | Data 107 | end, 108 | 109 | ets:insert(?MODULE,#beep_session{sid=Sid,data=Data1,ttl=0}), 110 | 111 | {reply,ok,undefined}. 112 | 113 | 114 | handle_cast(_Msg, State) -> 115 | {noreply, State}. 116 | 117 | handle_info(_Info, State) -> 118 | {noreply, State}. 119 | 120 | terminate(_Reason, _State) -> 121 | ok. 122 | 123 | code_change(_OldVsn, State, _Extra) -> 124 | {ok, State}. 125 | 126 | %%-------------------------------------------------------------------- 127 | %%% Internal functions 128 | %%-------------------------------------------------------------------- 129 | 130 | make_session() -> 131 | Data = crypto:rand_bytes(2048), 132 | Sha_list = binary_to_list(crypto:sha(Data)), 133 | Id = lists:flatten(list_to_hex(Sha_list)), 134 | Session = #beep_session{sid=Id,data=[],ttl=0}, 135 | ets:insert(?MODULE,Session), 136 | Id. 137 | 138 | %% Convert Integer from the SHA to Hex 139 | list_to_hex(L)-> 140 | lists:map(fun(X) -> int_to_hex(X) end, L). 141 | 142 | int_to_hex(N) when N < 256 -> 143 | [hex(N div 16), hex(N rem 16)]. 144 | 145 | hex(N) when N < 10 -> 146 | $0+N; 147 | hex(N) when N >= 10, N < 16 -> 148 | $a + (N-10). 149 | -------------------------------------------------------------------------------- /src/beepbeep_skel.erl: -------------------------------------------------------------------------------- 1 | %% This is a direct copy of mochiweb_skel I've only changed a few 2 | %% things for my purposes 3 | %% @hidden 4 | 5 | -module(beepbeep_skel). 6 | -export([skelcopy/2]). 7 | -include_lib("kernel/include/file.hrl"). 8 | 9 | %% External API 10 | 11 | skelcopy(DestDir, InName) -> 12 | Name = string:to_lower(InName), 13 | ok = ensuredir(DestDir), 14 | LDst = case length(filename:dirname(DestDir)) of 15 | 1 -> %% handle case when dirname returns "/" 16 | 0; 17 | N -> 18 | N + 1 19 | end, 20 | skelcopy(src(), DestDir, Name, LDst), 21 | % 22 | case os:type() of 23 | {win32,_} -> 24 | {ok, Cwd} = file:get_cwd(), 25 | mk_win_dir_syslink(Name, "beepbeep-src", DestDir, Cwd ++ "/../"), 26 | mk_win_dir_syslink(Name, "erlydtl-src", DestDir, Cwd ++ "/../deps/erlydtl"), 27 | mk_win_dir_syslink(Name, "mochiweb-src", DestDir, Cwd ++ "/../deps/mochiweb"), 28 | mk_bat_file(Name, DestDir); 29 | {unix,_} -> 30 | ok = file:make_symlink( 31 | filename:join(filename:dirname(code:which(?MODULE)), ".."), 32 | filename:join([DestDir, Name, "deps", "beepbeep-src"])), 33 | ok = file:make_symlink( 34 | filename:join(filename:dirname(code:which(?MODULE)), "../deps/erlydtl"), 35 | filename:join([DestDir, Name, "deps", "erlydtl-src"])), 36 | ok = file:make_symlink( 37 | filename:join(filename:dirname(code:which(?MODULE)), "../deps/mochiweb"), 38 | filename:join([DestDir, Name, "deps", "mochiweb-src"])) 39 | end. 40 | 41 | %% Internal API 42 | 43 | %% @doc Make symbolik link in current directory on windows vista or highter 44 | mk_win_dir_syslink(ProjectName, LinkName, DestDir, LinkTarget) -> 45 | S = (list_to_atom("cd "++ filename:join([DestDir, ProjectName, "deps"]) ++ "& mklink /D " ++ LinkName ++ " \"" ++ LinkTarget ++ "\"")), 46 | os:cmd(S), 47 | %io:format("~nname:~p~ntarget:~p~n~n", [LinkName, DestTarget]), 48 | %io:format("~n~p~n", [S]), 49 | ok. 50 | 51 | %% @doc make .bat file to start dev server on windows 52 | mk_bat_file(ProjectName, DestDir) -> 53 | Name = "start-server.bat", 54 | Content = "make \n" 55 | "start werl -pa ebin deps/*/ebin -boot start_sasl -s " ++ ProjectName ++ " -s reloader", 56 | file:set_cwd(DestDir ++ "//" ++ ProjectName), 57 | {ok, IODevice} = file:open(Name, [write]), file:write(IODevice, Content), file:close(IODevice), 58 | ok. 59 | 60 | src() -> 61 | Dir = filename:dirname(code:which(?MODULE)), 62 | filename:join(Dir, "../priv/skel"). 63 | 64 | skel() -> 65 | "skel". 66 | 67 | skelcopy(Src, DestDir, Name, LDst) -> 68 | {ok, Dest, _} = regexp:gsub(filename:basename(Src), skel(), Name), 69 | case file:read_file_info(Src) of 70 | {ok, #file_info{type=directory, mode=Mode}} -> 71 | Dir = DestDir ++ "/" ++ Dest, 72 | EDst = lists:nthtail(LDst, Dir), 73 | ok = ensuredir(Dir), 74 | ok = file:write_file_info(Dir, #file_info{mode=Mode}), 75 | {ok, Files} = file:list_dir(Src), 76 | io:format("~s/~n", [EDst]), 77 | lists:foreach(fun ("." ++ _) -> ok; 78 | (F) -> 79 | skelcopy(filename:join(Src, F), 80 | Dir, 81 | Name, 82 | LDst) 83 | end, 84 | Files), 85 | ok; 86 | {ok, #file_info{type=regular, mode=Mode}} -> 87 | OutFile = filename:join(DestDir, Dest), 88 | {ok, B} = file:read_file(Src), 89 | {ok, S, _} = regexp:gsub(binary_to_list(B), skel(), Name), 90 | ok = file:write_file(OutFile, list_to_binary(S)), 91 | ok = file:write_file_info(OutFile, #file_info{mode=Mode}), 92 | io:format(" ~s~n", [filename:basename(Src)]), 93 | ok; 94 | {ok, _} -> 95 | io:format("ignored source file: ~p~n", [Src]), 96 | ok; 97 | {error, _} -> 98 | io:format("problem with ~p~n",[Src]), 99 | ok 100 | end. 101 | 102 | ensuredir(Dir) -> 103 | case file:make_dir(Dir) of 104 | ok -> 105 | ok; 106 | {error, eexist} -> 107 | ok; 108 | E -> 109 | E 110 | end. 111 | -------------------------------------------------------------------------------- /src/mochiweb_env.erl: -------------------------------------------------------------------------------- 1 | %% @doc This code is adapted from the ewgi project: 2 | %% http://code.google.com/p/ewgi/ 3 | %% In the future BeepBeep may use the ewgi interface 4 | %% to support both mochiweb and yaws 5 | %% @hidden 6 | -module(mochiweb_env). 7 | 8 | -export([setup_environment/1]). 9 | 10 | -include("beepbeep.hrl"). 11 | 12 | setup_environment(Req) -> 13 | parse_request(Req). 14 | 15 | nhdr(L) when is_atom(L) -> 16 | nhdr(atom_to_list(L)); 17 | nhdr(L) when is_binary(L) -> 18 | nhdr(binary_to_list(L)); 19 | nhdr(L) when is_list(L) -> 20 | underscoreize(L, []). 21 | 22 | underscoreize([], S) -> 23 | lists:reverse(S); 24 | underscoreize([$-|T], S) -> 25 | underscoreize(T, [$_|S]); 26 | underscoreize([H|T], S) -> 27 | underscoreize(T, [H|S]). 28 | 29 | normalize_header({K, V}) -> 30 | {string:to_upper(string:strip(nhdr(K))), string:strip(V)}. 31 | 32 | parse_request(Req) -> 33 | Hdrs = parse_headers(Req), 34 | lists:foldl(fun({El, ElName}, PList) -> 35 | V = parse_element(El, Req), 36 | case V of 37 | undefined -> PList; 38 | V -> 39 | NewEl = proplists:property({ElName, V}), 40 | [NewEl|PList] 41 | end 42 | end, Hdrs, ?BEEPBEEP_ENV_DATA). 43 | 44 | parse_element(server_sw, _Req) -> 45 | "MochiWeb"; 46 | parse_element(server_name, Req) -> 47 | HostPort = Req:get_header_value(host), 48 | case HostPort of 49 | HostPort when is_list(HostPort) -> 50 | hd(string:tokens(HostPort, ":")); 51 | HostPort -> HostPort 52 | end; 53 | parse_element(server_port, Req) -> 54 | HostPort = Req:get_header_value(host), 55 | case HostPort of 56 | HostPort when is_list(HostPort) -> 57 | case length(HostPort) of 58 | 2 -> lists:nth(2, HostPort); 59 | _ -> undefined 60 | end; 61 | _ -> 62 | undefined 63 | end; 64 | parse_element(server_protocol, Req) -> 65 | {Maj, Min} = Req:get(version), 66 | lists:flatten(io_lib:format("HTTP/~b.~b", [Maj, Min])); 67 | parse_element(method, Req) -> 68 | Req:get(method); 69 | parse_element(path_info,Req) -> 70 | Req:get(path); 71 | parse_element(remote_addr, Req) -> 72 | Req:get(peer); 73 | parse_element(beepbeep_params,Req) -> 74 | case Req:get(method) of 75 | Method when Method =:= 'GET'; Method =:= 'HEAD' -> 76 | Req:parse_qs(); 77 | _ -> 78 | Req:parse_post() 79 | end; 80 | parse_element(content_type, Req) -> 81 | Req:get_header_value("content-type"); 82 | parse_element(content_length, Req) -> 83 | case Req:get_header_value("content-length") of 84 | undefined -> undefined; 85 | Length when is_integer(Length) -> 86 | Length; 87 | Length when is_list(Length) -> 88 | list_to_integer(Length) 89 | end. 90 | 91 | parse_headers(Req) -> 92 | Hdrs = Req:get(headers), 93 | lists:foldl(fun(Pair, Acc) -> 94 | {K1, V1} = normalize_header(Pair), 95 | %% Don't duplicate content-length and content-type 96 | case K1 of 97 | "CONTENT_LENGTH" -> 98 | Acc; 99 | "CONTENT_TYPE" -> 100 | Acc; 101 | K1 -> 102 | [{lists:append(["HTTP_", K1]), V1}|Acc] 103 | end 104 | end, 105 | [], 106 | mochiweb_headers:to_list(Hdrs)). 107 | 108 | 109 | -------------------------------------------------------------------------------- /support/include.mk: -------------------------------------------------------------------------------- 1 | ## -*- makefile -*- 2 | 3 | ###################################################################### 4 | ## Erlang 5 | 6 | ERL := erl 7 | ERLC := $(ERL)c 8 | 9 | INCLUDE_DIRS := ../include $(wildcard ../deps/*/include) 10 | EBIN_DIRS := $(wildcard ../deps/*/ebin) 11 | ERLC_FLAGS := -W $(INCLUDE_DIRS:../%=-I ../%) $(EBIN_DIRS:%=-pa %) 12 | 13 | ifndef no_debug_info 14 | ERLC_FLAGS += +debug_info 15 | endif 16 | 17 | ifdef debug 18 | ERLC_FLAGS += -Ddebug 19 | endif 20 | 21 | EBIN_DIR := ../ebin 22 | DOC_DIR := ../doc 23 | EMULATOR := beam 24 | 25 | ERL_SOURCES := $(wildcard *.erl) 26 | ERL_HEADERS := $(wildcard *.hrl) $(wildcard ../include/*.hrl) 27 | ERL_OBJECTS := $(ERL_SOURCES:%.erl=$(EBIN_DIR)/%.$(EMULATOR)) 28 | ERL_DOCUMENTS := $(ERL_SOURCES:%.erl=$(DOC_DIR)/%.html) 29 | ERL_OBJECTS_LOCAL := $(ERL_SOURCES:%.erl=./%.$(EMULATOR)) 30 | APP_FILES := $(wildcard *.app) 31 | EBIN_FILES = $(ERL_OBJECTS) $(ERL_DOCUMENTS) $(APP_FILES:%.app=../ebin/%.app) 32 | EBIN_FILES_NO_DOCS = $(ERL_OBJECTS) $(APP_FILES:%.app=../ebin/%.app) 33 | MODULES = $(ERL_SOURCES:%.erl=%) 34 | 35 | ../ebin/%.app: %.app 36 | cp $< $@ 37 | 38 | $(EBIN_DIR)/%.$(EMULATOR): %.erl 39 | $(ERLC) $(ERLC_FLAGS) -o $(EBIN_DIR) $< 40 | 41 | ./%.$(EMULATOR): %.erl 42 | $(ERLC) $(ERLC_FLAGS) -o . $< 43 | 44 | $(DOC_DIR)/%.html: %.erl 45 | $(ERL) -noshell -run edoc file $< -run init stop 46 | mv *.html $(DOC_DIR) 47 | --------------------------------------------------------------------------------