perennial 0.2.2.2 → 1.0.0.1

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