perennial 0.2.2.2 → 1.0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,7 @@
1
1
  require 'open-uri'
2
2
  require 'fileutils'
3
3
  require 'erb'
4
+ require 'readline'
4
5
 
5
6
  module Perennial
6
7
  class Generator
@@ -32,12 +33,46 @@ module Perennial
32
33
  def initialize(destination, opts = {})
33
34
  @destination_path = destination
34
35
  @template_path = opts[:template_path] || File.join(Settings.library_root, "templates")
35
- puts "Starting generator for #{destination}"
36
+ @silent = !!opts[:silent]
37
+ describe "Initializing generator in #{destination}"
36
38
  end
37
39
 
38
- def download(from, to)
40
+ # Helpers for testing file state
41
+
42
+ def fu
43
+ FileUtils
44
+ end
45
+
46
+ def chmod(permissions, path)
47
+ describe "Changing permissions for #{path} to #{permissions}"
48
+ FileUtils.chmod(permissions, expand_destination_path(path))
49
+ end
50
+
51
+ def file?(path)
52
+ describe "Checking if #{path} is a file"
53
+ File.file?(expand_destination_path(path))
54
+ end
55
+
56
+ def executable?(path)
57
+ describe "Checking if #{path} is an executable"
58
+ File.executable?(expand_destination_path(path))
59
+ end
60
+
61
+ def directory?(path)
62
+ describe "Checking if #{path} is a directory"
63
+ File.directory?(expand_destination_path(path))
64
+ end
65
+
66
+ alias folder? directory?
67
+
68
+ def exists?(path)
69
+ describe "Checking if #{path} exists"
70
+ File.exits?(expand_destination_path(path))
71
+ end
72
+
73
+ def download(from, to, append = false)
39
74
  describe "Downloading #{from}"
40
- file to, open(from).read
75
+ file to, open(from).read, append
41
76
  end
42
77
 
43
78
  def folders(*args)
@@ -47,20 +82,20 @@ module Perennial
47
82
  end
48
83
  end
49
84
 
50
- def file(name, contents)
85
+ def file(name, contents, append = false)
51
86
  dest_folder = File.dirname(name)
52
87
  folders(dest_folder) unless File.directory?(expand_destination_path(dest_folder))
53
88
  describe "Creating file #{name}"
54
- File.open(expand_destination_path(name), "w+") do |f|
89
+ File.open(expand_destination_path(name), "#{append ? "a" : "w"}+") do |f|
55
90
  f.write(contents)
56
91
  end
57
92
  end
58
93
 
59
- def template(source, destination, environment = {})
94
+ def template(source, destination, environment = {}, append = false)
60
95
  describe "Processing template #{source}"
61
96
  raw_template = File.read(expand_template_path(source))
62
97
  processed_template = ERB.new(raw_template).result(binding_for(environment))
63
- file destination, processed_template
98
+ file destination, processed_template, append
64
99
  end
65
100
 
66
101
  protected
@@ -82,7 +117,7 @@ module Perennial
82
117
  end
83
118
 
84
119
  def describe(action)
85
- puts "- #{action}"
120
+ puts "[generator] #{action}" unless @silent
86
121
  end
87
122
 
88
123
  end
@@ -53,7 +53,10 @@ module Perennial
53
53
  end
54
54
 
55
55
  def current_controller
56
- @current_controller ||= @@controllers[@@current_type.to_sym]
56
+ @current_controller ||= begin
57
+ c = @@controllers[@@current_type.to_sym]
58
+ c.is_a?(String) ? eval(c) : c
59
+ end
57
60
  end
58
61
 
59
62
  protected
@@ -70,6 +73,7 @@ module Perennial
70
73
  handler_directory = Settings.root / "handlers"
71
74
  if File.directory?(handler_directory)
72
75
  Dir[handler_directory / "**" / "*.rb"].each do |handler|
76
+ Perennial::Reloading.watch(handler, handler_directory)
73
77
  require handler
74
78
  end
75
79
  end
@@ -5,8 +5,9 @@ module Perennial
5
5
 
6
6
  cattr_accessor :logger, :log_name
7
7
 
8
- @@log_name = "perennial.log"
9
- @@setup = false
8
+ @@log_name = "perennial.log"
9
+ @@setup = false
10
+ @@default_logger_path = nil
10
11
 
11
12
  class << self
12
13
 
@@ -19,9 +20,18 @@ module Perennial
19
20
  setup!
20
21
  end
21
22
 
23
+ def default_logger_path=(value)
24
+ @@default_logger_path = value
25
+ # Rereun setup if setup is already done.
26
+ setup! if setup?
27
+ end
28
+
29
+ def default_logger_path
30
+ @@default_logger_path || (Settings.root / "log" / @@log_name.to_str)
31
+ end
32
+
22
33
  def setup!
23
- log_path = Settings.root / "log" / @@log_name.to_str
24
- @@logger = new(log_path, Settings.log_level, Settings.verbose?)
34
+ @@logger = new(self.default_logger_path, Settings.log_level, Settings.verbose?)
25
35
  @@setup = true
26
36
  end
27
37
 
@@ -0,0 +1,217 @@
1
+ require 'yaml'
2
+ module Perennial
3
+ # A ninja hash. Like OpenStruct, but better
4
+ class Nash
5
+
6
+ def self.load_file(path)
7
+ n = self.new
8
+ if File.file?(path) && File.readable?(path)
9
+ contents = YAML.load_file(path)
10
+ end
11
+ if contents.is_a?(Hash)
12
+ contents.to_nash
13
+ else
14
+ new(:data => contents).normalized
15
+ end
16
+ end
17
+
18
+ attr_reader :table
19
+
20
+ def initialize(initial = {})
21
+ @table = {}
22
+ initial.to_hash.each_pair { |k,v| self[k] = v }
23
+ end
24
+
25
+ def [](key)
26
+ @table[real_key(key)]
27
+ end
28
+
29
+ def []=(key, *values)
30
+ @table.send(:[]=, real_key(key), *values)
31
+ end
32
+
33
+ def respond_to?(name, rec = nil)
34
+ true
35
+ end
36
+
37
+ def id
38
+ self.has_key?(:id) ? self.id : super
39
+ end
40
+
41
+ def dup
42
+ Nash.new(self.table)
43
+ end
44
+
45
+ def to_hash
46
+ @table.dup
47
+ end
48
+
49
+ def keys
50
+ @table.keys
51
+ end
52
+
53
+ def values
54
+ @table.values
55
+ end
56
+
57
+ def has_key?(key)
58
+ @table.has_key? real_key(key)
59
+ end
60
+
61
+ def has_value?(value)
62
+ @table.has_value? value
63
+ end
64
+
65
+ def each_pair
66
+ @table.each_pair { |k, v| yield k, v }
67
+ end
68
+
69
+ def each_key
70
+ @table.each_key { |k| yield k }
71
+ end
72
+
73
+ def each_value
74
+ @table.each_value { |v| yield v }
75
+ end
76
+
77
+ def delete(key)
78
+ @table.delete(real_key(key))
79
+ end
80
+
81
+ def merge!(hash_or_nash)
82
+ hash_or_nash.to_hash.each_pair do |k, v|
83
+ self[k] = v
84
+ end
85
+ return self
86
+ end
87
+
88
+ def merge(hash_or_nash)
89
+ dup.merge! hash_or_nash
90
+ end
91
+
92
+ def reverse_merge!(hash_or_nash)
93
+ replace Nash.new(hash_or_nash).merge!(self)
94
+ end
95
+
96
+ def reverse_merge(hash_or_nash)
97
+ dup.reverse_merge(hash_or_nash)
98
+ end
99
+
100
+ def replace(nash)
101
+ if nash.is_a?(self.class)
102
+ @table = nash.table
103
+ else
104
+ @table = {}
105
+ nash.to_hash.each_pair { |k, v| self[k] = v }
106
+ end
107
+ return self
108
+ end
109
+
110
+ def blank?
111
+ @table.blank?
112
+ end
113
+
114
+ def present?
115
+ @table.present?
116
+ end
117
+
118
+ def inspect
119
+ str = ""
120
+ if Thread.current[:inspect_stack].nil?
121
+ Thread.current[:inspect_stack] = [self]
122
+ str = _inspect
123
+ Thread.current[:inspect_stack] = nil
124
+ else
125
+ if Thread.current[:inspect_stack].include?(self)
126
+ return "..."
127
+ else
128
+ Thread.current[:inspect_stack] << self
129
+ str = _inspect
130
+ Thread.current[:inspect_stack].pop
131
+ end
132
+ end
133
+ return str
134
+ end
135
+
136
+ def _inspect
137
+ str = "#<Perennial::Nash:#{(object_id * 2).to_s(16)}"
138
+ if !blank?
139
+ str << " "
140
+ str << table.map { |k, v| "#{k}=#{v.inspect}" }.join(", ")
141
+ end
142
+ str << ">"
143
+ return str
144
+ end
145
+
146
+ def hash
147
+ @table.hash
148
+ end
149
+
150
+ def normalized(n = nil)
151
+ item = nil
152
+ if Thread.current[:normalized].nil?
153
+ n = self.class.new
154
+ Thread.current[:normalized] = {self => n}
155
+ item = normalize_nash(n)
156
+ Thread.current[:normalized] = nil
157
+ else
158
+ if Thread.current[:normalized].has_key?(self)
159
+ return Thread.current[:normalized][self]
160
+ else
161
+ n = self.class.new
162
+ Thread.current[:normalized][self] = n
163
+ item = normalize_nash(n)
164
+ end
165
+ end
166
+ item
167
+ end
168
+
169
+ protected
170
+
171
+ def normalize_nash(n = self.class.new)
172
+ each_pair do |k, v|
173
+ n[k] = normalize_item(v)
174
+ end
175
+ return n
176
+ end
177
+
178
+ def normalize_item(i)
179
+ case i
180
+ when Hash
181
+ self.class.new(i).normalized
182
+ when Array
183
+ i.map { |v| normalize_item(v) }
184
+ when self.class
185
+ i.normalized
186
+ else
187
+ i
188
+ end
189
+ end
190
+
191
+ def method_missing(name, *args, &blk)
192
+ name = name.to_s
193
+ case name.to_s[-1]
194
+ when ??
195
+ self[name[0..-2]].present?
196
+ when ?=
197
+ send(:[]=, real_key(name[0..-2]), *args)
198
+ when ?!
199
+ self[name[0..-2]] = self.class.new
200
+ else
201
+ self[name]
202
+ end
203
+ end
204
+
205
+ def real_key(name)
206
+ name.to_sym
207
+ end
208
+
209
+
210
+ end
211
+ end
212
+
213
+ class Hash
214
+ def to_nash
215
+ Perennial::Nash.new(self)
216
+ end
217
+ end
@@ -52,9 +52,9 @@ module Perennial
52
52
  def add_defaults!
53
53
  return if defined?(@defaults_added) && @defaults_added
54
54
  logger_levels = Logger::LEVELS.keys.map { |k| k.to_s }
55
- add(:daemon, 'Runs this application as a daemon') { Settings.daemon = true }
56
- add(:verbose, 'Runs this application verbosely, writing to STDOUT') { Settings.verbose = true }
57
- add(:log_level, "Sets this applications log level, one of: #{logger_levels.join(", ")}") do |level|
55
+ add(:daemon, 'Runs this application as a daemon', :shortcut => "d") { Settings.daemon = true }
56
+ add(:verbose, 'Runs this application verbosely, writing to STDOUT', :shortcut => "v") { Settings.verbose = true }
57
+ add(:log_level, "Sets this applications log level, one of: #{logger_levels.join(", ")}", :shortcut => "l") do |level|
58
58
  if logger_levels.include?(level)
59
59
  Settings.log_level = level.to_sym
60
60
  else
@@ -0,0 +1,45 @@
1
+ module Perennial
2
+ class Reloading
3
+
4
+ cattr_accessor :mapping, :mtimes
5
+ self.mapping = {}
6
+ self.mtimes = {}
7
+
8
+ def self.watch(file, relative_to = File.dirname(file))
9
+ file = File.expand_path(file)
10
+ raise ArgumentError, "You must provide the path to a file" unless File.file?(file)
11
+ relative = file.gsub(/^#{File.expand_path(relative_to)}\//, '')
12
+ name = relative.gsub(/\.rb$/, '').split("/").map { |part|part.camelize }.join("::")
13
+ self.mapping[name] = file
14
+ self.mtimes[file] = File.mtime(file)
15
+ end
16
+
17
+ def self.reload!
18
+ self.mapping.each_pair do |constant, file|
19
+ next unless File.mtime(file) > self.mtimes[file]
20
+ begin
21
+ # Get a relative name and namespace
22
+ parts = constant.split("::")
23
+ name = parts.pop
24
+ ns = parts.inject(Object) { |a, c| a.const_get(c) }
25
+ # Notify object pre-reload.
26
+ final = ns.const_get(name)
27
+ final.reloading! if final.respond_to?(:reloading!)
28
+ final = nil
29
+ # Remove the constant
30
+ ns.send(:remove_const, name)
31
+ load(file)
32
+ # Notify the object it was reloaded...
33
+ final = ns.const_get(name)
34
+ final.reloaded! if final.respond_to?(:reloaded!)
35
+ # Finally, update the mtime
36
+ self.mtimes[file] = File.mtime(file)
37
+ rescue Exception => e
38
+ logger.fatal "Exception reloading #{file} (for #{constant})"
39
+ Perennial::ExceptionTracker.log(e)
40
+ end
41
+ end
42
+ end
43
+
44
+ end
45
+ end
@@ -4,10 +4,13 @@ module Perennial
4
4
  class Settings
5
5
 
6
6
  cattr_accessor :configuration, :log_level, :verbose, :daemon
7
-
8
- @@verbose = false
9
- @@log_level = :info
10
- @@daemon = false
7
+
8
+ @@configuration = Perennial::Nash.new
9
+ @@verbose = false
10
+ @@log_level = :info
11
+ @@daemon = false
12
+ @@default_settings_path = nil
13
+ @@lookup_key_path = ["default"]
11
14
 
12
15
  class << self
13
16
 
@@ -39,57 +42,82 @@ module Perennial
39
42
  @@setup ||= false
40
43
  end
41
44
 
45
+ def default_settings_path
46
+ @@default_settings_path || (root / "config" / "settings.yml")
47
+ end
48
+
49
+ def default_settings_path=(value)
50
+ @@default_settings_path = value
51
+ setup! if setup?
52
+ end
53
+
54
+ def lookup_key_path
55
+ @@lookup_key_path ||= []
56
+ end
57
+
58
+ def lookup_key_path=(value)
59
+ @@lookup_key_path = value
60
+ end
61
+
42
62
  def setup(options = {})
43
63
  self.setup!(options) unless setup?
44
64
  end
45
65
 
46
66
  def setup!(options = {})
47
- @@configuration = {}
48
- settings_file = root / "config" / "settings.yml"
67
+ @@configuration ||= Perennial::Nash.new
68
+ settings_file = self.default_settings_path
49
69
  if File.exist?(settings_file)
50
70
  loaded_yaml = YAML.load(File.read(settings_file))
51
- @@configuration.merge! loaded_yaml["default"]
71
+ @@configuration.merge!(lookup_settings_from(loaded_yaml))
52
72
  end
53
73
  @@configuration.merge! options
54
- @@configuration.symbolize_keys!
55
- # Generate a module
56
- mod = generate_settings_accessor_mixin
57
- extend mod
58
- include mod
74
+ # Finally, normalize settings
75
+ @@configuration = @@configuration.normalized
59
76
  @@setup = true
60
77
  end
61
78
 
62
- def [](key)
63
- self.setup
64
- return self.configuration[key.to_sym]
79
+ def update!(attributes = {})
80
+ return if attributes.blank?
81
+ settings_file = self.default_settings_path
82
+ settings = File.exist?(settings_file) ? YAML.load(File.read(settings_file)) : {}
83
+ namespaced_settings = lookup_settings_from(settings)
84
+ namespaced_settings.merge! attributes.stringify_keys
85
+ File.open(settings_file, "w+") { |f| f.write(settings.to_yaml) }
86
+ setup!
87
+ return true
65
88
  end
66
89
 
67
- def []=(key, value)
68
- self.setup
69
- self.configuration[key.to_sym] = value
70
- return value
90
+ def to_hash
91
+ @@configuration.to_hash
71
92
  end
72
93
 
73
- def to_hash
74
- self.configuration.dup
94
+ def method_missing(name, *args, &blk)
95
+ self.setup! unless self.setup?
96
+ @@configuration.send(name, *args, &blk)
97
+ end
98
+
99
+ def respond_to?(name, rec = nil)
100
+ true
75
101
  end
76
102
 
77
103
  protected
78
104
 
79
- def generate_settings_accessor_mixin
80
- Module.new do
81
- Settings.configuration.keys.each do |k|
82
- define_method(k) do
83
- return Settings.configuration[k]
84
- end
85
- define_method("#{k}=") do |val|
86
- Settings.configuration[k] = val
87
- end
88
- end
105
+ def lookup_settings_from(settings_hash)
106
+ lookup_key_path.inject(settings_hash) do |h, k|
107
+ h[k.to_s] ||= {}
89
108
  end
90
109
  end
91
110
 
92
111
  end
112
+
113
+ def method_missing(name, *args, &blk)
114
+ self.class.setup
115
+ @@configuration.send(name, *args, &blk)
116
+ end
117
+
118
+ def respond_to?(name, rec = nil)
119
+ true
120
+ end
93
121
 
94
122
  end
95
123
  end
data/lib/perennial.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # Append the perennial lib folder onto the load path to make it
2
2
  # nicer to require perennial-related libraries.
3
- $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ perennial_dir = File.dirname(__FILE__)
4
+ $LOAD_PATH.unshift(perennial_dir) unless $LOAD_PATH.include?(perennial_dir)
4
5
 
5
6
  require 'pathname'
6
7
  require 'perennial/core_ext'
@@ -8,11 +9,12 @@ require 'perennial/exceptions'
8
9
 
9
10
  module Perennial
10
11
 
11
- VERSION = "0.2.2.2"
12
+ VERSION = "1.0.0.1"
12
13
 
13
- has_libary :dispatchable, :hookable, :loader, :logger,
14
- :loggable, :manifest, :settings, :argument_parser,
15
- :option_parser, :application, :generator, :daemon
14
+ has_library :dispatchable, :hookable, :loader, :logger, :nash,
15
+ :loggable, :manifest, :settings, :argument_parser,
16
+ :option_parser, :application, :generator, :daemon,
17
+ :delegateable, :reloading
16
18
 
17
19
  def self.included(parent)
18
20
  parent.extend(Manifest::Mixin)
@@ -1,4 +1,5 @@
1
- $LOAD_PATH.unshift(File.dirname(__FILE__))
1
+ lib_path = File.dirname(__FILE__)
2
+ $LOAD_PATH.unshift(lib_path) unless $LOAD_PATH.include?(lib_path)
2
3
  require 'perennial'
3
4
 
4
5
  module <%= @application_module %>
@@ -7,7 +8,7 @@ module <%= @application_module %>
7
8
  VERSION = "0.0.1"
8
9
 
9
10
  manifest do |m, l|
10
- Settings.root = File.dirname(__FILE__)
11
+ Settings.root = __FILE__.to_pathname.dirname.dirname
11
12
  # Initialize your controllers, e.g:
12
13
  # l.register_controller :client, <%= @application_module %>::Client
13
14
  end
@@ -26,6 +26,7 @@ task :gemspec do
26
26
  s.summary = ""
27
27
  s.files = FileList["{bin,vendor,lib,test}/**/*"].to_a
28
28
  s.platform = Gem::Platform::RUBY
29
+ s.add_dependency "Sutto-perennial", ">= <%= Perennial::VERSION %>"
29
30
  end
30
31
  File.open("<%= @application_path %>.gemspec", "w+") { |f| f.puts spec.to_ruby }
31
32
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: perennial
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2.2
4
+ version: 1.0.0.1
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-09 00:00:00 +08:00
12
+ date: 2009-09-25 00:00:00 +08:00
13
13
  default_executable: perennial
14
14
  dependencies: []
15
15
 
@@ -23,19 +23,17 @@ extra_rdoc_files: []
23
23
 
24
24
  files:
25
25
  - bin/perennial
26
- - vendor/fakefs/lib/fakefs.rb
27
- - vendor/fakefs/LICENSE
28
- - vendor/fakefs/Rakefile
29
- - vendor/fakefs/README.markdown
30
- - vendor/fakefs/test/fakefs_test.rb
31
- - vendor/fakefs/test/verify.rb
32
26
  - lib/perennial/application.rb
33
27
  - lib/perennial/argument_parser.rb
34
28
  - lib/perennial/core_ext/attribute_accessors.rb
35
29
  - lib/perennial/core_ext/blank.rb
30
+ - lib/perennial/core_ext/hash_key_conversions.rb
31
+ - lib/perennial/core_ext/inflections.rb
36
32
  - lib/perennial/core_ext/misc.rb
33
+ - lib/perennial/core_ext/proxy.rb
37
34
  - lib/perennial/core_ext.rb
38
35
  - lib/perennial/daemon.rb
36
+ - lib/perennial/delegateable.rb
39
37
  - lib/perennial/dispatchable.rb
40
38
  - lib/perennial/exceptions.rb
41
39
  - lib/perennial/generator.rb
@@ -44,23 +42,18 @@ files:
44
42
  - lib/perennial/loggable.rb
45
43
  - lib/perennial/logger.rb
46
44
  - lib/perennial/manifest.rb
45
+ - lib/perennial/nash.rb
47
46
  - lib/perennial/option_parser.rb
47
+ - lib/perennial/reloading.rb
48
48
  - lib/perennial/settings.rb
49
49
  - lib/perennial.rb
50
- - test/dispatchable_test.rb
51
- - test/hookable_test.rb
52
- - test/loader_test.rb
53
- - test/loggable_test.rb
54
- - test/logger_test.rb
55
- - test/settings_test.rb
56
- - test/test_helper.rb
57
50
  - templates/application.erb
58
51
  - templates/boot.erb
59
52
  - templates/rakefile.erb
60
53
  - templates/setup.erb
61
54
  - templates/test.erb
62
55
  - templates/test_helper.erb
63
- has_rdoc: true
56
+ has_rdoc: false
64
57
  homepage: http://sutto.net/
65
58
  licenses: []
66
59
 
@@ -84,7 +77,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
84
77
  requirements: []
85
78
 
86
79
  rubyforge_project:
87
- rubygems_version: 1.3.5
80
+ rubygems_version: 1.3.2
88
81
  signing_key:
89
82
  specification_version: 3
90
83
  summary: A simple (generally event-oriented) application library for Ruby