ptero 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +350 -0
  6. data/Rakefile +10 -0
  7. data/bin/ptero +13 -0
  8. data/lib/ptero.rb +14 -0
  9. data/lib/ptero/application.rb +281 -0
  10. data/lib/ptero/cli.rb +35 -0
  11. data/lib/ptero/cli/root.rb +282 -0
  12. data/lib/ptero/composer_default.json +8 -0
  13. data/lib/ptero/exception.rb +26 -0
  14. data/lib/ptero/exceptions/applicationexception.rb +9 -0
  15. data/lib/ptero/exceptions/generatorexception.rb +9 -0
  16. data/lib/ptero/generator.rb +146 -0
  17. data/lib/ptero/generators/applicationjavascriptgenerator.rb +19 -0
  18. data/lib/ptero/generators/applicationstylesheetgenerator.rb +18 -0
  19. data/lib/ptero/generators/configgenerator.rb +34 -0
  20. data/lib/ptero/generators/controllergenerator.rb +23 -0
  21. data/lib/ptero/generators/htaccessgenerator.rb +27 -0
  22. data/lib/ptero/generators/javascriptgenerator.rb +34 -0
  23. data/lib/ptero/generators/landinggenerator.rb +23 -0
  24. data/lib/ptero/generators/layoutgenerator.rb +19 -0
  25. data/lib/ptero/generators/loadallgenerator.rb +26 -0
  26. data/lib/ptero/generators/modelgenerator.rb +22 -0
  27. data/lib/ptero/generators/pagegenerator.rb +56 -0
  28. data/lib/ptero/generators/pagenotfoundgenerator.rb +16 -0
  29. data/lib/ptero/generators/phpclassgenerator.rb +27 -0
  30. data/lib/ptero/generators/phpgenerator.rb +18 -0
  31. data/lib/ptero/generators/phpinfogenerator.rb +16 -0
  32. data/lib/ptero/generators/routesgenerator.rb +68 -0
  33. data/lib/ptero/generators/setupgenerator.rb +13 -0
  34. data/lib/ptero/generators/stylesheetgenerator.rb +33 -0
  35. data/lib/ptero/generators/twiggenerator.rb +24 -0
  36. data/lib/ptero/generators/viewgenerator.rb +24 -0
  37. data/lib/ptero/templates/applicationjavascriptgenerator.js.erb +11 -0
  38. data/lib/ptero/templates/applicationstylesheetgenerator.css.erb +40 -0
  39. data/lib/ptero/templates/configgenerator.php.erb +26 -0
  40. data/lib/ptero/templates/controllergenerator.php.erb +25 -0
  41. data/lib/ptero/templates/generator.txt.erb +1 -0
  42. data/lib/ptero/templates/htaccessgenerator.htaccess.erb +9 -0
  43. data/lib/ptero/templates/javascriptgenerator.js.erb +11 -0
  44. data/lib/ptero/templates/landinggenerator.php.erb +3 -0
  45. data/lib/ptero/templates/layoutgenerator.html.twig.erb +37 -0
  46. data/lib/ptero/templates/loadallgenerator.php.erb +12 -0
  47. data/lib/ptero/templates/modelgenerator.php.erb +21 -0
  48. data/lib/ptero/templates/pagegenerator.html.twig.erb +13 -0
  49. data/lib/ptero/templates/pagenotfoundgenerator.html.twig.erb +13 -0
  50. data/lib/ptero/templates/phpclassgenerator.php.erb +24 -0
  51. data/lib/ptero/templates/phpgenerator.php.erb +3 -0
  52. data/lib/ptero/templates/phpinfogenerator.php.erb +3 -0
  53. data/lib/ptero/templates/routesgenerator.php.erb +13 -0
  54. data/lib/ptero/templates/setupgenerator.php.erb +19 -0
  55. data/lib/ptero/templates/stylesheetgenerator.css.erb +8 -0
  56. data/lib/ptero/templates/twiggenerator.html.twig.erb +5 -0
  57. data/lib/ptero/templates/viewgenerator.html.twig.erb +13 -0
  58. data/lib/ptero/version.rb +4 -0
  59. data/ptero.gemspec +28 -0
  60. data/test/fixtures/test_generators_fixtures.yaml +74 -0
  61. data/test/test_application.rb +123 -0
  62. data/test/test_exceptions.rb +52 -0
  63. data/test/test_generators.rb +110 -0
  64. data/test/test_root.rb +212 -0
  65. metadata +212 -0
@@ -0,0 +1,35 @@
1
+ #
2
+ #
3
+ # cli.rb
4
+ # ======
5
+ #
6
+ # Superclass and container for all Thor command-line tools in Ptero
7
+ #
8
+
9
+ require 'thor'
10
+
11
+ module Ptero
12
+ # The superclass of all Ptero command-line interfaces
13
+ class CLI < Thor
14
+
15
+
16
+
17
+
18
+
19
+
20
+ # eigenclass for autoloading
21
+ class << self
22
+
23
+ # autoload and return any cli that is missing
24
+ # @param const_name [Symbol] the name of the constant to be found
25
+ def const_missing(const_name)
26
+ # Load the cli
27
+ require "#{__dir__}/cli/#{const_name.downcase}.rb"
28
+ return const_get const_name
29
+ rescue LoadError
30
+ super
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,282 @@
1
+ #
2
+ #
3
+ # root.rb
4
+ # =======
5
+ # The root CLI that is run with arguments passed to the ptero tool
6
+ #
7
+ #
8
+
9
+ require 'colorize'
10
+
11
+ # The principal Ptero CLI, the one that is called with the 'ptero' command
12
+ class Ptero::CLI::Root < Ptero::CLI
13
+ desc 'new NAME', 'Create a new application called NAME'
14
+ long_desc <<-LONGDESC
15
+ `$ ptero new NAME` will create a new directory called NAME in the current working directory and
16
+ set up a new Dinosaur PHP project inside the directory. The ptero tool:
17
+
18
+ \n\n* Creates a new directory, called NAME, in the current pwd
19
+ \n\n* Initializes a basic composer.json file in the root of the new project that includes all Dinosaur dependencies, as well as project metadata for future use
20
+ \n\n* After checking for the presence of PHP, installs the composer.phar executable and uses it to install all Dinosaur dependencies
21
+ \n\n* Generates basic templates for models, views, and controllers for the beginning of the project
22
+
23
+ \n\nAfter this command is executed, you can use further `$ ptero generate` commands to create your project content once in the root of your project
24
+ LONGDESC
25
+ # Seed a new Application, then download composer.phar and all dependencies
26
+ # @param name [String] the name of the new application
27
+ def new(name)
28
+ seed(name)
29
+ Dir.chdir name do
30
+ composer()
31
+ install()
32
+ end
33
+ puts "Change your working directory to '#{Dir.pwd}/#{name}' and starting generating files with 'ptero generate' to start your application"
34
+ end
35
+
36
+ desc 'seed NAME', 'Seed a new application'
37
+ long_desc <<-LONGDESC
38
+ `$ ptero seed NAME` generates the necessary files for a new application in a new directory called NAME. It generates all default models, views, and controllers
39
+ without accessing the internet. This means that it does not download composer and does not install composer dependencies, but performs all other application setup.
40
+ The execution of new consists of calling seed NAME, then changing to the new project's directory and calling composer, then install.
41
+ LONGDESC
42
+ # Seed a new Application by generating a new application directory and generating all necessary files, without accessing the internet or getting dependencies
43
+ # @param name [String] the name of the new application
44
+ def seed(name)
45
+ Ptero::Application.create(name) do |app|
46
+
47
+ # PHP
48
+ Dir.mkdir 'php'
49
+ app.generate('PHP_Info')
50
+ app.generate_setup(app.name.downcase)
51
+ app.generate_routes
52
+
53
+ Dir.mkdir 'php/controllers'
54
+ app.generate_controller('Application','\Dinosaur\\')
55
+ app.generate_load_all('php/controllers','ApplicationController')
56
+
57
+ Dir.mkdir 'php/models'
58
+ app.generate_load_all('php/models')
59
+
60
+ # Configuration
61
+ Dir.mkdir 'config'
62
+ app.generate_config
63
+
64
+ # Templates
65
+ Dir.mkdir 'views'
66
+ app.generate_layout
67
+ app.generate_page_not_found
68
+
69
+ # Document Root
70
+ Dir.mkdir 'www'
71
+ app.generate('HTAccess')
72
+ app.generate_landing
73
+
74
+ # CSS, Javascripts, Images
75
+ Dir.mkdir 'www/assets'
76
+
77
+ # CSS
78
+ Dir.mkdir 'www/assets/css'
79
+ app.generate_application_stylesheet('application','This is the main CSS stylesheet for application-wide styles. It is included in all pages by default.')
80
+
81
+ # JS
82
+ Dir.mkdir 'www/assets/js'
83
+ app.generate_application_javascript('application','This is the main file that is loaded along with every page. Put application-wide behavior here.')
84
+
85
+ # Images
86
+ Dir.mkdir 'www/assets/images'
87
+
88
+ end
89
+ end
90
+
91
+ desc 'route PATH, CONTROLLER', 'add a new route'
92
+ long_desc <<-LONGDESC
93
+ 'ptero route PATH, CONTROLLER' saves all current routes to a new RoutesGenerator object, adds the new route specified by PATH => CONTROLLER to this generator,
94
+ then removes and regenerates the routes.php file with the new route in place.
95
+ LONGDESC
96
+ # Add a new route to the Application routes file
97
+ # @param path [String] the path to route
98
+ # @param controller [String] the name of the controller to route
99
+ def route(path,controller)
100
+ verify do
101
+ app.route(path,controller)
102
+ end
103
+ end
104
+
105
+ desc 'unroute PATH', 'remove the PATH route in the routes.php file'
106
+ long_desc <<-LONGDESC
107
+ 'ptero unroute PATH' performs the inverse of the 'ptero route PATH, CONTROLLER' command. It captures all existing routes in a RoutesGenerator object,
108
+ removes the route specified by PATH from this list, then removes and regenerates the routes.php file without the named route.
109
+ LONGDESC
110
+ # Remove a route from the Application routes file
111
+ # @param path [String] the path to unroute
112
+ def unroute(path)
113
+ verify do
114
+ app.unroute(path)
115
+ end
116
+ end
117
+
118
+ desc 'verify DIR', "Verify that DIR is a dinosaur project"
119
+ long_desc <<-LONGDESC
120
+ `$ ptero verify DIR` checks the dinosaur.root property DIR/composer.json for a non-null, non-false value that signifies that DIR is the root directory of a dinosaur project.
121
+ \n\nIf no DIR is specified, the current working directory is used, so running `$ ptero verify` is a quick way to check if the current directory is a dinosaur project
122
+ LONGDESC
123
+ # Tell the user whether or not the specified directory is a Ptero directory.
124
+ # Print "#{dir} is a dinosaur directory" if so or call ptero_dir_message if not
125
+ # If a block is given and dir is a dinosaur directory, yield control to the block (without arguments) rather than displaying output
126
+ # @param dir [String,Pathname] the directory to check
127
+ def verify(dir=Dir.pwd)
128
+ a = Ptero::Application.new(dir)
129
+ if a.verify
130
+ if block_given?
131
+ yield
132
+ else
133
+ puts "#{dir} is a dinosaur project root.".green
134
+ end
135
+ true
136
+ else
137
+ ptero_dir_message
138
+ false
139
+ end
140
+ end
141
+
142
+ desc 'destroy DIR', "Delete DIR"
143
+ long_desc <<-LONGDESC
144
+ After asking for confirmation, ptero destroy DIR will recursively remove an entire dinosaur application folder. DIR is set to the current working directory by default
145
+ LONGDESC
146
+ # Destroy dir if dir is a dinosaur directory
147
+ # This method will ask for confirmation by prompting to the user to type "Yes" (case sensitive) before destroying the directory.
148
+ # If the user types this exactly, destroy will recursively delete dir and
149
+ # print "Removed dir".green. If the user types anything else, the method will exit without output.
150
+ # If the call to verify fails and the application is not a dinosaur directory, destroy prints "Cannot destroy dir because it is not a dinosaur directory".red
151
+ # and exits.
152
+ # @param dir [String,Pathname] the directory to destroy
153
+ def destroy(dir = Dir.pwd)
154
+ a = Ptero::Application.new(dir)
155
+ if a.verify
156
+ puts "Are you sure you want to destroy #{dir}? Type 'Yes' to authorize".yellow
157
+ print '>> '
158
+ return unless $stdin.gets.chomp == 'Yes'
159
+ a.destroy
160
+ puts "Removed #{dir}".green
161
+
162
+ else
163
+ puts "Cannot destroy #{dir} because it is not a dinosaur directory.".red
164
+ end
165
+ end
166
+
167
+ desc 'install', 'Install dependencies'
168
+ long_desc <<-LONGDESC
169
+ Use the composer.json file to install dependencies
170
+ LONGDESC
171
+ # Call the appropriate Application methods on the current Application to install Composer dependencies.
172
+ # If verify returns false, install quits with the ptero_dir_message text
173
+ def install
174
+ verify do
175
+ app.install_dependencies
176
+ end
177
+ end
178
+
179
+ desc 'composer', 'Download composer.phar'
180
+ long_desc <<-LONGDESC
181
+ Download the compser.phar file in order to facilitate future downloads
182
+ LONGDESC
183
+ # Call the appropriate Application methods on the current Application to download composer.phar.
184
+ # If verify returns false, composer quits with the ptero_dir_message text
185
+ def composer
186
+ verify do
187
+ app.get_composer
188
+ end
189
+ end
190
+
191
+
192
+ desc 'generate TYPE, *ARGS', 'Generate a file of type TYPE with ARGS'
193
+ long_desc <<-LONGDESC
194
+ The generate command creates a new file from ptero-generator TYPE, names it name, and passes it the following arguments as generator params.
195
+ This often results in the creation of a file called NAME + TYPE in a specific directory, as determined by the generator. For a more concrete example:
196
+ \n\n `$ ptero generate controller blog`
197
+ \n\n results in the creation of the file ./php/controllers/BlogController.php. Default output from the generate command will tell you what file was generated.
198
+ LONGDESC
199
+ # Use the appropriate Application methods to generate new files and components.
200
+ # As with other methods, this one runs a check with the verify method before undertaking any action
201
+ # @param type [String] the type of file to generate
202
+ # @param *args other parameters for the generator
203
+ def generate(type,*args)
204
+ verify do
205
+ app.generate(type,*args)
206
+ end
207
+ end
208
+
209
+
210
+ desc 'reload TYPE, *ARGS', 'Remove and regenerate TYPE,*ARGS'
211
+ long_desc <<-LONGDESC
212
+ The reload command removes and regenerates generator TYPE with *ARGS.
213
+ ** USE WITH CAUTION ON UNCOMMITTED FILES, THIS WILL OVERWRITE EXISTING CHANGES **
214
+ LONGDESC
215
+ # Use the Application object to remove and regenerate a file or component
216
+ # As with other methods, this one runs a check with the verify method before undertaking any action
217
+ # @param type [String] the type of file to reload
218
+ # @param *args other parameters
219
+ def reload(type,*args)
220
+ verify do
221
+ app.reload(type,*args)
222
+ end
223
+ end
224
+
225
+ desc 'remove TYPE, NAME', 'Remove a file'
226
+ long_desc <<-LONGDESC
227
+ The remove command is the inverse of the generate command. (see $ ptero help generate) It locates the file that would be generated from a $ ptero generate command and
228
+ deletes it. Like the generate command, the remove command will tell you what file(s) it has removed.
229
+ LONGDESC
230
+ # Reload is the inverse of generate. Use the Application object to remove a file.
231
+ # It runs verify on the Application before taking any action.
232
+ # @param type [String] the type of file to remove
233
+ # @param *args other arguments to remove the file
234
+ def remove(type,*args)
235
+ verify do
236
+ app.remove(type,*args)
237
+ end
238
+ end
239
+
240
+
241
+ desc 'routes', 'Display current routes'
242
+ long_desc <<-LONGDESC
243
+ The routes command takes no arguments and displays all current routes in the application.
244
+ LONGDESC
245
+ # If verify succeeds, display all routes of the current application
246
+ def routes
247
+ verify do
248
+ app.routes.each_pair do |key,value|
249
+ puts "#{key} => #{value}Controller"
250
+ end
251
+ end
252
+ end
253
+
254
+ desc 'version', 'Display Ptero version number'
255
+ long_desc <<-LONGDESC
256
+ Use the Ptero::VERSION constant to represent the version number
257
+ LONGDESC
258
+ # Print the Ptero version number
259
+ def version
260
+ puts Ptero::VERSION
261
+ end
262
+
263
+ private
264
+ @@cache = {}
265
+ def app
266
+ if @@cache[Pathname.new(Dir.pwd)]
267
+ @@cache[Pathname.new(Dir.pwd)]
268
+ else
269
+ @@cache[Pathname.new(Dir.pwd)] = Ptero::Application.new(Dir.pwd)
270
+ end
271
+ end
272
+
273
+ def ptero_dir_message
274
+ puts
275
+ puts 'The current directory is not a dinosaur root directory.'
276
+ puts 'Run the following command:'
277
+ puts ' $ ptero new NAME'
278
+ puts 'Then run the desired command inside the NAME directory.'
279
+ puts
280
+ end
281
+
282
+ end
@@ -0,0 +1,8 @@
1
+ {
2
+ "require": {
3
+ "luminousrubyist/dinosaur": "dev-master"
4
+ },
5
+
6
+ "minimum-stability": "dev",
7
+
8
+ }
@@ -0,0 +1,26 @@
1
+ #
2
+ # exception.rb
3
+ # ============
4
+ # The superclass of all Ptero-related exceptions
5
+ #
6
+ #
7
+
8
+ # The superclass of Ptero-related Exceptions
9
+ class Ptero::Exception < StandardError
10
+
11
+
12
+ class << self
13
+ # Autoload exceptions
14
+ def const_missing(const_name)
15
+ # Require the exception
16
+ require "#{__dir__}/exceptions/#{const_name.downcase}.rb"
17
+ return const_get const_name
18
+ # If we couldn't load the file, throw an error
19
+ rescue LoadError
20
+ super
21
+ end
22
+ end
23
+
24
+
25
+
26
+ end
@@ -0,0 +1,9 @@
1
+ #
2
+ # applicationexception.rb
3
+ # =======================
4
+ #
5
+
6
+ # An Application-related exception
7
+ class Ptero::Exception::ApplicationException < Ptero::Exception
8
+
9
+ end
@@ -0,0 +1,9 @@
1
+ #
2
+ # pteroexception.rb
3
+ # =================
4
+ #
5
+
6
+ # A Generator-related exception
7
+ class Ptero::Exception::GeneratorException < Ptero::Exception
8
+
9
+ end
@@ -0,0 +1,146 @@
1
+ #
2
+ #
3
+ # Generator.rb
4
+ # ============
5
+ #
6
+ # Generates files from templates
7
+ #
8
+ require 'ptero'
9
+ require 'erubis'
10
+ require 'pathname'
11
+ require 'colorize'
12
+ require 'mute'
13
+
14
+ module Ptero
15
+ # An object that generates files for an application
16
+ class Generator
17
+
18
+ # Input the generator's name and the Application to generate for
19
+ # @param name [String] the generator's name
20
+ # @param app [Application] the application in which to generate files
21
+ def initialize(name,app=Application.app_for(Dir.pwd))
22
+ @name = name
23
+ @dir = Pathname.new(app.dir)
24
+ @app = app
25
+ end
26
+
27
+ attr_reader :name, :app, :dir
28
+
29
+ # The filename of the generated file
30
+ # @return [String] an unqualifed filename, name and extension
31
+ def filename
32
+ "#{@name}.#{extension}"
33
+ end
34
+
35
+ # The extension of the file to be generated
36
+ # @return [String] "txt"
37
+ def extension
38
+ 'txt'
39
+ end
40
+
41
+ # The unqualified name of the class, e.g. 'Controller' for an object of class Ptero::Generator::ControllerGenerator
42
+ # @return [String] the unqualified name of the class
43
+ def type
44
+ self.class.name.split('::').last
45
+ end
46
+
47
+ # Simple string representation of this object, represented by the unqualified class name and filename of the current object
48
+ # @return [String] a string representation of this object, of type "[type - filename]"
49
+ def to_s
50
+ "[#{type} - #{filename}]"
51
+ end
52
+
53
+ # Default path to write to, used along with filename to determine the destination of the generated file\
54
+ # @return [String] the empty string
55
+ def path
56
+ ''
57
+ end
58
+
59
+ # The fully-qualified filename of the file to be generated by this object.
60
+ # @return [String] a fully-qualified pathname to the file to be generated by this object.
61
+ def location
62
+ dir.join(path).join(filename)
63
+ end
64
+
65
+ # The path to the directory where Ptero templates are stored
66
+ # @return [String] the aforementioned path
67
+ def template_path
68
+ Pathname.new("#{Ptero::TEMPLATE_PATH}/#{self.class.name.split('::').last.downcase}.#{extension}.erb")
69
+ end
70
+
71
+ # Generate a file and print the location of the generated file
72
+ # @return [Generator] self
73
+ def generate
74
+ loc = location
75
+ raise Ptero::Exception::GeneratorException, "Generator is already generated: #{self}" if generated?
76
+ unless loc.dirname.exist?
77
+ loc.dirname.descend do |dir|
78
+ Dir.mkdir dir unless dir.exist?
79
+ end
80
+ end
81
+ File.open(loc,'w') do |file|
82
+ file.puts content
83
+ end
84
+ puts "GENERATE - #{self}".green
85
+ self
86
+
87
+ end
88
+
89
+ # Remove the file corresponding to self and print its location
90
+ # @return [Generator] self
91
+ def remove
92
+ loc = location
93
+ raise Ptero::Exception::GeneratorException, "Cannot remove because generator is already generated: #{self}" unless generated?
94
+ File.unlink(loc);
95
+ puts "REMOVE - #{self}".red
96
+ self
97
+ end
98
+
99
+ # Find out if this Generator's file is generated
100
+ # @return [Boolean] Does the file exist?
101
+ def generated?
102
+ File.exist? location
103
+ end
104
+
105
+ # Remove and regenerate and print the regenerated file
106
+ # @return [Generator] self
107
+ def reload
108
+ Mute::IO.capture_stdout do
109
+ remove if generated?
110
+ generate
111
+ end
112
+ puts "RELOAD - #{self}".blue
113
+ self
114
+ end
115
+
116
+ # Return the content of the file to be generated by inputting content_params into an erubis template
117
+ # @return [String] the content of the file to be generated
118
+ def content
119
+ File.open(template_path, 'r') do |file|
120
+ eruby = Erubis::Eruby.new(file.read)
121
+ eruby.evaluate(content_params)
122
+ end
123
+ end
124
+ # The context for generating a template, default to self
125
+ # @return [Generator] self
126
+ def content_params
127
+ self
128
+ end
129
+
130
+
131
+
132
+ class << self
133
+ # Autoload Generators
134
+ def const_missing(const_name)
135
+ # Require the generator
136
+ require "#{__dir__}/generators/#{const_name.downcase}.rb"
137
+ return const_get const_name
138
+ # If we couldn't load the file, throw an error
139
+ rescue LoadError
140
+ super
141
+ end
142
+ end
143
+ end
144
+
145
+
146
+ end