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 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
@@ -32,7 +32,7 @@ module Perennial
32
32
 
33
33
  def run!
34
34
  self.register_signals
35
- OptionParser.setup
35
+ OptionParser.parse_argv
36
36
  self.class.invoke_hooks! :before_setup
37
37
  Daemon.daemonize! if Settings.daemon?
38
38
  Logger.log_name = "#{@@current_type.to_s}.log"
@@ -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}: ".ljust(max_length + 2) + description }.join("\n")
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.instance.send(name, *args, &blk)
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 setup
82
- return if defined?(@setup) && @setup
83
- setup!
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 self.setup!
88
- opts = self.instance
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.parse
91
- ARGV.replace opts.arguments
92
+ return opts
92
93
  end
93
94
 
94
95
  protected
@@ -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.1.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.1.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-01 00:00:00 -07:00
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