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.
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