Sutto-perennial 0.1.0 → 0.2.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.
- data/bin/perennial +36 -0
- data/lib/perennial/application.rb +149 -0
- data/lib/perennial/generator.rb +89 -0
- data/lib/perennial/loader.rb +1 -1
- data/lib/perennial/manifest.rb +1 -0
- data/lib/perennial/option_parser.rb +13 -12
- data/lib/perennial/settings.rb +8 -0
- data/lib/perennial.rb +2 -2
- metadata +5 -2
data/bin/perennial
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require File.join(File.dirname(__FILE__), "..", "lib", "perennial")
|
3
|
+
|
4
|
+
Perennial::Application.processing(ARGV) do |a|
|
5
|
+
|
6
|
+
a.banner = "Perennial v#{Perennial::VERSION} CLI"
|
7
|
+
|
8
|
+
a.generator!
|
9
|
+
|
10
|
+
a.option(:force, "force the creation of the application")
|
11
|
+
a.add("create PATH [APP-NAME]", "Creates a Perennial-based library with a given PATH and, if provided, APP-NAME.") do |path, *args|
|
12
|
+
# Get the app name, path etc.
|
13
|
+
opts = args.extract_options!
|
14
|
+
app_name = args.empty? ? File.basename(path) : args.shift
|
15
|
+
path = File.expand_path(path)
|
16
|
+
# Check if the folder exists
|
17
|
+
if File.exist?(path) && !opts[:force]
|
18
|
+
puts "The path you tried to use, #{path}, already exists. Please try another or pass --force"
|
19
|
+
exit
|
20
|
+
end
|
21
|
+
# Convert the name and class name.
|
22
|
+
app_path = app_name.underscore
|
23
|
+
app_module = app_name.camelize
|
24
|
+
# Actually do the generation.
|
25
|
+
env = {:application_module => app_module, :application_path => app_path}
|
26
|
+
setup_generator path
|
27
|
+
folders 'tmp', 'config', 'lib', 'handlers', 'test'
|
28
|
+
template 'application.erb', "lib/#{app_path}.rb", env
|
29
|
+
template 'boot.erb', 'config/boot.rb', env
|
30
|
+
template 'setup.erb', 'config/setup.rb', env
|
31
|
+
template 'rakefile.erb', 'Rakefile', env
|
32
|
+
template 'test_helper.erb', 'test/test_helper.rb', env
|
33
|
+
template 'test.erb', "test/#{app_path}_test.rb", env
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
module Perennial
|
2
|
+
class Application
|
3
|
+
|
4
|
+
class CommandEnv
|
5
|
+
include Loggable
|
6
|
+
|
7
|
+
# Executes a given block with given
|
8
|
+
# arguments in a specified environment.
|
9
|
+
# Used to provide the logger functionality
|
10
|
+
# as well as the ability to do things
|
11
|
+
# such as easily write generators.
|
12
|
+
def self.execute(blk, arguments)
|
13
|
+
klass = Class.new(self)
|
14
|
+
klass.class_eval { define_method(:apply, &blk) }
|
15
|
+
klass.new.apply(*arguments)
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_accessor :options, :banner, :command_env
|
21
|
+
|
22
|
+
def initialize(opts = {})
|
23
|
+
@options = opts
|
24
|
+
@commands = {}
|
25
|
+
@descriptions = {}
|
26
|
+
@option_parsers = {}
|
27
|
+
@option_parser = nil
|
28
|
+
@command_options = {}
|
29
|
+
@command_env = (opts[:command_env] || CommandEnv)
|
30
|
+
@banners = {}
|
31
|
+
end
|
32
|
+
|
33
|
+
def option(name, description = nil)
|
34
|
+
option_parser.add(name, description) { |v| @command_options[name] = v }
|
35
|
+
end
|
36
|
+
|
37
|
+
def add_default_options!
|
38
|
+
option_parser.add_defaults!
|
39
|
+
end
|
40
|
+
|
41
|
+
def generator!
|
42
|
+
self.command_env = Perennial::Generator::CommandEnv
|
43
|
+
end
|
44
|
+
|
45
|
+
def add(raw_command, description = nil, &blk)
|
46
|
+
raise ArgumentError, "You must provide a block with an #{self.class.name}#add" if blk.nil?
|
47
|
+
raise ArgumentError, "Your block must accept atleast one argument (a hash of options)" if blk.arity == 0
|
48
|
+
command, _ = raw_command.split(" ", 2)
|
49
|
+
@banners[command] = raw_command
|
50
|
+
@commands[command] = blk
|
51
|
+
@descriptions[command] = description if description.present?
|
52
|
+
# Add the default help message for a command
|
53
|
+
option_parser.add(:help, "Show this message") { help_for(command) }
|
54
|
+
@option_parsers[command] = option_parser
|
55
|
+
@option_parser = nil
|
56
|
+
end
|
57
|
+
|
58
|
+
def execute(arguments)
|
59
|
+
return usage if arguments.empty?
|
60
|
+
arguments = arguments.dup
|
61
|
+
command = arguments.shift
|
62
|
+
if @commands.has_key?(command)
|
63
|
+
execute_command(command, arguments)
|
64
|
+
else
|
65
|
+
puts "Unknown command '#{command}', please try again."
|
66
|
+
return usage
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def usage
|
71
|
+
if banner.present?
|
72
|
+
puts banner
|
73
|
+
puts ""
|
74
|
+
end
|
75
|
+
puts "Usage:"
|
76
|
+
max_width = @banners.values.map { |b| b.length }.max
|
77
|
+
@commands.keys.sort.each do |command|
|
78
|
+
next unless @descriptions.has_key?(command)
|
79
|
+
formatted_command = "#{@banners[command]} [OPTIONS]".ljust(max_width + 10)
|
80
|
+
command = "%s - %s" % [formatted_command, @descriptions[command]]
|
81
|
+
puts command
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def help_for(command)
|
86
|
+
if banner.present?
|
87
|
+
puts banner
|
88
|
+
puts ""
|
89
|
+
end
|
90
|
+
puts @descriptions[command]
|
91
|
+
puts "Usage: #{$0} #{@banners[command]} [options]"
|
92
|
+
puts "Options:"
|
93
|
+
puts @option_parsers[command].summary
|
94
|
+
exit
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.processing(args, &blk)
|
98
|
+
application = self.new
|
99
|
+
if blk.arity == 1
|
100
|
+
blk.call(application)
|
101
|
+
else
|
102
|
+
application.instance_eval(&blk)
|
103
|
+
end
|
104
|
+
application.execute args
|
105
|
+
end
|
106
|
+
|
107
|
+
protected
|
108
|
+
|
109
|
+
def execute_command(command, arguments)
|
110
|
+
command_proc = @commands[command]
|
111
|
+
args, opts = extract_arguments(command, arguments)
|
112
|
+
if valid_arity?(command_proc, args)
|
113
|
+
args << opts
|
114
|
+
@command_env.execute(command_proc, args)
|
115
|
+
else
|
116
|
+
usage
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def extract_arguments(command, arguments)
|
121
|
+
option_parser = @option_parsers[command]
|
122
|
+
if option_parser.present?
|
123
|
+
option_parser.parse(arguments)
|
124
|
+
return option_parser.arguments, @command_options
|
125
|
+
else
|
126
|
+
return arguments, {}
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def option_parser(reset = false)
|
131
|
+
@option_parser = nil if reset
|
132
|
+
@option_parser ||= OptionParser.new
|
133
|
+
end
|
134
|
+
|
135
|
+
def valid_arity?(blk, arguments)
|
136
|
+
needed_count = blk.arity - 1
|
137
|
+
provided_count = arguments.size
|
138
|
+
if needed_count > 0 && needed_count != provided_count
|
139
|
+
puts "You didn't provide the correct number of arguments (needed #{needed_count}, provided #{provided_count})"
|
140
|
+
elsif needed_count < 0 && (-needed_count - 2) > provided_count
|
141
|
+
puts "You didn't provide enough arguments - a minimum of #{-needed_count} are needed."
|
142
|
+
else
|
143
|
+
return true
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'erb'
|
4
|
+
|
5
|
+
module Perennial
|
6
|
+
class Generator
|
7
|
+
|
8
|
+
class CommandEnv < Perennial::Application::CommandEnv
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@generator = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
protected
|
15
|
+
|
16
|
+
def setup_generator(*args)
|
17
|
+
@generator = Generator.new(*args)
|
18
|
+
end
|
19
|
+
|
20
|
+
def method_missing(name, *args, &blk)
|
21
|
+
if @generator && @generator.respond_to?(name)
|
22
|
+
@generator.send(name, *args, &blk)
|
23
|
+
else
|
24
|
+
super
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_accessor :template_path, :destination_path
|
31
|
+
|
32
|
+
def initialize(destination, opts = {})
|
33
|
+
@destination_path = destination
|
34
|
+
@template_path = opts[:template_path] || File.join(Settings.library_root, "templates")
|
35
|
+
puts "Starting generator for #{destination}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def download(from, to)
|
39
|
+
describe "Downloading #{from}"
|
40
|
+
file to, open(from).read
|
41
|
+
end
|
42
|
+
|
43
|
+
def folders(*args)
|
44
|
+
args.each do |f|
|
45
|
+
describe "Creating folder #{f}"
|
46
|
+
FileUtils.mkdir_p(expand_destination_path(f))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def file(name, contents)
|
51
|
+
dest_folder = File.dirname(name)
|
52
|
+
folders(dest_folder) unless File.directory?(expand_destination_path(dest_folder))
|
53
|
+
describe "Creating file #{name}"
|
54
|
+
File.open(expand_destination_path(name), "w+") do |f|
|
55
|
+
f.write(contents)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def template(source, destination, environment = {})
|
60
|
+
describe "Processing template #{source}"
|
61
|
+
raw_template = File.read(expand_template_path(source))
|
62
|
+
processed_template = ERB.new(raw_template).result(binding_for(environment))
|
63
|
+
file destination, processed_template
|
64
|
+
end
|
65
|
+
|
66
|
+
protected
|
67
|
+
|
68
|
+
def binding_for(hash = {})
|
69
|
+
object = Object.new
|
70
|
+
hash.each_pair do |k, v|
|
71
|
+
object.instance_variable_set("@#{k}", v)
|
72
|
+
end
|
73
|
+
return object.send(:binding)
|
74
|
+
end
|
75
|
+
|
76
|
+
def expand_template_path(p)
|
77
|
+
File.expand_path(p, @template_path)
|
78
|
+
end
|
79
|
+
|
80
|
+
def expand_destination_path(p)
|
81
|
+
File.expand_path(p, @destination_path)
|
82
|
+
end
|
83
|
+
|
84
|
+
def describe(action)
|
85
|
+
puts "- #{action}"
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
data/lib/perennial/loader.rb
CHANGED
data/lib/perennial/manifest.rb
CHANGED
@@ -18,6 +18,7 @@ module Perennial
|
|
18
18
|
Manifest.namespace = self
|
19
19
|
Manifest.app_name = self.name.to_s.underscore
|
20
20
|
parent_folder = __DIR__(1)
|
21
|
+
Settings.library_root = parent_folder
|
21
22
|
attempt_require parent_folder / 'core_ext', parent_folder / 'exceptions'
|
22
23
|
unless blk.nil?
|
23
24
|
args = []
|
@@ -1,7 +1,5 @@
|
|
1
|
-
require 'singleton'
|
2
1
|
module Perennial
|
3
2
|
class OptionParser
|
4
|
-
include Singleton
|
5
3
|
|
6
4
|
attr_reader :arguments
|
7
5
|
|
@@ -32,7 +30,7 @@ module Perennial
|
|
32
30
|
max_length = [text.size, max_length].max
|
33
31
|
output << [text, @descriptions[name]]
|
34
32
|
end
|
35
|
-
output.map { |text, description| "#{text
|
33
|
+
output.map { |text, description| "#{text.ljust(max_length)} - #{description}" }.join("\n")
|
36
34
|
end
|
37
35
|
|
38
36
|
def parse(arguments = ARGV)
|
@@ -48,7 +46,7 @@ module Perennial
|
|
48
46
|
end
|
49
47
|
|
50
48
|
def self.method_missing(name, *args, &blk)
|
51
|
-
self.
|
49
|
+
self.default.send(name, *args, &blk)
|
52
50
|
end
|
53
51
|
|
54
52
|
# Over ride with your apps custom banner
|
@@ -78,17 +76,20 @@ module Perennial
|
|
78
76
|
@defaults_added = true
|
79
77
|
end
|
80
78
|
|
81
|
-
def
|
82
|
-
return if defined?(@
|
83
|
-
|
84
|
-
@setup = true
|
79
|
+
def self.default
|
80
|
+
return @default if defined?(@default) && @default.present?
|
81
|
+
@default = setup_default!
|
85
82
|
end
|
86
83
|
|
87
|
-
def
|
88
|
-
|
84
|
+
def parse_argv(with = default)
|
85
|
+
with.parse
|
86
|
+
ARGV.replace with.arguments
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.setup_default!
|
90
|
+
opts = self.new
|
89
91
|
opts.add_defaults!
|
90
|
-
opts
|
91
|
-
ARGV.replace opts.arguments
|
92
|
+
return opts
|
92
93
|
end
|
93
94
|
|
94
95
|
protected
|
data/lib/perennial/settings.rb
CHANGED
@@ -27,6 +27,14 @@ module Perennial
|
|
27
27
|
@@root ||= File.expand_path(File.dirname(__FILE__) / ".." / "..")
|
28
28
|
end
|
29
29
|
|
30
|
+
def library_root=(path)
|
31
|
+
@@library_root = path.to_str
|
32
|
+
end
|
33
|
+
|
34
|
+
def library_root
|
35
|
+
@@library_root ||= File.expand_path(File.dirname(__FILE__) / ".." / "..")
|
36
|
+
end
|
37
|
+
|
30
38
|
def setup?
|
31
39
|
@@setup ||= false
|
32
40
|
end
|
data/lib/perennial.rb
CHANGED
@@ -8,11 +8,11 @@ require 'perennial/exceptions'
|
|
8
8
|
|
9
9
|
module Perennial
|
10
10
|
|
11
|
-
VERSION = "0.
|
11
|
+
VERSION = "0.2.0"
|
12
12
|
|
13
13
|
has_libary :dispatchable, :hookable, :loader, :logger,
|
14
14
|
:loggable, :manifest, :settings, :argument_parser,
|
15
|
-
:option_parser
|
15
|
+
:option_parser, :application, :generator
|
16
16
|
|
17
17
|
def self.included(parent)
|
18
18
|
parent.extend(Manifest::Mixin)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: Sutto-perennial
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Darcy Laycock
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-08-
|
12
|
+
date: 2009-08-08 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -22,6 +22,7 @@ extensions: []
|
|
22
22
|
extra_rdoc_files: []
|
23
23
|
|
24
24
|
files:
|
25
|
+
- bin/perennial
|
25
26
|
- vendor/fakefs
|
26
27
|
- vendor/fakefs/lib
|
27
28
|
- vendor/fakefs/lib/fakefs.rb
|
@@ -32,6 +33,7 @@ files:
|
|
32
33
|
- vendor/fakefs/test/fakefs_test.rb
|
33
34
|
- vendor/fakefs/test/verify.rb
|
34
35
|
- lib/perennial
|
36
|
+
- lib/perennial/application.rb
|
35
37
|
- lib/perennial/argument_parser.rb
|
36
38
|
- lib/perennial/core_ext
|
37
39
|
- lib/perennial/core_ext/attribute_accessors.rb
|
@@ -41,6 +43,7 @@ files:
|
|
41
43
|
- lib/perennial/daemon.rb
|
42
44
|
- lib/perennial/dispatchable.rb
|
43
45
|
- lib/perennial/exceptions.rb
|
46
|
+
- lib/perennial/generator.rb
|
44
47
|
- lib/perennial/hookable.rb
|
45
48
|
- lib/perennial/loader.rb
|
46
49
|
- lib/perennial/loggable.rb
|