Sutto-perennial 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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