ptero 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +350 -0
- data/Rakefile +10 -0
- data/bin/ptero +13 -0
- data/lib/ptero.rb +14 -0
- data/lib/ptero/application.rb +281 -0
- data/lib/ptero/cli.rb +35 -0
- data/lib/ptero/cli/root.rb +282 -0
- data/lib/ptero/composer_default.json +8 -0
- data/lib/ptero/exception.rb +26 -0
- data/lib/ptero/exceptions/applicationexception.rb +9 -0
- data/lib/ptero/exceptions/generatorexception.rb +9 -0
- data/lib/ptero/generator.rb +146 -0
- data/lib/ptero/generators/applicationjavascriptgenerator.rb +19 -0
- data/lib/ptero/generators/applicationstylesheetgenerator.rb +18 -0
- data/lib/ptero/generators/configgenerator.rb +34 -0
- data/lib/ptero/generators/controllergenerator.rb +23 -0
- data/lib/ptero/generators/htaccessgenerator.rb +27 -0
- data/lib/ptero/generators/javascriptgenerator.rb +34 -0
- data/lib/ptero/generators/landinggenerator.rb +23 -0
- data/lib/ptero/generators/layoutgenerator.rb +19 -0
- data/lib/ptero/generators/loadallgenerator.rb +26 -0
- data/lib/ptero/generators/modelgenerator.rb +22 -0
- data/lib/ptero/generators/pagegenerator.rb +56 -0
- data/lib/ptero/generators/pagenotfoundgenerator.rb +16 -0
- data/lib/ptero/generators/phpclassgenerator.rb +27 -0
- data/lib/ptero/generators/phpgenerator.rb +18 -0
- data/lib/ptero/generators/phpinfogenerator.rb +16 -0
- data/lib/ptero/generators/routesgenerator.rb +68 -0
- data/lib/ptero/generators/setupgenerator.rb +13 -0
- data/lib/ptero/generators/stylesheetgenerator.rb +33 -0
- data/lib/ptero/generators/twiggenerator.rb +24 -0
- data/lib/ptero/generators/viewgenerator.rb +24 -0
- data/lib/ptero/templates/applicationjavascriptgenerator.js.erb +11 -0
- data/lib/ptero/templates/applicationstylesheetgenerator.css.erb +40 -0
- data/lib/ptero/templates/configgenerator.php.erb +26 -0
- data/lib/ptero/templates/controllergenerator.php.erb +25 -0
- data/lib/ptero/templates/generator.txt.erb +1 -0
- data/lib/ptero/templates/htaccessgenerator.htaccess.erb +9 -0
- data/lib/ptero/templates/javascriptgenerator.js.erb +11 -0
- data/lib/ptero/templates/landinggenerator.php.erb +3 -0
- data/lib/ptero/templates/layoutgenerator.html.twig.erb +37 -0
- data/lib/ptero/templates/loadallgenerator.php.erb +12 -0
- data/lib/ptero/templates/modelgenerator.php.erb +21 -0
- data/lib/ptero/templates/pagegenerator.html.twig.erb +13 -0
- data/lib/ptero/templates/pagenotfoundgenerator.html.twig.erb +13 -0
- data/lib/ptero/templates/phpclassgenerator.php.erb +24 -0
- data/lib/ptero/templates/phpgenerator.php.erb +3 -0
- data/lib/ptero/templates/phpinfogenerator.php.erb +3 -0
- data/lib/ptero/templates/routesgenerator.php.erb +13 -0
- data/lib/ptero/templates/setupgenerator.php.erb +19 -0
- data/lib/ptero/templates/stylesheetgenerator.css.erb +8 -0
- data/lib/ptero/templates/twiggenerator.html.twig.erb +5 -0
- data/lib/ptero/templates/viewgenerator.html.twig.erb +13 -0
- data/lib/ptero/version.rb +4 -0
- data/ptero.gemspec +28 -0
- data/test/fixtures/test_generators_fixtures.yaml +74 -0
- data/test/test_application.rb +123 -0
- data/test/test_exceptions.rb +52 -0
- data/test/test_generators.rb +110 -0
- data/test/test_root.rb +212 -0
- metadata +212 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4da3a464d447bcedad4e6dcbcef6f91c508185fb
|
4
|
+
data.tar.gz: 097dec9857ca53e7383297ccbc291bbcc7933d9c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 25e1ff3a390dbcb356212a3b520fb7dc676e58c2f669d326a7a77b36c8e54feb1696747164af8c5a1fcad7b0a8ad21e2f2cbc198ac81d18be0253674da7b282c
|
7
|
+
data.tar.gz: 5b3ca3cd4dd4bafae1d3c8d504e996706898af8185d4fe671f8e023f8f9324ccd67b93fa19dfaf052e48c546b6552c36854f1023d3be9e6d6d83a4fefa27c632
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 AndrewTLee
|
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,350 @@
|
|
1
|
+
### Ptero
|
2
|
+
Ptero is a fast and flexible model-view-controller framework for PHP. It allows you to generate models, views, controllers, and routes instantly over the command-line and automatically provides a strong, clean structure for running web apps.
|
3
|
+
|
4
|
+
Think wearing a giant robot exo-skeleton with lasers and missile launchers while crushing the puny challenges of archaic PHP in a two-hour-long feature film directed by Michael Bay. It's that good.
|
5
|
+
|
6
|
+
|
7
|
+
### It has the good stuff
|
8
|
+
Features include:
|
9
|
+
* RESTful routing
|
10
|
+
* Sensible templates: layouts and views separate
|
11
|
+
* RoR-esque code generation
|
12
|
+
* PHP-ActiveRecord for ORM: No SQL needed!
|
13
|
+
* Installs dependencies quickly and easily with Composer
|
14
|
+
* Built in Ruby: Who doesn't love Ruby?
|
15
|
+
|
16
|
+
### It takes care of the bad stuff
|
17
|
+
Don't worry about:
|
18
|
+
* annoying PHP syntax errors
|
19
|
+
* copy-pasting or otherwise repeating code
|
20
|
+
* SQL, SQL injection, escaping strings, etc
|
21
|
+
* URLs that look ugly
|
22
|
+
* PHP being stupid in general
|
23
|
+
|
24
|
+
### How do I do it?
|
25
|
+
To start using Ptero, make sure your PHP command-line tool is up-to-date, then run the following:
|
26
|
+
```bash
|
27
|
+
$ gem install ptero
|
28
|
+
```
|
29
|
+
|
30
|
+
Now make an application:
|
31
|
+
```bash
|
32
|
+
$ ptero new Blog
|
33
|
+
```
|
34
|
+
|
35
|
+
There! Your web application is seeded and ready. Now you can start generating files.
|
36
|
+
```bash
|
37
|
+
$ cd Blog
|
38
|
+
$ ptero generate page hippo
|
39
|
+
$ ptero route /hippo Hippo
|
40
|
+
```
|
41
|
+
Now run an apache server with root directory Blog/www, and navigate to http://localhost/hippo to see a fully-formed page.
|
42
|
+
|
43
|
+
(I really should have a built-in server command, but making Ruby run PHP is a mess. If someone wants to write up a cross-platform WEBrick server for PHP, I would be glad to incorporate it)
|
44
|
+
|
45
|
+
### Onward!
|
46
|
+
Now for the best part. Let's take a look at the generated code.
|
47
|
+
|
48
|
+
Here's the default controller, Blog/php/controllers/HippoController.php
|
49
|
+
```php
|
50
|
+
/*
|
51
|
+
*
|
52
|
+
* HippoController.php
|
53
|
+
* ===================
|
54
|
+
* This controller does ...
|
55
|
+
*
|
56
|
+
*/
|
57
|
+
|
58
|
+
namespace Blog\Controllers;
|
59
|
+
|
60
|
+
|
61
|
+
class HippoController extends ApplicationController {
|
62
|
+
|
63
|
+
// Handle GET requests, use 'public function post()' to handle POST
|
64
|
+
public function get() {
|
65
|
+
// Render template
|
66
|
+
$this->render();
|
67
|
+
}
|
68
|
+
|
69
|
+
}
|
70
|
+
```
|
71
|
+
|
72
|
+
The controller inherits from ApplicationController, giving it all sorts of useful functionality, most notably the render() method.
|
73
|
+
|
74
|
+
Calling $this->render(); with (or without) an array mapping of arguments will automatically render the template of the same name as the controller, in this case Blog/views/hippo.html.twig.
|
75
|
+
|
76
|
+
Here it is:
|
77
|
+
|
78
|
+
```twig
|
79
|
+
{% extends 'application.html.twig' %}
|
80
|
+
|
81
|
+
{% block head %}
|
82
|
+
<!-- HTML head code produced by view: hippo -->
|
83
|
+
<script type="text/javascript" src="/assets/js/hippo.js"></script>
|
84
|
+
<link rel="stylesheet" type="text/css" src="/assets/css/hippo.css" />
|
85
|
+
{% endblock %}
|
86
|
+
|
87
|
+
{% block content %}
|
88
|
+
<!-- HTML content code produced by view: hippo -->
|
89
|
+
<h2>View: Hippo</h2>
|
90
|
+
|
91
|
+
{% endblock %}
|
92
|
+
|
93
|
+
```
|
94
|
+
This template is written in Twig. [Click here](http://twig.sensiolabs.org) to learn the basics of Twig.
|
95
|
+
|
96
|
+
|
97
|
+
Notice that the template has only a few lines of code, and that it extends a layout, called application.html.twig.
|
98
|
+
|
99
|
+
In this way, global layout changes can be made to the global application template, and individual changes made to individual pages.
|
100
|
+
|
101
|
+
For example, let's try doing some arbitrary server-side work and sending it to the client:
|
102
|
+
```php
|
103
|
+
// HippoController.php
|
104
|
+
|
105
|
+
class HippoController extends ApplicationController {
|
106
|
+
|
107
|
+
// Handle GET requests, use 'public function post()' to handle POST
|
108
|
+
public function get() {
|
109
|
+
// sum the numbers 1-10
|
110
|
+
$sum = 0;
|
111
|
+
for($i=1;$i<10;$i++) {
|
112
|
+
$sum += $i;
|
113
|
+
}
|
114
|
+
$this->render(array(
|
115
|
+
'sum' => $sum
|
116
|
+
));
|
117
|
+
}
|
118
|
+
|
119
|
+
}
|
120
|
+
```
|
121
|
+
the template:
|
122
|
+
```twig
|
123
|
+
{% extends 'application.html.twig' %}
|
124
|
+
|
125
|
+
{% block head %}
|
126
|
+
<!-- HTML head code produced by view: hippo -->
|
127
|
+
<script type="text/javascript" src="/assets/js/hippo.js"></script>
|
128
|
+
<link rel="stylesheet" type="text/css" src="/assets/css/hippo.css" />
|
129
|
+
{% endblock %}
|
130
|
+
|
131
|
+
{% block content %}
|
132
|
+
<!-- HTML content code produced by view: hippo -->
|
133
|
+
<h2>The sum is: {{sum}}</h2>
|
134
|
+
|
135
|
+
{% endblock %}
|
136
|
+
|
137
|
+
```
|
138
|
+
Now our code will print the $sum variable passed to it by the server.
|
139
|
+
|
140
|
+
### Routing
|
141
|
+
We already did some routing with the `$ptero route` command, but routes are also stored in a file and editable by hand. Let's open Blog/php/routes.php
|
142
|
+
```php
|
143
|
+
// Application route maps
|
144
|
+
$application->route(array(
|
145
|
+
|
146
|
+
'/hippo' => 'Blog\Controllers\HippoController',
|
147
|
+
));
|
148
|
+
|
149
|
+
```
|
150
|
+
Look! The route we made is there! It maps the string '/hippo' to the controller class Blog\Controllers\HippoController.
|
151
|
+
|
152
|
+
You can also print all current routes by running
|
153
|
+
```bash
|
154
|
+
$ ptero routes
|
155
|
+
```
|
156
|
+
|
157
|
+
We can try using RESTful parameters in our application and passing them to controllers. Try this:
|
158
|
+
```bash
|
159
|
+
$ ptero generate page rhino
|
160
|
+
$ ptero route /rhino/:number Rhino
|
161
|
+
```
|
162
|
+
Now edit php/controllers/RhinoController.php:
|
163
|
+
```php
|
164
|
+
namespace Blog\Controllers;
|
165
|
+
|
166
|
+
|
167
|
+
class RhinoController extends ApplicationController {
|
168
|
+
|
169
|
+
// Handle GET requests, use 'public function post()' to handle POST
|
170
|
+
public function get($number) {
|
171
|
+
$number = intval($number);
|
172
|
+
$result = $number * $number;
|
173
|
+
$this->render(array(
|
174
|
+
'result' => $result
|
175
|
+
));
|
176
|
+
}
|
177
|
+
|
178
|
+
}
|
179
|
+
```
|
180
|
+
and make the template print our result:
|
181
|
+
```twig
|
182
|
+
{% block content %}
|
183
|
+
<!-- HTML content code produced by view: rhino -->
|
184
|
+
<h2>View: Rhino</h2>
|
185
|
+
<div>
|
186
|
+
Result: {{ result }}
|
187
|
+
</div>
|
188
|
+
|
189
|
+
{% endblock %}
|
190
|
+
```
|
191
|
+
|
192
|
+
Now navigate to http://localhost/rhino/74
|
193
|
+
|
194
|
+
Yay! Our application can compute squares!
|
195
|
+
|
196
|
+
Routes in the routes.php file support a full range of regular expressions, although stubs such as :number and :alpha can be useful. Learn more at <https://github.com/anandkunal/ToroPHP>
|
197
|
+
|
198
|
+
### DATABASES!!!
|
199
|
+
|
200
|
+
Ptero wouldn't be complete without Databases, though, (as-of-yet) we have not incorporated the ability to generate them through the command-line tool.
|
201
|
+
|
202
|
+
You'll have to bring your own SQL server. Sorry. Try [MAMP](http://www.mamp.info/en/).
|
203
|
+
|
204
|
+
Ptero uses PHP-ActiveRecord Databases, which are as easy as pie to use with a few minutes' practice. [Take a look.](http://www.phpactiverecord.org/projects/main/wiki/Quick_Start)
|
205
|
+
|
206
|
+
The first step of using Ptero databases is to configure your application. Open config/config.php:
|
207
|
+
```php
|
208
|
+
<?php
|
209
|
+
/*
|
210
|
+
* config.php
|
211
|
+
* return array of configuration values
|
212
|
+
*
|
213
|
+
*
|
214
|
+
*
|
215
|
+
*/
|
216
|
+
|
217
|
+
|
218
|
+
return array(
|
219
|
+
'templates_path' => 'views',
|
220
|
+
|
221
|
+
'controllers_path' => 'php/controllers',
|
222
|
+
|
223
|
+
'models_path' => 'php/models',
|
224
|
+
|
225
|
+
|
226
|
+
|
227
|
+
'database' => array(
|
228
|
+
'name' => 'database-name',
|
229
|
+
'user' => 'username',
|
230
|
+
'password' => 'password',
|
231
|
+
'server' => 'localhost'
|
232
|
+
)
|
233
|
+
)
|
234
|
+
?>
|
235
|
+
```
|
236
|
+
Change the values 'database-name', 'username', 'password', and 'server' to the configuration of your database so that PHP-ActiveRecord knows where to look.
|
237
|
+
|
238
|
+
Now let's generate another page. We'll assume that we have a table called Elephants in our database that has a primary-key int "id", a string "name", and another int "age."
|
239
|
+
```bash
|
240
|
+
$ ptero generate page elephant
|
241
|
+
$ ptero generate model elephant
|
242
|
+
$ ptero route /elephants/:number Elephant
|
243
|
+
```
|
244
|
+
In php/controllers/ElephantController.php:
|
245
|
+
```php
|
246
|
+
<?php
|
247
|
+
|
248
|
+
/*
|
249
|
+
*
|
250
|
+
* ElephantController.php
|
251
|
+
* ======================
|
252
|
+
* This controller does ...
|
253
|
+
*
|
254
|
+
*/
|
255
|
+
|
256
|
+
namespace Blog\Controllers;
|
257
|
+
use \Blog\Models as DB; // Make sure we can access Models
|
258
|
+
|
259
|
+
class ElephantController extends ApplicationController {
|
260
|
+
|
261
|
+
// Handle GET requests, use 'public function post()' to handle POST
|
262
|
+
public function get($id) {
|
263
|
+
$this->db_connect('development'); // Connect to our database, in the mode specified. 'development' is the default value.
|
264
|
+
|
265
|
+
$id = intval($id);
|
266
|
+
$elephant = DB\Elephant::find($id);
|
267
|
+
|
268
|
+
$name = $elephant->name;
|
269
|
+
$age = $elephant->age;
|
270
|
+
$this->render(array(
|
271
|
+
'name' => $name,
|
272
|
+
'age' => $age
|
273
|
+
));
|
274
|
+
}
|
275
|
+
|
276
|
+
}
|
277
|
+
|
278
|
+
|
279
|
+
?>
|
280
|
+
```
|
281
|
+
In views/elephant.twig:
|
282
|
+
```twig
|
283
|
+
{% extends 'application.html.twig' %}
|
284
|
+
|
285
|
+
{% block head %}
|
286
|
+
<!-- HTML head code produced by view: elephant -->
|
287
|
+
<script type="text/javascript" src="/assets/js/elephant.js"></script>
|
288
|
+
<link rel="stylesheet" type="text/css" src="/assets/css/elephant.css" />
|
289
|
+
{% endblock %}
|
290
|
+
|
291
|
+
{% block content %}
|
292
|
+
<!-- HTML content code produced by view: elephant -->
|
293
|
+
<h2>View: Elephant</h2>
|
294
|
+
<h2>Name: {{ name }}</h2>
|
295
|
+
<h2>Age: {{ age }}</h2>
|
296
|
+
|
297
|
+
{% endblock %}
|
298
|
+
```
|
299
|
+
With this code in place, a request to http://localhost/elephants/n will output the information of elephant with id=n.
|
300
|
+
|
301
|
+
In this same way, you can create new Elephants and save them to the database by running something akin to:
|
302
|
+
```php
|
303
|
+
$dumbo = new \Blog\Models\Elephant();
|
304
|
+
$dumbo->name = 'dumbo';
|
305
|
+
$dumbo->age = 25;
|
306
|
+
$dumbo->save();
|
307
|
+
```
|
308
|
+
In the post() method of a controller.
|
309
|
+
|
310
|
+
### Inverse Commands
|
311
|
+
Each "create" command has an inverse command to undo it.
|
312
|
+
|
313
|
+
This "destroy" command will delete or undo a create operation.
|
314
|
+
|
315
|
+
USE WITH CARE ON UNCOMMITTED CHANGES.
|
316
|
+
|
317
|
+
Here are some common inverse commands:
|
318
|
+
```bash
|
319
|
+
$ ptero new Slideshow # Create the Slideshow application
|
320
|
+
$ cd Slideshow
|
321
|
+
|
322
|
+
$ ptero generate controller Index
|
323
|
+
$ ptero remove controller Index # remove is the inverse of generate
|
324
|
+
|
325
|
+
$ ptero route /asdf/:alpha.jpg Tree
|
326
|
+
$ ptero unroute /asdf/:alpha.jpg # unroute is the inverse of route
|
327
|
+
|
328
|
+
|
329
|
+
$ ptero destroy # destroy is the inverse of new, it will destroy a named directory or the current one
|
330
|
+
```
|
331
|
+
|
332
|
+
### Conclusion
|
333
|
+
Ptero is still in a phase of development, so please contact @LuminousRubyist, the creator and maintainer of this project, for any questions, comments, complaints, insults, or profound observations about everyday life.
|
334
|
+
|
335
|
+
|
336
|
+
### Contributing
|
337
|
+
|
338
|
+
1. Fork it ( https://github.com/luminousrubyist/ptero/fork )
|
339
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
340
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
341
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
342
|
+
5. Create a new Pull Request
|
343
|
+
|
344
|
+
|
345
|
+
### Thank you
|
346
|
+
Thank you to all the libraries, and services that make this possible:
|
347
|
+
* [PHP-ActiveRecord](http://www.phpactiverecord.org/)
|
348
|
+
* [Twig](http://twig.sensiolabs.org/)
|
349
|
+
* [ToroPHP](https://github.com/anandkunal/ToroPHP)
|
350
|
+
* [Composer](https://getcomposer.org/)
|
data/Rakefile
ADDED
data/bin/ptero
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'ptero'
|
4
|
+
require 'colorize'
|
5
|
+
|
6
|
+
begin
|
7
|
+
# Run the Ptero command-line
|
8
|
+
Ptero::CLI::Root.start(ARGV)
|
9
|
+
rescue Ptero::Exception => e
|
10
|
+
# Report Ptero::Exception and subclasses to the user
|
11
|
+
puts "#{'Error:'.red} #{e.message}"
|
12
|
+
puts " Exception type: #{e.class}"
|
13
|
+
end
|
data/lib/ptero.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'ptero/version'
|
2
|
+
require 'ptero/exception'
|
3
|
+
require 'ptero/generator'
|
4
|
+
require 'ptero/cli'
|
5
|
+
require 'ptero/application'
|
6
|
+
require 'pathname'
|
7
|
+
|
8
|
+
# The namespace for all Ptero classes and constants
|
9
|
+
module Ptero
|
10
|
+
|
11
|
+
# The directory location of all Ptero templates for code generation
|
12
|
+
TEMPLATE_PATH = Pathname.new("#{__dir__}/ptero/templates")
|
13
|
+
|
14
|
+
end
|
@@ -0,0 +1,281 @@
|
|
1
|
+
# application.rb
|
2
|
+
# ==============
|
3
|
+
# This class represents a single application built with ptero.
|
4
|
+
# It uses generators to orchestrate the creation of an application, and also oversees composer dependencies and installations
|
5
|
+
|
6
|
+
require 'json'
|
7
|
+
require 'pathname'
|
8
|
+
require 'colorize'
|
9
|
+
require 'fileutils'
|
10
|
+
require 'open3'
|
11
|
+
|
12
|
+
module Ptero
|
13
|
+
# Each instance of this class represents a Ptero application. Within its directory,
|
14
|
+
# this class is used to test the presence of PHP, download the composer.phar file, install composer dependencies, and use Generators to automatically create
|
15
|
+
# new files and components in the application.
|
16
|
+
class Application
|
17
|
+
|
18
|
+
# Input a directory String or Pathname to make a new Application from that path. (Defaults to Dir.pwd)
|
19
|
+
# @param dir [Pathname,String] the Application directory
|
20
|
+
def initialize(dir=Dir.pwd)
|
21
|
+
@name = dir.to_s.split('/').last.capitalize
|
22
|
+
@dir = Pathname.new(dir)
|
23
|
+
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
attr_reader :name,:dir
|
28
|
+
|
29
|
+
# Find out whether or not the user has the PHP command-line tool installed so that they can use Composer.
|
30
|
+
# @return [Boolean] whether or not the user's PHP command works.
|
31
|
+
def test_php
|
32
|
+
Dir.chdir @dir do
|
33
|
+
`php -r'echo "Success!";'` == 'Success!'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Download the composer.phar file into the Application directory using the command
|
38
|
+
# 'curl -sS https://getcomposer.org/installer | php' as prescribed on the Composer website
|
39
|
+
#
|
40
|
+
# @return [Application,Boolean] self
|
41
|
+
def get_composer
|
42
|
+
raise Ptero::Exception::ApplicationException, "Application #{self} already has composer.phar file" if has_composer?
|
43
|
+
Dir.chdir @dir do
|
44
|
+
# Download code from the composer website
|
45
|
+
# https://getcomposer.org
|
46
|
+
Open3.popen3("php -r \"readfile('https://getcomposer.org/installer');\" | php") do |stdin,stdout,stderr|
|
47
|
+
print 'Downloading composer'
|
48
|
+
stdout.each_line do |line|
|
49
|
+
print '.'
|
50
|
+
end
|
51
|
+
puts
|
52
|
+
puts 'Done!'
|
53
|
+
end
|
54
|
+
end
|
55
|
+
self
|
56
|
+
end
|
57
|
+
|
58
|
+
# Remove the composer.phar file
|
59
|
+
# @return [Application,Boolean] self if composer was removed, flse if composer is not downloaded
|
60
|
+
def remove_composer
|
61
|
+
raise Ptero::Exception::ApplicationException, "Application #{self} does not have a composer.phar file" unless has_composer?
|
62
|
+
File.unlink(dir.join('composer.phar'))
|
63
|
+
self
|
64
|
+
end
|
65
|
+
|
66
|
+
# Find out if this Application has the composer.phar file.
|
67
|
+
# @return [Boolean] whether or not composer.phar was found
|
68
|
+
def has_composer?
|
69
|
+
Dir.chdir @dir do
|
70
|
+
File.exist? 'composer.phar'
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Install all dependencies listed in the composer.json file using Composer
|
75
|
+
# 'php composer.phar install'
|
76
|
+
# @return [Application] self
|
77
|
+
def install_dependencies
|
78
|
+
raise Ptero::Exception::ApplicationException, "Not a dinosaur root: #{Dir.pwd}" unless verify
|
79
|
+
raise Ptero::Exception::ApplicationException, "PHP command-line tool failed, update your version of PHP" unless test_php
|
80
|
+
Dir.chdir @dir do
|
81
|
+
# Install dependencies using Composer
|
82
|
+
# https://getcomposer.org
|
83
|
+
Open3.popen3('php composer.phar install') do |stdin,stdout,stderr|
|
84
|
+
print 'Installing dependencies'
|
85
|
+
stdout.each_line do |line|
|
86
|
+
print '.'
|
87
|
+
end
|
88
|
+
puts
|
89
|
+
puts 'Done!'
|
90
|
+
end
|
91
|
+
end
|
92
|
+
self
|
93
|
+
end
|
94
|
+
|
95
|
+
# Update all dependencies listed in the composer.json file using Composer
|
96
|
+
# 'php composer.phar update'
|
97
|
+
# @return [Application] self
|
98
|
+
def update_dependencies
|
99
|
+
raise Ptero::Exception::ApplicationException, "Not a dinosaur root: #{Dir.pwd}" unless verify
|
100
|
+
raise Ptero::Exception::ApplicationException, "PHP command-line tool failed, update your version of PHP" unless test_php
|
101
|
+
Dir.chdir @dir do
|
102
|
+
# Update dependencies using Composer
|
103
|
+
# https://getcomposer.org
|
104
|
+
Open3.popen3('php composer.phar update') do |stdin,stdout,stderr|
|
105
|
+
stdout.read
|
106
|
+
end
|
107
|
+
end
|
108
|
+
self
|
109
|
+
end
|
110
|
+
|
111
|
+
def remove_dependencies
|
112
|
+
FileUtils.rm_r(dir.join('vendor'))
|
113
|
+
end
|
114
|
+
|
115
|
+
def dependencies_installed?
|
116
|
+
@dir.join('vendor').directory?
|
117
|
+
end
|
118
|
+
|
119
|
+
# Find out if this folder really contains a Ptero application using metadata in composer.json
|
120
|
+
# @return [Boolean] whether or not this application was verified successfully.
|
121
|
+
def verify
|
122
|
+
composer_path = dir.join('composer.json')
|
123
|
+
return false unless File.exist? composer_path
|
124
|
+
hash = JSON.parse File.read(composer_path)
|
125
|
+
return false unless hash['dinosaur'] && hash['dinosaur']['root']
|
126
|
+
self
|
127
|
+
end
|
128
|
+
|
129
|
+
# Generate a file using the given arguments
|
130
|
+
# @param klass [String,Class] what type of file to generate
|
131
|
+
# @param *params all other parameters to pass to the generator
|
132
|
+
def generate(klass,*params)
|
133
|
+
klass = generator_for_name(klass) if klass.is_a? String # if klass is a string, make sure it's a class
|
134
|
+
generator = klass.new(*params)
|
135
|
+
generator.generate
|
136
|
+
self
|
137
|
+
end
|
138
|
+
|
139
|
+
def generated?(klass,*params)
|
140
|
+
klass = generator_for_name(klass) if klass.is_a? String
|
141
|
+
generator = klass.new(*params)
|
142
|
+
generator.generated?
|
143
|
+
end
|
144
|
+
|
145
|
+
# Remove and reload an existing file
|
146
|
+
# @param klass [String,Class] what type of file to reload
|
147
|
+
# @param *params all other parameters to pass to the generator
|
148
|
+
def reload(klass,*params)
|
149
|
+
klass = generator_for_name(klass) if klass.is_a? String
|
150
|
+
generator = klass.new(*params)
|
151
|
+
generator.reload
|
152
|
+
self
|
153
|
+
end
|
154
|
+
|
155
|
+
# Add a route to the Application routes file
|
156
|
+
# @param path [String] the new route's path
|
157
|
+
# @param controller [String] the name of the new route's controller
|
158
|
+
def route(path,controller)
|
159
|
+
klass = generator_for_name('Routes')
|
160
|
+
generator = klass.new
|
161
|
+
generator.route(path,controller)
|
162
|
+
self
|
163
|
+
end
|
164
|
+
|
165
|
+
# Remove a rotue from the Application routes file
|
166
|
+
# @param path [String] the path to be deleted
|
167
|
+
def unroute(path)
|
168
|
+
klass = generator_for_name('Routes')
|
169
|
+
generator = klass.new
|
170
|
+
generator.unroute(path)
|
171
|
+
self
|
172
|
+
end
|
173
|
+
|
174
|
+
# Remove a previously generated file
|
175
|
+
# @param klass [String,Klass] what kind of file to remove
|
176
|
+
# @param *params all other parameters to identify the file to be removed
|
177
|
+
def remove(klass,*params)
|
178
|
+
klass = generator_for_name(klass) if klass.is_a? String # if klass is a string, make sure it's a class
|
179
|
+
generator = klass.new(*params)
|
180
|
+
generator.remove
|
181
|
+
self
|
182
|
+
end
|
183
|
+
|
184
|
+
# Find all routes set in the current Application
|
185
|
+
# @return [Hash] a Hash-map of each path to its corresponding controller
|
186
|
+
def routes
|
187
|
+
klass = generator_for_name('Routes')
|
188
|
+
generator = klass.new
|
189
|
+
generator.get_current_routes
|
190
|
+
generator.routes.dup
|
191
|
+
end
|
192
|
+
|
193
|
+
# If the method name is of the type "generate_NAME(*params)",
|
194
|
+
# such as generate_javascript('contact','Display contact information'),
|
195
|
+
# call self.generate(NAME,*params), so
|
196
|
+
# self.generate('javascript','contact','Display contact information')
|
197
|
+
def method_missing(method_name,*params)
|
198
|
+
match = method_name.to_s.match /^generate_(\w+)$/
|
199
|
+
super unless match
|
200
|
+
type = match[1]
|
201
|
+
generate(type, *params)
|
202
|
+
rescue NameError => e # Couldn't load the proper constant
|
203
|
+
super
|
204
|
+
end
|
205
|
+
|
206
|
+
# Remove the directory of this application
|
207
|
+
def destroy
|
208
|
+
FileUtils.rm_r dir
|
209
|
+
nil
|
210
|
+
end
|
211
|
+
|
212
|
+
private
|
213
|
+
# Convert the names of generators to real Generator class objects
|
214
|
+
def generator_for_name(name)
|
215
|
+
name = name.split('_').map! { |str| "#{str[0].upcase}#{str[1,str.length-1]}" }.join('')
|
216
|
+
Ptero::Generator.const_get("#{name}Generator")
|
217
|
+
end
|
218
|
+
|
219
|
+
|
220
|
+
|
221
|
+
class << self
|
222
|
+
|
223
|
+
# Create a new application with basic files and Composer dependencies. Yield the newly-created Application object to a block if one is given.
|
224
|
+
# @param name [String] the name of the new application
|
225
|
+
# @param dir [String,Pathname] the directory in which to create the new application
|
226
|
+
# @return [Application] the new Application object
|
227
|
+
def create(name,dir=Pathname.new(Dir.pwd))
|
228
|
+
|
229
|
+
Dir.chdir dir do
|
230
|
+
raise Ptero::Exception::ApplicationException, "Cannot create project #{name} because a file already exists with that name" if File.exist? name
|
231
|
+
|
232
|
+
puts "Creating directory #{name} in #{Dir.pwd}"
|
233
|
+
Dir.mkdir(name)
|
234
|
+
Dir.chdir(name) do
|
235
|
+
|
236
|
+
puts "Loading default composer.json"
|
237
|
+
composer = JSON.parse( File.read("#{__dir__}/composer_default.json") )
|
238
|
+
composer[:dinosaur] = {
|
239
|
+
name: name,
|
240
|
+
root: true
|
241
|
+
}
|
242
|
+
|
243
|
+
puts "Writing project composer.json"
|
244
|
+
File.open('composer.json','w') do |file|
|
245
|
+
file.puts JSON.pretty_generate(composer)
|
246
|
+
end
|
247
|
+
|
248
|
+
puts "Initializing app"
|
249
|
+
app = app_for(Dir.pwd)
|
250
|
+
|
251
|
+
yield app if block_given?
|
252
|
+
|
253
|
+
return app
|
254
|
+
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
end
|
259
|
+
|
260
|
+
# Return the first verified application in the specified directory or its parents
|
261
|
+
# @param dir [String,Pathname] the starting directory
|
262
|
+
# @return [Application] the verified Application object
|
263
|
+
def app_for(dir=Dir.pwd)
|
264
|
+
path = Pathname.new dir
|
265
|
+
path.realpath.ascend do |level|
|
266
|
+
app = self.new(level)
|
267
|
+
return app if app.verify
|
268
|
+
end
|
269
|
+
|
270
|
+
raise Ptero::Exception::ApplicationException, "Couldn't find composer.json with dinosaur information in #{dir} or parent directories"
|
271
|
+
end
|
272
|
+
|
273
|
+
# Return the verified Application object corresponding to Dir.pwd or its parent directories
|
274
|
+
# @return [Application] the Application object that represents the found verified application
|
275
|
+
def current_app
|
276
|
+
app_for Dir.pwd
|
277
|
+
end
|
278
|
+
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|