1 
2     ////////////////////////////////////////////////////////////////////////////////////////////
3     //  Copyright (c) 2012 Christopher Nicholson-Sauls                                        //
4     //                                                                                        //
5     //  Permission is hereby granted, free of charge, to any person obtaining a copy of this  //
6     //  software and associated documentation files (the "Software"), to deal in the          //
7     //  Software without restriction, including without limitation the rights to use, copy,   //
8     //  modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,   //
9     //  and to permit persons to whom the Software is furnished to do so, subject to the      //
10     //  following conditions:                                                                 //
11     //                                                                                        //
12     //  The above copyright notice and this permission notice shall be included in all        //
13     //  copies or substantial portions of the Software.                                       //
14     //                                                                                        //
15     //  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,   //
16     //  INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A         //
17     //  PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT    //
18     //  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF  //
19     //  CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE  //
20     //  OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                                         //
21     ////////////////////////////////////////////////////////////////////////////////////////////
22 
23 /**
24  *
25  */
26 module zeal.http.router;
27 
28 import std.metastrings;
29 import std.string;
30 
31 import vibe.core.log;
32 
33 import vibe.http.fileserver;
34 import vibe.http.router;
35 import vibe.http.server;
36 
37 import zeal.config;
38 import zeal.inflector;
39 
40 import zeal.base.controller;
41 
42 import zeal.utils.singleton;
43 import zeal.utils.tuple;
44 
45 import sass;
46 
47 mixin ConfigImports;
48 
49 
50 /**
51  *
52  */
53 class ZealRouter : UrlRouter {
54 	mixin Singleton;
55 	
56 	protected this () {
57 		foreach ( _R; ArrayTuple!( ZealConfig!`resources` ) ) {
58 			resource!( _R );
59 		}
60 		routeAssets();
61 	}
62 
63 	/**
64 	 *
65 	 */
66 	final @property typeof( this ) match ( string _Path, string _C_A, string _Via_ = "any" ) () 
67 	if ( _Via_ == "delete" || _Via_ == "delete_" || _Via_ == "get" || _Via_ == "post" || _Via_ == "put" || _Via_ == "any" ) {
68 		enum _Module		= _C_A.parentize();
69 		enum _Controller	= _Module.controllerize();
70 		enum _Action		= _C_A.childize();
71 		enum _Via			= _Via_ ~ ( _Via_ == "delete" ? "_" : "" );
72 		
73 		mixin(Format!(
74 			q{
75 				import controllers.%s;
76 				auto cb = %s().action!`%s`;
77 				if ( cb == null ) {
78 					throw new Exception( "Attempted to add route to nonexistant action: %s -> %s" );
79 				}
80 				%s( "%s", cb );
81 			},
82 			_Module,
83 			_Controller, _Action,
84 			_Path, _C_A,
85 			_Via, _Path
86 		));
87 		return this;
88 	}
89 	
90 	/**
91 	 *
92 	 */
93 	final @property typeof( this ) resource ( _C : Controller ) ( _C c ) {
94 		enum _Base	= "/" ~ _C.stringof[ 0 .. $ - 10 ].decamelize();
95 		enum _New	= _Base ~ "/new";
96 		enum _ID 	= _Base ~ "/:id";
97 		enum _Edit	= _ID ~ "/edit";
98 		
99 		if ( auto a = c.action!`new`     ) get    ( _New , a );
100 		if ( auto a = c.action!`create`  ) post   ( _Base, a );
101 		if ( auto a = c.action!`index`   ) get    ( _Base, a );
102 		if ( auto a = c.action!`show`    ) get    ( _ID  , a );
103 		if ( auto a = c.action!`edit`    ) get    ( _Edit, a );
104 		if ( auto a = c.action!`update`  ) put    ( _ID  , a );
105 		if ( auto a = c.action!`destroy` ) delete_( _ID  , a );
106 		
107 		static if ( is( typeof( c.route( this ) ) ) ) {
108 			c.route( this );
109 		}
110 		
111 		logInfo( "ZealRouter: added resource: %s.", _Base[ 1 .. $ ] );
112 		return this;
113 	}
114 
115 	///ditto
116 	final @property typeof( this ) resource ( string _R ) () {
117 		enum _Module		= "controllers." ~ _R;
118 		enum _Controller	= _R.controllerize();
119 		
120 		mixin(Format!(
121 			q{
122 				import %s;
123 				resource!%s = %s();
124 			},
125 			_Module,
126 			_Controller, _Controller
127 		));
128 		return this;
129 	}
130 	
131 	/**
132 	 *
133 	 */
134 	final @property typeof( this ) root ( string _Via = "any", _Dummy = void ) ( Controller.Action cb ) {
135 		mixin(Format!(
136 			q{
137 				%s( "/", cb );
138 			},
139 			_Via
140 		));
141 		return this;
142 	}
143 
144 	///ditto
145 	final @property typeof( this ) root ( string _C_A, string _Via = "any" ) () {
146 		match!( "/", _C_A, _Via );
147 		return this;
148 	}
149 	
150 	/**
151 	 *
152 	 */
153 	final typeof( this ) routeAssets () {
154 		static done = false;
155 		
156 		if ( !done ) {
157 			auto assets = ZealConfig!`assets`;
158 			
159 			auto dir = assets ~ `styles/`;
160 			foreach ( style; ZealConfig!`styles` ) {
161 				compileSass( dir, style );
162 			}
163 			
164 			get( `*`, serveStaticFiles( ZealConfig!`assets` ) );
165 			
166 			done = true;
167 		}
168 		return this;
169 	}
170 	
171 } // end class ZealRouter
172 
173 
174 /**
175  *
176  */
177 private mixin template ConfigImports ( alias _List ) {
178 	static if ( _List.length > 0 ) {
179 		mixin(Format!(
180 			`import controllers.%s;`,
181 			_List[ 0 ]
182 		));
183 		mixin ConfigImports!( _List[ 1 .. $ ] );
184 	}
185 }
186 
187 private mixin template ConfigImports () {
188 	mixin ConfigImports!( ZealConfig!`resources` );
189 }