plezi 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/CHANGELOG.md +450 -0
- data/Gemfile +4 -0
- data/KNOWN_ISSUES.md +13 -0
- data/LICENSE.txt +22 -0
- data/README.md +341 -0
- data/Rakefile +2 -0
- data/TODO.md +19 -0
- data/bin/plezi +301 -0
- data/lib/plezi.rb +125 -0
- data/lib/plezi/base/cache.rb +77 -0
- data/lib/plezi/base/connections.rb +33 -0
- data/lib/plezi/base/dsl.rb +177 -0
- data/lib/plezi/base/engine.rb +85 -0
- data/lib/plezi/base/events.rb +84 -0
- data/lib/plezi/base/io_reactor.rb +41 -0
- data/lib/plezi/base/logging.rb +62 -0
- data/lib/plezi/base/rack_app.rb +89 -0
- data/lib/plezi/base/services.rb +57 -0
- data/lib/plezi/base/timers.rb +71 -0
- data/lib/plezi/handlers/controller_magic.rb +383 -0
- data/lib/plezi/handlers/http_echo.rb +27 -0
- data/lib/plezi/handlers/http_host.rb +215 -0
- data/lib/plezi/handlers/http_router.rb +69 -0
- data/lib/plezi/handlers/magic_helpers.rb +43 -0
- data/lib/plezi/handlers/route.rb +272 -0
- data/lib/plezi/handlers/stubs.rb +143 -0
- data/lib/plezi/server/README.md +33 -0
- data/lib/plezi/server/helpers/http.rb +169 -0
- data/lib/plezi/server/helpers/mime_types.rb +999 -0
- data/lib/plezi/server/protocols/http_protocol.rb +318 -0
- data/lib/plezi/server/protocols/http_request.rb +133 -0
- data/lib/plezi/server/protocols/http_response.rb +294 -0
- data/lib/plezi/server/protocols/websocket.rb +208 -0
- data/lib/plezi/server/protocols/ws_response.rb +92 -0
- data/lib/plezi/server/services/basic_service.rb +224 -0
- data/lib/plezi/server/services/no_service.rb +196 -0
- data/lib/plezi/server/services/ssl_service.rb +193 -0
- data/lib/plezi/version.rb +3 -0
- data/plezi.gemspec +26 -0
- data/resources/404.erb +68 -0
- data/resources/404.haml +64 -0
- data/resources/404.html +67 -0
- data/resources/404.slim +63 -0
- data/resources/500.erb +68 -0
- data/resources/500.haml +63 -0
- data/resources/500.html +67 -0
- data/resources/500.slim +63 -0
- data/resources/Gemfile +85 -0
- data/resources/anorexic_gray.png +0 -0
- data/resources/anorexic_websockets.html +47 -0
- data/resources/code.rb +8 -0
- data/resources/config.ru +39 -0
- data/resources/controller.rb +139 -0
- data/resources/db_ac_config.rb +58 -0
- data/resources/db_dm_config.rb +51 -0
- data/resources/db_sequel_config.rb +42 -0
- data/resources/en.yml +204 -0
- data/resources/environment.rb +41 -0
- data/resources/haml_config.rb +6 -0
- data/resources/i18n_config.rb +14 -0
- data/resources/rakefile.rb +22 -0
- data/resources/redis_config.rb +35 -0
- data/resources/routes.rb +26 -0
- data/resources/welcome_page.html +72 -0
- data/websocket chatroom.md +639 -0
- metadata +141 -0
data/Gemfile
ADDED
data/KNOWN_ISSUES.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Known Issues
|
2
|
+
|
3
|
+
Here we will list known issues and weather or not a solution is being persued.
|
4
|
+
|
5
|
+
## Caching?
|
6
|
+
|
7
|
+
seems caching sometimes fails ( data isn't cached / cache keeps reloading)...?
|
8
|
+
|
9
|
+
## Chuncked data issues? or Apache benchmarks bug?
|
10
|
+
|
11
|
+
the Apache benchmark hangs some requests, when chuncked data and missing mimetypes are introduced...
|
12
|
+
|
13
|
+
is this a server or benchmark error?
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Myst
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,341 @@
|
|
1
|
+
# Plezi, The Ruby Websocket and HTTP Framework
|
2
|
+
[![Gem Version](https://badge.fury.io/rb/plezi.svg)](http://badge.fury.io/rb/plezi)
|
3
|
+
[![Inline docs](http://inch-ci.org/github/boazsegev/plezi.svg?branch=master)](http://inch-ci.org/github/boazsegev/plezi)
|
4
|
+
|
5
|
+
> People who are serious about their frameworks, should make their own servers...
|
6
|
+
|
7
|
+
_(if to para-phrase "People who are serious about their software, should make their own hardware.")_
|
8
|
+
|
9
|
+
## About the Plezi framework \ server
|
10
|
+
|
11
|
+
Plezi is an easy to use Ruby Websocket Framework, with full RESTful routing support and HTTP streaming support. It's name comes from the word "fun" in Haitian, since Plezi is really fun to work with and it keeps our code clean and streamlined.
|
12
|
+
|
13
|
+
Plezi works as an asynchronous multi-threaded Ruby alternative to a Rack/Rails/Sintra/Faye/EM-Websockets combo. It's also great as an alternative to socket.io, allowing for both websockets and long pulling.
|
14
|
+
|
15
|
+
Plezi contains an object-oriented server, divided into parts that can be changed/updated and removed easily and dynamically. This allows - much like Node.js - native WebSocket support (and, if you would like to write your own Protocol or Handler, native SMPT or any other custom protocol you might wish to implement).
|
16
|
+
|
17
|
+
You can follow our [tutorial to write your first Plezi Chatroom](http://boazsegev.github.io/plezi/websockets.html) - but it's better to start with this readme and explore the WebSockets example given here.
|
18
|
+
|
19
|
+
## Installation
|
20
|
+
|
21
|
+
Add this line to your application's Gemfile:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
gem 'plezi'
|
25
|
+
```
|
26
|
+
Or install it yourself as:
|
27
|
+
|
28
|
+
$ gem install plezi
|
29
|
+
|
30
|
+
## Creating an Plezi Application
|
31
|
+
|
32
|
+
to create a new barebones app using the Plezi framework, run from terminal:
|
33
|
+
|
34
|
+
$ plezi new appname
|
35
|
+
|
36
|
+
That's it, now you have a ready to use basic web server (with some demo code), just run it:
|
37
|
+
|
38
|
+
$ cd appname
|
39
|
+
$ ./appname.rb # ( or: plezi s )
|
40
|
+
|
41
|
+
now go, in your browser, to: [http://localhost:3000/](http://localhost:3000/)
|
42
|
+
|
43
|
+
the default first port for the app is 3000. you can set the first port to listen to by using the `-p ` option (make sure you have permissions for the requested port):
|
44
|
+
|
45
|
+
$ ./appname.rb -p 80
|
46
|
+
|
47
|
+
you now have a smart framework app that will happily eat any gem you feed it. it responds extra well to Haml, Sass and Coffee-Script, which you can enable in it's Gemfile.
|
48
|
+
|
49
|
+
## Barebones Web Service
|
50
|
+
|
51
|
+
this example is basic, useless, but required for every doc out there...
|
52
|
+
|
53
|
+
"Hello World!" in 3 lines - try it in irb (exit irb to start server):
|
54
|
+
|
55
|
+
require 'plezi'
|
56
|
+
listen
|
57
|
+
route(/.?/) { |req, res| res << "Hello World!" }
|
58
|
+
|
59
|
+
After you exited irb, the Plezi server started up. go to http://localhost:3000/ and see it run :)
|
60
|
+
|
61
|
+
## Plezi Controller classes
|
62
|
+
|
63
|
+
One of the best things about the Plezi is it's ability to take in any class as a controller class and route to the classes methods with special support for RESTful methods (`index`, `show`, `new`, `save`, `update`, `delete`, `before` and `after`) and for WebSockets (`pre_connect`, `on_connect`, `on_message(data)`, `on_disconnect`, `broadcast`, `collect`):
|
64
|
+
|
65
|
+
require 'plezi'
|
66
|
+
|
67
|
+
class Controller
|
68
|
+
def index
|
69
|
+
"Hello World!"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
listen
|
74
|
+
route '*' , Controller
|
75
|
+
|
76
|
+
Except for WebSockets, returning a String will automatically add the string to the response before sending the response - which makes for cleaner code. It's also possible to send the response as it is (by returning true).
|
77
|
+
|
78
|
+
Controllers can even be nested (order matters) or have advanced uses that are definitly worth exploring.
|
79
|
+
|
80
|
+
**please read the demo code for Plezi::StubRESTCtrl and Plezi::StubWSCtrl to learn more.**
|
81
|
+
|
82
|
+
## Native Websocket and Radis support
|
83
|
+
|
84
|
+
Plezi Controllers have access to native websocket support through the `pre_connect`, `on_connect`, `on_message(data)`, `on_disconnect`, `broadcast` and `collect` methods.
|
85
|
+
|
86
|
+
Here is some demo code for a simple Websocket broadcasting server, where messages sent to the server will be broadcasted back to all the **other** active connections (the connection sending the message will not recieve the broadcast).
|
87
|
+
|
88
|
+
As a client side, we will use the WebSockets echo demo page - we will simply put in ws://localhost:3000/ as the server, instead of the default websocket server (ws://echo.websocket.org).
|
89
|
+
|
90
|
+
Remember to connect to the service from at least two browser windows - to truly experience the `broadcast`ed websocket messages.
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
require 'plezi'
|
94
|
+
|
95
|
+
# do you need automated redis support?
|
96
|
+
# require 'redis'
|
97
|
+
# ENV['PL_REDIS_URL'] = "http://user:password@localhost:6379"
|
98
|
+
|
99
|
+
class BroadcastCtrl
|
100
|
+
def index
|
101
|
+
redirect_to 'http://www.websocket.org/echo.html'
|
102
|
+
end
|
103
|
+
def on_message data
|
104
|
+
# try replacing the following two lines are with:
|
105
|
+
# self.class.broadcast :_send_message, data
|
106
|
+
broadcast :_send_message, data
|
107
|
+
response << "sent."
|
108
|
+
end
|
109
|
+
def _send_message data
|
110
|
+
response << data
|
111
|
+
end
|
112
|
+
def people
|
113
|
+
'I made this :)'
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
listen
|
118
|
+
|
119
|
+
route '/', BroadcastCtrl
|
120
|
+
```
|
121
|
+
|
122
|
+
method names starting with an underscore ('_') will NOT be made public by the router: so while '/people' is public ( [try it](http://localhost:3000/people) ), '/_send_message' will return a 404 not found error ( [try it](http://localhost:3000/_send_message) ).
|
123
|
+
|
124
|
+
## Native HTTP streaming with Asynchronous events
|
125
|
+
|
126
|
+
Plezi comes with native HTTP streaming support, alowing you to use Plezi Events and Timers to send an Asynchronous response.
|
127
|
+
|
128
|
+
Let's make the classic 'Hello World' use HTTP Streaming and Asynchronous Plezi Events:
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
require 'plezi'
|
132
|
+
|
133
|
+
class Controller
|
134
|
+
def index
|
135
|
+
response.start_http_streaming
|
136
|
+
PL.callback(response, :send, "Hello World") { response.finish }
|
137
|
+
true
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
listen
|
142
|
+
route '*' , Controller
|
143
|
+
```
|
144
|
+
|
145
|
+
Notice the easy use of Asynchronous Events using the PL#callback method. The optional block passed to this method (`response.finish`) will be executed only after the asynchronous call for the response#send method with the "Hello World" argument has completed.
|
146
|
+
|
147
|
+
More on asynchronous events and timers later.
|
148
|
+
|
149
|
+
## Plezi Routes
|
150
|
+
|
151
|
+
Plezi supports magic routes, in similar formats found in other systems, such as: `route "/:required/(:optional_with_format){[\\d]*}/(:optional)", Plezi::StubRESTCtrl`.
|
152
|
+
|
153
|
+
Plezi assummes all simple string routes to be RESTful routes woth the parameter `:id` ( `"/user" == "/user/(:id)"` ).
|
154
|
+
|
155
|
+
require 'plezi'
|
156
|
+
listen
|
157
|
+
|
158
|
+
# this route demos a route for listing/showing posts,
|
159
|
+
# with or without revision numbers or page-control....
|
160
|
+
# notice the single quotes (otherwise the '\' would need to be escaped).
|
161
|
+
route '/post/(:id)/(:revision){[\d]+\.[\d]+}/(:page_number)', Plezi::StubRESTCtrl
|
162
|
+
|
163
|
+
now visit:
|
164
|
+
|
165
|
+
* [http://localhost:3000/post/12/1.3/1](http://localhost:3000/post/12/1.3/1)
|
166
|
+
* [http://localhost:3000/post/12/1](http://localhost:3000/post/12/1)
|
167
|
+
|
168
|
+
**please see the `route` documentation for more information on routes**.
|
169
|
+
|
170
|
+
## Plezi Virtual Hosts
|
171
|
+
|
172
|
+
Plezi can be used to create virtual hosts for the same service:
|
173
|
+
|
174
|
+
require 'plezi'
|
175
|
+
listen
|
176
|
+
host 'localhost', alias: 'localhost2'
|
177
|
+
|
178
|
+
shared_route '/people' do |req, res|
|
179
|
+
res << "we are people - shared by all routes."
|
180
|
+
end
|
181
|
+
|
182
|
+
host
|
183
|
+
|
184
|
+
route('*') do |req, res|
|
185
|
+
res << "this is a 'catch-all' host. you got here by putting in the IP adderess."
|
186
|
+
end
|
187
|
+
|
188
|
+
host 'localhost'
|
189
|
+
|
190
|
+
route('*') do |req, res|
|
191
|
+
res << "this is localhost or localhost 2"
|
192
|
+
end
|
193
|
+
|
194
|
+
Now visit:
|
195
|
+
|
196
|
+
* [http://127.0.0.1:3000/]( http://127.0.0.1:3000/ )
|
197
|
+
* [http://localhost:3000/]( http://localhost:3000/ )
|
198
|
+
* [http://127.0.0.1:3000/people]( http://127.0.0.1:3000/people )
|
199
|
+
* [http://localhost:3000/people]( http://localhost:3000/people )
|
200
|
+
|
201
|
+
## Plezi Logging
|
202
|
+
|
203
|
+
The Plezi module (also `PL`) has methods to help with logging as well as the support you already noticed for dynamic routes, dynamic services and more.
|
204
|
+
|
205
|
+
Logging:
|
206
|
+
|
207
|
+
require 'plezi'
|
208
|
+
|
209
|
+
# simple logging of strings
|
210
|
+
PL.info 'log info'
|
211
|
+
PL.warn 'log warning'
|
212
|
+
PL.error 'log error'
|
213
|
+
PL.fatal "log a fatal error (shuoldn't be needed)."
|
214
|
+
PL.log_raw "Write raw strings to the logger."
|
215
|
+
|
216
|
+
# the logger accepts exceptions as well.
|
217
|
+
begin
|
218
|
+
raise "hell"
|
219
|
+
rescue Exception => e
|
220
|
+
PL.error e
|
221
|
+
end
|
222
|
+
|
223
|
+
## Plezi Events and Timers
|
224
|
+
|
225
|
+
The Plezi module (also `PL`) also has methods to help with asynchronous tasking, callbacks, timers and customized shutdown cleanup.
|
226
|
+
|
227
|
+
Asynchronous callbacks (works only while services are active and running):
|
228
|
+
|
229
|
+
require 'plezi'
|
230
|
+
|
231
|
+
def my_shutdown_proc time_start
|
232
|
+
puts "Services were running for #{Time.now - time_start} ms."
|
233
|
+
end
|
234
|
+
|
235
|
+
# shutdown callbacks
|
236
|
+
PL.on_shutdown(Kernel, :my_shutdown_proc, Time.now) { puts "this will run after shutdown." }
|
237
|
+
PL.on_shutdown() { puts "this will run too." }
|
238
|
+
|
239
|
+
# a timer
|
240
|
+
PL.run_after 2, -> {puts "this will wait 2 seconds to run... too late. for this example"}
|
241
|
+
|
242
|
+
# an asynchronous method call with an optional callback block
|
243
|
+
PL.callback(Kernel, :puts, "Plezi will start eating our code once we exit terminal.") {puts 'first output finished'}
|
244
|
+
|
245
|
+
## Food for thought - advanced controller uses
|
246
|
+
|
247
|
+
Here's some food for thought - code similar to something actually used at some point while developing the applicatio template used by `plezi new myapp`:
|
248
|
+
|
249
|
+
require 'plezi'
|
250
|
+
|
251
|
+
# this controller will re-write the request to extract data,
|
252
|
+
# and then it will fail, so that routing continues.
|
253
|
+
#
|
254
|
+
# this is here just for the demo.
|
255
|
+
#
|
256
|
+
class ReWriteController
|
257
|
+
# using the before filter and regular expressions to make some changes.
|
258
|
+
def before
|
259
|
+
# extract the fr and en locales.
|
260
|
+
result = request.path.match /^\/(en|fr)($|\/.*)/
|
261
|
+
|
262
|
+
if result
|
263
|
+
params[:locale] = result[1]
|
264
|
+
request.path = result[2]
|
265
|
+
end
|
266
|
+
|
267
|
+
# let the routing continue.
|
268
|
+
return false
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
class Controller
|
273
|
+
def index
|
274
|
+
return "Bonjour le monde!" if params[:locale] == 'fr'
|
275
|
+
"Hello World!\n #{params}"
|
276
|
+
end
|
277
|
+
def show
|
278
|
+
return "Vous êtes à la recherche d' : #{params[:id]}" if params[:locale] == 'fr'
|
279
|
+
"You're looking for: #{params[:id]}"
|
280
|
+
end
|
281
|
+
def debug
|
282
|
+
# binding.pry
|
283
|
+
# do you use pry for debuging?
|
284
|
+
# no? oh well, let's ignore this.
|
285
|
+
false
|
286
|
+
end
|
287
|
+
def delete
|
288
|
+
return "Mon Dieu! Mon français est mauvais!" if params[:locale] == 'fr'
|
289
|
+
"did you try /#{params["id"]}/?_method=delete or does your server support a native DELETE method?"
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
listen
|
294
|
+
|
295
|
+
# we run the ReWriteController first, to rewrite the path for all the remaining routes.
|
296
|
+
#
|
297
|
+
# this is here just for the demo...
|
298
|
+
#
|
299
|
+
# ...in this specific case, it is possible to dispense with the ReWriteController class
|
300
|
+
# and write:
|
301
|
+
#
|
302
|
+
# route '/(:locale){fr|en}/*', false
|
303
|
+
#
|
304
|
+
# the false controller acts as a simple path re-write that
|
305
|
+
# deletes everything before the '*' sign (the catch-all).
|
306
|
+
#
|
307
|
+
route '*' , ReWriteController
|
308
|
+
|
309
|
+
# this route takes a regular expression that is a simple math calculation
|
310
|
+
# (calculator)
|
311
|
+
route /^\/[\d\+\-\*\/\(\)\.]+$/ do |request, response|
|
312
|
+
message = (request.params[:locale] == 'fr') ? "La solution est" : "My Answer is"
|
313
|
+
response << "#{message}: #{eval(request.path[1..-1])}"
|
314
|
+
end
|
315
|
+
|
316
|
+
route "/users" , Controller
|
317
|
+
|
318
|
+
route "/" , Controller
|
319
|
+
|
320
|
+
try:
|
321
|
+
|
322
|
+
* [http://localhost:3000/](http://localhost:3000/)
|
323
|
+
* [http://localhost:3000/fr](http://localhost:3000/fr)
|
324
|
+
* [http://localhost:3000/users/hello](http://localhost:3000/users/hello)
|
325
|
+
* [http://localhost:3000/(5+5*20-15)/9](http://localhost:3000/(5+5*20-15)/9)
|
326
|
+
* [http://localhost:3000/fr/(5+5*20-15)/9](http://localhost:3000/fr/(5+5*20-15)/9)
|
327
|
+
* [http://localhost:3000/users/hello?_method=delete](http://localhost:3000/users/hello?_method=delete)
|
328
|
+
|
329
|
+
## Plezi Settings
|
330
|
+
|
331
|
+
Plezi is ment to be very flexible. please take a look at the Plezi Module for settings you might want to play with (max_threads, idle_sleep, create_logger) or any monkey patching you might enjoy.
|
332
|
+
|
333
|
+
Feel free to fork or contribute. right now I am one person, but together we can make something exciting that will help us enjoy Ruby in this brave new world and (hopefully) set an example that will induce progress in the popular mainstream frameworks such as Rails and Sinatra.
|
334
|
+
|
335
|
+
## Contributing
|
336
|
+
|
337
|
+
1. Fork it ( https://github.com/boazsegev/plezi-server/fork )
|
338
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
339
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
340
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
341
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/TODO.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# HTTPHost
|
2
|
+
|
3
|
+
Folder Listing:: add folder listing option?
|
4
|
+
|
5
|
+
# Template
|
6
|
+
|
7
|
+
update gem file (remove erb).
|
8
|
+
|
9
|
+
# Name?
|
10
|
+
|
11
|
+
rename the project?
|
12
|
+
|
13
|
+
# HTTPProtocol
|
14
|
+
|
15
|
+
XML::
|
16
|
+
support for XML HTTP body types?
|
17
|
+
|
18
|
+
Charset::
|
19
|
+
parse chareset for incoming content-type in the multipart request body? (or leave if binary?)
|
data/bin/plezi
ADDED
@@ -0,0 +1,301 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$0="Plezi Builder"
|
3
|
+
# count lines of code with: ^[ \t]*[\w\d\"\(\{\@\[\]\}\)\:\'\.\*\&]+.*$
|
4
|
+
|
5
|
+
require 'irb'
|
6
|
+
require 'securerandom'
|
7
|
+
|
8
|
+
##########
|
9
|
+
# this is the template writer
|
10
|
+
#
|
11
|
+
# you can update it by aliasing the old initialize and writing adding to it:
|
12
|
+
#
|
13
|
+
# if defined? BUILDING_PLEZI_TEMPLATE
|
14
|
+
# class AppTemplate
|
15
|
+
# alias :my_new_gem_old_init :initialize
|
16
|
+
# def initialize
|
17
|
+
# # start with the old initialize, to set the template up
|
18
|
+
# my_new_gem_old_init appname
|
19
|
+
# # add your gem to the gemfile
|
20
|
+
# app_tree["Gemfile"] << "\n# feed GEMNAME to plezi"
|
21
|
+
# app_tree["Gemfile"] << "\ngem 'GEMNAME'"
|
22
|
+
# # make sure your folder exists, but don't overwrite!!!
|
23
|
+
# # if you overwrite!!! you might destroy other gems additions.
|
24
|
+
# app_tree["lib"] ||= {}
|
25
|
+
# app_tree["MY_SPECIAL_GEM"] ||= {}
|
26
|
+
# # once your folder exists, create your file
|
27
|
+
# app_tree["lib]["MY_SPECIAL_GEM"]["filename.rb"] = "# code goes here..."
|
28
|
+
# app_tree["file_without_folder.rb"] ||= "# more code goes here..."
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
class AppTemplate
|
34
|
+
|
35
|
+
def initialize
|
36
|
+
require 'rubygems'
|
37
|
+
# set end comments
|
38
|
+
@end_comments = []
|
39
|
+
@app_tree ||= {}
|
40
|
+
|
41
|
+
# set up application files
|
42
|
+
app_tree["app"] ||= {}
|
43
|
+
app_tree["app"]["controllers"] ||= {}
|
44
|
+
app_tree["app"]["controllers"]["sample_controller.rb"] ||= IO.read(::File.expand_path(File.join("..", "..", "resources" ,"controller.rb"), __FILE__))
|
45
|
+
app_tree["app"]["models"] ||= {}
|
46
|
+
app_tree["app"]["views"] ||= {}
|
47
|
+
|
48
|
+
# set up the assets folder
|
49
|
+
app_tree["assets"] ||= {}
|
50
|
+
app_tree["assets"]["stylesheets"] ||= {}
|
51
|
+
app_tree["assets"]["javascripts"] ||= {}
|
52
|
+
app_tree["assets"]["welcome.html"] ||= IO.read(::File.expand_path(File.join("..", "..", "resources" ,"welcome_page.html"), __FILE__)).gsub('appname', ARGV[1])
|
53
|
+
|
54
|
+
# app core files.
|
55
|
+
app_tree["environment.rb"] ||= IO.read ::File.expand_path(File.join("..", "..", "resources" ,"environment.rb"), __FILE__)
|
56
|
+
app_tree["routes.rb"] ||= IO.read ::File.expand_path(File.join("..", "..", "resources" ,"routes.rb"), __FILE__)
|
57
|
+
app_tree["rakefile.rb"] ||= IO.read ::File.expand_path(File.join("..", "..", "resources" ,"rakefile.rb"), __FILE__)
|
58
|
+
app_tree["Procfile"] ||= ""
|
59
|
+
app_tree["Procfile"] << "\nweb: bundle exec ./#{ARGV[1]}.rb -p $PORT\n"
|
60
|
+
app_tree["Gemfile"] ||= IO.read ::File.expand_path(File.join("..", "..", "resources" ,"Gemfile"), __FILE__)
|
61
|
+
|
62
|
+
# set up config files
|
63
|
+
app_tree["config"] ||= {}
|
64
|
+
app_tree["config"]["db_ac_config.rb"] ||= IO.read(::File.expand_path(File.join("..", "..", "resources" ,"db_ac_config.rb"), __FILE__))
|
65
|
+
app_tree["config"]["db_sequel_config.rb"] ||= IO.read(::File.expand_path(File.join("..", "..", "resources" ,"db_sequel_config.rb"), __FILE__))
|
66
|
+
app_tree["config"]["db_dm_config.rb"] ||= IO.read(::File.expand_path(File.join("..", "..", "resources" ,"db_dm_config.rb"), __FILE__))
|
67
|
+
app_tree["config"]["haml_config.rb"] ||= IO.read(::File.expand_path(File.join("..", "..", "resources" ,"haml_config.rb"), __FILE__))
|
68
|
+
app_tree["config"]["i18n_config.rb"] ||= IO.read(::File.expand_path(File.join("..", "..", "resources" ,"i18n_config.rb"), __FILE__))
|
69
|
+
app_tree["config"]["redis_config.rb"] ||= (IO.read(::File.expand_path(File.join("..", "..", "resources" ,"redis_config.rb"), __FILE__))).gsub('appsecret', "#{ARGV[1]}_#{SecureRandom.hex}")
|
70
|
+
|
71
|
+
#set up database stub folders
|
72
|
+
app_tree["db"] ||= {}
|
73
|
+
app_tree["db"]["migrate"] ||= {}
|
74
|
+
|
75
|
+
#set up the extras folder, to be filled with future goodies.
|
76
|
+
app_tree["extras"] ||= {}
|
77
|
+
app_tree["extras"]["config.ru"] ||= IO.read ::File.expand_path(File.join("..", "..", "resources" ,"config.ru"), __FILE__)
|
78
|
+
|
79
|
+
#set up I18n stub
|
80
|
+
app_tree["locales"] ||= {}
|
81
|
+
app_tree["locales"]["en.yml"] ||= IO.read ::File.expand_path(File.join("..", "..", "resources" ,"en.yml"), __FILE__)
|
82
|
+
|
83
|
+
# create library, log and tmp folders
|
84
|
+
app_tree["logs"] ||= {}
|
85
|
+
app_tree["lib"] ||= {}
|
86
|
+
app_tree["tmp"] ||= {}
|
87
|
+
|
88
|
+
|
89
|
+
# set up a public folder for static file service
|
90
|
+
app_tree["public"] ||= {}
|
91
|
+
app_tree["public"]["404.slim"] ||= IO.read(::File.expand_path(File.join("..", "..", "resources" ,"404.slim"), __FILE__))
|
92
|
+
app_tree["public"]["500.slim"] ||= IO.read(::File.expand_path(File.join("..", "..", "resources" ,"500.slim"), __FILE__))
|
93
|
+
app_tree["public"]["404.html"] ||= IO.read(::File.expand_path(File.join("..", "..", "resources" ,"404.html"), __FILE__))
|
94
|
+
app_tree["public"]["500.html"] ||= IO.read(::File.expand_path(File.join("..", "..", "resources" ,"500.html"), __FILE__))
|
95
|
+
app_tree["public"]["404.erb"] ||= IO.read(::File.expand_path(File.join("..", "..", "resources" ,"404.erb"), __FILE__))
|
96
|
+
app_tree["public"]["500.erb"] ||= IO.read(::File.expand_path(File.join("..", "..", "resources" ,"500.erb"), __FILE__))
|
97
|
+
app_tree["public"]["404.haml"] ||= IO.read(::File.expand_path(File.join("..", "..", "resources" ,"404.haml"), __FILE__))
|
98
|
+
app_tree["public"]["500.haml"] ||= IO.read(::File.expand_path(File.join("..", "..", "resources" ,"500.haml"), __FILE__))
|
99
|
+
app_tree["public"]["assets"] ||= {}
|
100
|
+
app_tree["public"]["assets"]["stylesheets"] ||= {}
|
101
|
+
app_tree["public"]["assets"]["javascripts"] ||= {}
|
102
|
+
app_tree["public"]["images"] ||= {}
|
103
|
+
app_tree["public"]["images"]['plezi_gray.png'] ||= IO.read(::File.expand_path(File.join("..", "..", "resources" ,"plezi_gray.png"), __FILE__))
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
def app_tree
|
108
|
+
@app_tree ||= {}
|
109
|
+
end
|
110
|
+
def build
|
111
|
+
# require 'pry'
|
112
|
+
# binding.pry
|
113
|
+
app_tree["Gemfile"] << "\n\n\nruby '#{RUBY_VERSION}'\n"
|
114
|
+
begin
|
115
|
+
Dir.mkdir ARGV[1]
|
116
|
+
puts "created the #{ARGV[1]} application directory.".green
|
117
|
+
rescue Exception => e
|
118
|
+
puts "the #{ARGV[1]} application directory exists - trying to rebuild (no overwrite).".pink
|
119
|
+
end
|
120
|
+
Dir.chdir ARGV[1]
|
121
|
+
puts "starting to write template data...".red
|
122
|
+
puts ""
|
123
|
+
@app_tree["#{ARGV[1]}"] ||= IO.read ::File.expand_path(File.join("..", "..", "resources" ,"code.rb"), __FILE__)
|
124
|
+
write_files app_tree
|
125
|
+
File.chmod 0775, "#{ARGV[1]}"
|
126
|
+
puts "tried to update execution permissions. this probably failed.".pink
|
127
|
+
puts "use: chmod +x ./#{ARGV[1]} to set execution permissions on Unix machines."
|
128
|
+
puts ""
|
129
|
+
puts "done."
|
130
|
+
puts "\n#{@end_comments.join("\n")}" unless @end_comments.empty?
|
131
|
+
puts ""
|
132
|
+
puts "please change directory into the app directory: cd #{ARGV[1]}"
|
133
|
+
puts ""
|
134
|
+
puts "run the #{ARGV[1]} app using: ./#{ARGV[1]} or using: plezi s"
|
135
|
+
puts ""
|
136
|
+
end
|
137
|
+
|
138
|
+
def write_files files, parent = "."
|
139
|
+
if files.is_a? Hash
|
140
|
+
files.each do |k, v|
|
141
|
+
if v.is_a? Hash
|
142
|
+
begin
|
143
|
+
Dir.mkdir k
|
144
|
+
puts " created #{parent}/#{k}".green
|
145
|
+
rescue Exception => e
|
146
|
+
puts " exists #{parent}/#{k}".red
|
147
|
+
end
|
148
|
+
Dir.chdir k
|
149
|
+
write_files v, (parent + "/" + k)
|
150
|
+
Dir.chdir ".."
|
151
|
+
elsif v.is_a? String
|
152
|
+
if ::File.exists? k
|
153
|
+
if false #%w{Gemfile rakefile.rb}.include? k
|
154
|
+
# old = IO.read k
|
155
|
+
# old = (old.lines.map {|l| "\##{l}"}).join
|
156
|
+
# IO.write k, "#####################\n#\n# OLD DATA COMMENTED OUT - PLEASE REVIEW\n#\n##{old}\n#{v}"
|
157
|
+
# puts " #{parent}/#{k} WAS OVERWRITTEN, old data was preserved by comenting it out.".pink
|
158
|
+
# puts " #{parent}/#{k} PLEASE REVIEW.".pink
|
159
|
+
# @end_comments << "#{parent}/#{k} WAS OVERWRITTEN, old data was preserved by comenting it out. PLEASE REVIEW."
|
160
|
+
else
|
161
|
+
puts " EXISTS(!) #{parent}/#{k}".red
|
162
|
+
end
|
163
|
+
else
|
164
|
+
IO.write k, v
|
165
|
+
puts " wrote #{parent}/#{k}".yellow
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
|
174
|
+
######################################################################
|
175
|
+
# tweek the string class for termial coloring options
|
176
|
+
class String
|
177
|
+
# colorization
|
178
|
+
def colorize(color_code)
|
179
|
+
"\e[#{color_code}m#{self}\e[0m"
|
180
|
+
end
|
181
|
+
|
182
|
+
def red
|
183
|
+
colorize(31)
|
184
|
+
end
|
185
|
+
|
186
|
+
def green
|
187
|
+
colorize(32)
|
188
|
+
end
|
189
|
+
|
190
|
+
def yellow
|
191
|
+
colorize(33)
|
192
|
+
end
|
193
|
+
|
194
|
+
def pink
|
195
|
+
colorize(35)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
######################################################################
|
200
|
+
######################################################################
|
201
|
+
##
|
202
|
+
## Start the Build script
|
203
|
+
##
|
204
|
+
######################################################################
|
205
|
+
######################################################################
|
206
|
+
|
207
|
+
if ARGV[0] == 'new' || ARGV[0] == 'n' || ARGV[0] == "force"
|
208
|
+
#########
|
209
|
+
## set up building environment
|
210
|
+
BUILDING_PLEZI_TEMPLATE = true
|
211
|
+
ARGV[1] = ARGV[1].gsub /[^a-zA-Z0-9]/, '_'
|
212
|
+
if Dir.exists?(ARGV[1]) && ARGV[0] != "force"
|
213
|
+
puts ""
|
214
|
+
puts "WARNING: app/folder alread exists, use `plezi fource #{ARGV[1]}` to attempt rebuild (no files will be overwritten).".red
|
215
|
+
puts ""
|
216
|
+
exit
|
217
|
+
end
|
218
|
+
if Dir.exists?(ARGV[1]) && ARGV[0] == "force"
|
219
|
+
Dir.chdir ARGV[1]
|
220
|
+
require ::File.expand_path(::Dir["."][0], ( ARGV[1] + ".rb") )
|
221
|
+
Dir.chdir '..'
|
222
|
+
end
|
223
|
+
|
224
|
+
if ARGV.count > 3 && (ARGV[2] == 'with' || ARGV[2] == 'w')
|
225
|
+
|
226
|
+
# gem loading
|
227
|
+
local_gems = Gem::Specification.map {|g| g.name}
|
228
|
+
# this will load all requested gems and allow them to update the AppTemplate
|
229
|
+
if ARGV[3] == "all"
|
230
|
+
puts "loading gems and giving each gem a chance to update the app template:".yellow
|
231
|
+
local_gems.each do |g|
|
232
|
+
begin
|
233
|
+
puts "loaded the #{g} gem." if require g
|
234
|
+
rescue Exception => e
|
235
|
+
puts "couldn't load the #{g} gem... moving on.".red
|
236
|
+
end
|
237
|
+
end
|
238
|
+
else
|
239
|
+
require 'pathname'
|
240
|
+
ARGV[3..-1].each do |g|
|
241
|
+
if local_gems.include? g
|
242
|
+
require g
|
243
|
+
puts "loaded the #{g} gem, and gave it a change to update the template."
|
244
|
+
else
|
245
|
+
puts "Error, the gem: #{g} could not be found!".red
|
246
|
+
puts "try first running: gem install #{g}".green
|
247
|
+
exit
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
true
|
252
|
+
end
|
253
|
+
|
254
|
+
# building
|
255
|
+
template = AppTemplate.new
|
256
|
+
template.build
|
257
|
+
elsif ARGV[0] == 'server' || ARGV[0] == 'start' || ARGV[0] == 's'
|
258
|
+
ARGV.shift
|
259
|
+
load File.expand_path(Dir["."][0], (File.expand_path(Dir["."][0]).split(/[\\\/]/).last) ) rescue load( File.expand_path(Dir["."][0], (File.expand_path(Dir["."][0]).split(/[\\\/]/).last + '.rb') ) )
|
260
|
+
elsif ARGV[0] == 'console' || ARGV[0] == 'c'
|
261
|
+
NO_PLEZI_AUTO_START ||= true
|
262
|
+
load File.expand_path(Dir["."][0], (File.expand_path(Dir["."][0]).split(/[\\\/]/).last) ) rescue load( File.expand_path(Dir["."][0], (File.expand_path(Dir["."][0]).split(/[\\\/]/).last + '.rb') ) )
|
263
|
+
ARGV.clear
|
264
|
+
IRB.setup nil
|
265
|
+
IRB.conf[:MAIN_CONTEXT] = IRB::Irb.new.context
|
266
|
+
require 'irb/ext/multi-irb'
|
267
|
+
IRB.irb nil, self
|
268
|
+
else
|
269
|
+
puts ""
|
270
|
+
puts "Plezi fast web app starter.".pink
|
271
|
+
puts "use: plezi new appname"
|
272
|
+
puts "or: plezi new appname with template-gem-to-put-in another-template-gem-to-put"
|
273
|
+
puts "==============================".green
|
274
|
+
puts "new app options:".pink
|
275
|
+
puts "option description".yellow
|
276
|
+
puts "new <appname> creates a new application called <appname>."
|
277
|
+
puts "n alias for new."
|
278
|
+
puts "new app with gem 'new' accepts the 'with' paramater (or w for short)."
|
279
|
+
puts "starting up an app:".pink
|
280
|
+
puts "start runs the app. accepts any paramaters the app supports."
|
281
|
+
puts "s alias for start/server."
|
282
|
+
puts "start console innsead of services:".pink
|
283
|
+
puts "console runs the app. accepts any paramaters the app supports."
|
284
|
+
puts "c alias for start/server."
|
285
|
+
puts "==============================".green
|
286
|
+
puts "create an app with specific plugins:".pink
|
287
|
+
puts "plezi new app with gem1 gem2"
|
288
|
+
puts "loads the specific gem(s) and allows them to update the template before building the app.".green
|
289
|
+
puts ""
|
290
|
+
puts "create an app with ALL plugins:".pink
|
291
|
+
puts "plezi n app w all"
|
292
|
+
puts "loads the all available gem(s) and allows them to update the template before building the app.".green
|
293
|
+
puts ""
|
294
|
+
puts "start the application with any paramaters it supports:".pink
|
295
|
+
puts "plezi s -p 80"
|
296
|
+
puts "loads the app with paramaters -p 80".green
|
297
|
+
puts ""
|
298
|
+
end
|
299
|
+
|
300
|
+
|
301
|
+
|