golly-utils 0.0.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/.corvid/Gemfile +28 -0
  2. data/.corvid/features.yml +4 -0
  3. data/.corvid/plugins.yml +4 -0
  4. data/.corvid/stats_cfg.rb +13 -0
  5. data/.corvid/todo_cfg.rb +15 -0
  6. data/.corvid/versions.yml +2 -0
  7. data/.simplecov +6 -3
  8. data/CHANGELOG.md +45 -2
  9. data/Gemfile +6 -3
  10. data/Gemfile.lock +34 -37
  11. data/Guardfile +10 -4
  12. data/RELEASE.md +2 -0
  13. data/Rakefile +1 -1
  14. data/golly-utils.gemspec +19 -10
  15. data/lib/golly-utils/attr_declarative.rb +120 -49
  16. data/lib/golly-utils/callbacks.rb +211 -19
  17. data/lib/golly-utils/child_process.rb +28 -8
  18. data/lib/golly-utils/delegator.rb +14 -3
  19. data/lib/golly-utils/ruby_ext/classes_and_types.rb +120 -0
  20. data/lib/golly-utils/ruby_ext/enumerable.rb +16 -0
  21. data/lib/golly-utils/ruby_ext/env_helpers.rb +17 -0
  22. data/lib/golly-utils/ruby_ext/kernel.rb +18 -0
  23. data/lib/golly-utils/ruby_ext/options.rb +28 -0
  24. data/lib/golly-utils/ruby_ext/pretty_error_messages.rb +1 -1
  25. data/lib/golly-utils/singleton.rb +130 -0
  26. data/lib/golly-utils/testing/dynamic_fixtures.rb +268 -0
  27. data/lib/golly-utils/testing/file_helpers.rb +117 -0
  28. data/lib/golly-utils/testing/helpers_base.rb +20 -0
  29. data/lib/golly-utils/testing/rspec/arrays.rb +85 -0
  30. data/lib/golly-utils/testing/rspec/base.rb +9 -0
  31. data/lib/golly-utils/testing/rspec/deferrable_specs.rb +111 -0
  32. data/lib/golly-utils/testing/rspec/files.rb +262 -0
  33. data/lib/golly-utils/{test/spec → testing/rspec}/within_time.rb +17 -7
  34. data/lib/golly-utils/version.rb +2 -1
  35. data/test/bootstrap/all.rb +8 -1
  36. data/test/bootstrap/spec.rb +1 -1
  37. data/test/bootstrap/unit.rb +1 -1
  38. data/test/spec/child_process_spec.rb +1 -1
  39. data/test/spec/testing/dynamic_fixtures_spec.rb +131 -0
  40. data/test/spec/testing/rspec/arrays_spec.rb +33 -0
  41. data/test/spec/testing/rspec/files_spec.rb +300 -0
  42. data/test/unit/attr_declarative_test.rb +79 -13
  43. data/test/unit/callbacks_test.rb +103 -5
  44. data/test/unit/delegator_test.rb +25 -1
  45. data/test/unit/ruby_ext/classes_and_types_test.rb +103 -0
  46. data/test/unit/ruby_ext/enumerable_test.rb +12 -0
  47. data/test/unit/ruby_ext/options_test.rb +29 -0
  48. data/test/unit/singleton_test.rb +59 -0
  49. metadata +100 -10
  50. data/Gemfile.corvid +0 -27
  51. data/lib/golly-utils/ruby_ext.rb +0 -2
  52. data/lib/golly-utils/ruby_ext/subclasses.rb +0 -17
  53. data/lib/golly-utils/test/spec/deferrable_specs.rb +0 -85
  54. data/test/unit/ruby_ext/subclasses_test.rb +0 -24
@@ -0,0 +1,28 @@
1
+ # vim:ft=ruby et ts=2 sw=2:
2
+
3
+ #gem 'corvid'
4
+ gem "corvid", git: 'git://github.com/japgolly/corvid.git' # TODO Use proper Corvid once released
5
+
6
+ gem 'rake', group: :rake
7
+
8
+ group :test do
9
+ gem 'ci_reporter', require: false
10
+ gem 'simplecov', '>= 0.6.4', require: false
11
+ gem 'guard', '>= 1.3.2', require: false
12
+ end if Dir.exists?('test')
13
+
14
+ group :test_unit do
15
+ gem 'minitest'
16
+ gem 'guard-minitest', '>= 0.5.0', require: false
17
+ gem 'turn', '>= 0.9.4'
18
+ end if Dir.exists?('test/unit')
19
+
20
+ group :test_spec do
21
+ gem 'rspec'
22
+ gem 'guard-rspec', require: false
23
+ end if Dir.exists?('test/spec')
24
+
25
+ group :doc do
26
+ # gem 'yard'
27
+ gem 'yard', git: 'git://github.com/japgolly/yard.git', branch: 'happy_days' # TODO Use proper yard
28
+ end
@@ -0,0 +1,4 @@
1
+ ---
2
+ - corvid:corvid
3
+ - corvid:test_unit
4
+ - corvid:test_spec
@@ -0,0 +1,4 @@
1
+ ---
2
+ corvid:
3
+ :path: corvid/builtin/builtin_plugin
4
+ :class: Corvid::Builtin::BuiltinPlugin
@@ -0,0 +1,13 @@
1
+ # Customisable configuration for the Corvid 'rake stats' task.
2
+
3
+ # Provide your own configuration
4
+ #{
5
+ # 'Source code' => { category: :code, dirs: %w[src] },
6
+ # 'Specifications' => { category: :test, dirs: %w[test/spec], line_parser: :spec },
7
+ #}
8
+
9
+ # Add to the default configuration
10
+ #defaults.merge 'Bin scripts' => { category: :code, dirs: %w[bin] }
11
+
12
+ # Use the default configuration as is
13
+ defaults
@@ -0,0 +1,15 @@
1
+ # Customisable configuration for the Corvid 'rake todo' task.
2
+ #
3
+ # See Corvid's corvid/builtin/todo_finder.rb for more details.
4
+
5
+ # Ignore files that match find's -path switch.
6
+ #todo_finder.ignore_paths += %w[
7
+ # */test/fixtures/*
8
+ # */vendor/*
9
+ #]
10
+
11
+ # Ignore files that match find's -name switch.
12
+ #todo_finder.ignore_names += %w[
13
+ # *_spec.rb
14
+ # *.html
15
+ #]
@@ -0,0 +1,2 @@
1
+ ---
2
+ corvid: 1
data/.simplecov CHANGED
@@ -1,14 +1,17 @@
1
1
  SimpleCov.start do
2
- # project_name 'My Awesome Project'
2
+ project_name 'Golly-Utils'
3
3
 
4
4
  # add_group 'Models', 'app/model'
5
5
  # add_group 'Plugins', '(app|lib)/plugins'
6
6
 
7
- # Remove test code from coverage
8
- add_filter 'test'
7
+ # Exclude tests from coverage
8
+ add_filter '^(?:(?<!/(?:app|lib)/).)*/test/'
9
9
 
10
10
  # Add files that don't get required to coverage too
11
11
  add_files_to_coverage_at_exit '{app,lib}/**/*.rb'
12
+
13
+ # Skip LOC that contain nothing but "end", "ensure", and so on.
14
+ skip_boring_loc_in_coverage
12
15
  end
13
16
 
14
17
  # vim:ft=ruby et ts=2 sw=2:
@@ -1,9 +1,52 @@
1
1
  Golly's Utils: Changelog
2
2
  ========================
3
3
 
4
- ## 0.0.2 (WIP)
4
+ ## 0.6.0 (2012-09-17)
5
5
 
6
- WIP
6
+ Lots of stuff:
7
+
8
+ #### Added
9
+ * Added {Object#validate_type!} and {Symbol#validate_lvar_type!}.
10
+ * Added {Object#superclasses}.
11
+ * Added {GollyUtils::Testing::DynamicFixtures}.
12
+ * Added {Hash#validate_keys} and {Hash#validate_option_keys}.
13
+ * Added {Kernel#at_exit_preserving_exit_status}.
14
+ * {GollyUtils::Singleton Singleton} module.
15
+ * Added new {GollyUtils::Delegator Delegator} option: `:allow_protected`
16
+ * {GollyUtils::Callbacks Callbacks} can now be defined in modules.
17
+ * Added {GollyUtils::Callbacks::ClassMethods#callbacks} and {GollyUtils::Callbacks::ModuleMethods#callbacks} to provide a list of all defined and inherited callbacks.
18
+ * {GollyUtils::Callbacks::InstanceMethods#run_callbacks Callbacks#run_callbacks} now accepts options which currently allows for callback arguments to be specified.
19
+ * {GollyUtils::Callbacks::InstanceMethods#run_callback Callbacks#run_callback} and {GollyUtils::Callbacks::InstanceMethods#run_callbacks Callbacks#run_callbacks}
20
+ now accept an option to pass arguments to callback functions.
21
+ * {GollyUtils::Callbacks::InstanceMethods#run_callback Callbacks#run_callback} and {GollyUtils::Callbacks::InstanceMethods#run_callbacks Callbacks#run_callbacks}
22
+ now accept an option to provide a context for callback function execution.
23
+ * Added Ruby extensions:
24
+ * {Enumerable#frequency_map}
25
+ * Added test helpers:
26
+ * {GollyUtils::Testing::Helpers#get_dirs get_dirs}
27
+ * {GollyUtils::Testing::Helpers#get_files get_files}
28
+ * {GollyUtils::Testing::Helpers#get_dir_entries get_dir_entries}
29
+ * {GollyUtils::Testing::Helpers#inside_empty_dir inside_empty_dir}
30
+ * {GollyUtils::Testing::Helpers#in_tmp_dir? in_tmp_dir?}
31
+ * {GollyUtils::Testing::Helpers#step_out_of_tmp_dir step_out_of_tmp_dir}
32
+ * Added RSpec helpers:
33
+ * {GollyUtils::Testing::Helpers::ClassMethods#run_all_in_empty_dir run_all_in_empty_dir}
34
+ * {GollyUtils::Testing::Helpers::ClassMethods#run_each_in_empty_dir run_each_in_empty_dir}
35
+ * {GollyUtils::Testing::Helpers::ClassMethods#run_each_in_empty_dir_unless_in_one_already run_each_in_empty_dir_unless_in_one_already}
36
+ * {GollyUtils::Testing::RSpecMatchers#be_file_with_contents be_file_with_contents}
37
+ * {GollyUtils::Testing::RSpecMatchers#exist_as_a_dir exist_as_a_dir}
38
+ * {GollyUtils::Testing::RSpecMatchers#exist_as_a_file exist_as_a_file}
39
+ * {GollyUtils::Testing::RSpecMatchers#equal_array equal_array}
40
+
41
+ #### Modified
42
+ * Finished the experiment that was {GollyUtils::AttrDeclarative}. No mucking around with blocks, eager/lazy evaluation anymore; now it's all values.
43
+ * {GollyUtils::Delegator#respond_to? Delegator#respond_to?} now also indicates `true` for local methods (in addition to delegatable).
44
+ * {GollyUtils::Callbacks::InstanceMethods#run_callback Callbacks#run_callback} now only runs a single callback.
45
+ * Renamed dir: `golly-utils/test` => `golly-utils/testing`.
46
+ * Renamed dir: `golly-utils/test/spec` => `golly-utils/testing/rpsec`.
47
+ * Renamed module: `GollyUtils::TestHelpers` => `GollyUtils::Testing::Helpers`
48
+ * Renamed module: `GollyUtils::DeferrableSpecs` => `GollyUtils::Testing::DeferrableSpecs`
49
+ * Renamed `golly-utils/ruby_ext/subclasses.rb` => `golly-utils/ruby_ext/classes_and_types.rb`
7
50
 
8
51
  ## 0.0.1 (2012-07-16)
9
52
 
data/Gemfile CHANGED
@@ -1,12 +1,15 @@
1
1
  source :rubygems
2
-
3
2
  gemspec
4
3
 
5
- # Load Gemfile.corvid
6
- eval File.read(__FILE__ + '.corvid')
4
+ # Load Corvid dependencies
5
+ eval File.read(File.expand_path '../.corvid/Gemfile', __FILE__)
7
6
 
8
7
  # Parser for Markdown documentation
9
8
  group :doc do
10
9
  gem 'rdiscount', platforms: :mri
11
10
  gem 'kramdown', platforms: :jruby
12
11
  end
12
+
13
+ group :test do
14
+ gem 'rb-inotify', '>= 0.8.8', require: false
15
+ end
@@ -1,61 +1,59 @@
1
1
  GIT
2
2
  remote: git://github.com/japgolly/corvid.git
3
- revision: b4bd43588c25b3496c703f5b31a212f35d887de9
3
+ revision: ea00947384e8d2a507724c8aacb00afdade6333a
4
4
  specs:
5
5
  corvid (0.0.1)
6
+ activesupport
7
+ golly-utils
8
+ thor (~> 0.15.4)
9
+
10
+ GIT
11
+ remote: git://github.com/japgolly/yard.git
12
+ revision: 9e86df2a9c14412bd5c4b05005137e776b703129
13
+ branch: happy_days
14
+ specs:
15
+ yard (0.8.2.1)
6
16
 
7
17
  PATH
8
18
  remote: .
9
19
  specs:
10
- golly-utils (0.0.1)
20
+ golly-utils (0.6.0)
11
21
 
12
22
  GEM
13
23
  remote: http://rubygems.org/
14
24
  specs:
25
+ activesupport (3.2.8)
26
+ i18n (~> 0.6)
27
+ multi_json (~> 1.0)
15
28
  ansi (1.4.3)
16
- builder (3.0.0)
17
- ci_reporter (1.7.0)
29
+ builder (3.1.3)
30
+ ci_reporter (1.7.2)
18
31
  builder (>= 2.1.2)
19
- columnize (0.3.6)
20
- debugger (1.1.4)
21
- columnize (>= 0.3.1)
22
- debugger-linecache (~> 1.1.1)
23
- debugger-ruby_core_source (~> 1.1.3)
24
- debugger-linecache (1.1.2)
25
- debugger-ruby_core_source (>= 1.1.1)
26
- debugger-ruby_core_source (1.1.3)
27
32
  diff-lcs (1.1.3)
28
- ffi (1.0.11)
29
- ffi (1.0.11-java)
30
- guard (1.2.3)
33
+ ffi (1.1.5)
34
+ guard (1.3.2)
31
35
  listen (>= 0.4.2)
32
36
  thor (>= 0.14.6)
33
37
  guard-minitest (0.5.0)
34
38
  guard (>= 0.4)
35
- guard-rspec (1.1.0)
39
+ guard-rspec (1.2.1)
36
40
  guard (>= 1.1)
37
- kramdown (0.13.7)
38
- listen (0.4.7)
39
- rb-fchange (~> 0.0.5)
40
- rb-fsevent (~> 0.9.1)
41
- rb-inotify (~> 0.8.8)
42
- minitest (3.2.0)
41
+ i18n (0.6.1)
42
+ listen (0.5.0)
43
+ minitest (3.4.0)
43
44
  multi_json (1.3.6)
44
45
  rake (0.9.2.2)
45
- rb-fchange (0.0.5)
46
- ffi
47
- rb-fsevent (0.9.1)
48
46
  rb-inotify (0.8.8)
49
47
  ffi (>= 0.5.0)
50
48
  rdiscount (1.6.8)
51
- rspec (2.10.0)
52
- rspec-core (~> 2.10.0)
53
- rspec-expectations (~> 2.10.0)
54
- rspec-mocks (~> 2.10.0)
55
- rspec-core (2.10.1)
56
- rspec-expectations (2.10.0)
49
+ rspec (2.11.0)
50
+ rspec-core (~> 2.11.0)
51
+ rspec-expectations (~> 2.11.0)
52
+ rspec-mocks (~> 2.11.0)
53
+ rspec-core (2.11.1)
54
+ rspec-expectations (2.11.3)
57
55
  diff-lcs (~> 1.1.3)
58
- rspec-mocks (2.10.1)
56
+ rspec-mocks (2.11.2)
59
57
  simplecov (0.6.4)
60
58
  multi_json (~> 1.0)
61
59
  simplecov-html (~> 0.5.3)
@@ -63,24 +61,23 @@ GEM
63
61
  thor (0.15.4)
64
62
  turn (0.9.6)
65
63
  ansi
66
- yard (0.8.2.1)
67
64
 
68
65
  PLATFORMS
69
- java
70
66
  ruby
71
67
 
72
68
  DEPENDENCIES
73
69
  ci_reporter
74
70
  corvid!
75
- debugger
76
71
  golly-utils!
72
+ guard (>= 1.3.2)
77
73
  guard-minitest (>= 0.5.0)
78
74
  guard-rspec
79
75
  kramdown
80
76
  minitest
81
77
  rake
78
+ rb-inotify (>= 0.8.8)
82
79
  rdiscount
83
80
  rspec
84
81
  simplecov (>= 0.6.4)
85
- turn
86
- yard
82
+ turn (>= 0.9.4)
83
+ yard!
data/Guardfile CHANGED
@@ -1,4 +1,9 @@
1
- require 'corvid/guard'
1
+ require 'corvid/builtin/guard'
2
+
3
+ project_name = Regexp.quote(determine_project_name)
4
+ rspec_options = read_rspec_options(File.dirname __FILE__)
5
+
6
+ ignore VIM_SWAP_FILES
2
7
 
3
8
  ########################################################################################################################
4
9
  # test/unit
@@ -7,7 +12,8 @@ group :unit do
7
12
  guard 'minitest', test_folders: 'test/unit', test_file_patterns: '*_test.rb' do
8
13
 
9
14
  # Src files
10
- watch(%r'^lib/golly-utils/(.+)\.rb$') {|m| "test/unit/#{m[1]}_test.rb"}
15
+ watch(%r'^lib/(.+)\.rb$') {|m| "test/unit/#{m[1]}_test.rb"}
16
+ watch(%r'^lib/#{project_name}/(.+)\.rb$') {|m| "test/unit/#{m[1]}_test.rb"}
11
17
 
12
18
  # Each test
13
19
  watch(%r'^test/unit/.+_test\.rb$')
@@ -24,12 +30,12 @@ end if Dir.exists?('test/unit')
24
30
  ########################################################################################################################
25
31
  # test/spec
26
32
 
27
- rspec_options= read_rspec_options(File.dirname __FILE__)
28
33
  group :spec do
29
34
  guard 'rspec', binstubs: true, spec_paths: ['test/spec'], cli: rspec_options, all_on_start: false, all_after_pass: false do
30
35
 
31
36
  # Src files
32
- watch(%r'^lib/(.+)\.rb$') {|m| "test/spec/#{m[1]}_spec.rb"}
37
+ watch(%r'^lib/(.+)\.rb$') {|m| "test/spec/#{m[1]}_spec.rb"}
38
+ watch(%r'^lib/#{project_name}/(.+)\.rb$') {|m| "test/spec/#{m[1]}_spec.rb"}
33
39
 
34
40
  # Each spec
35
41
  watch(%r'^test/spec/.+_spec\.rb$')
data/RELEASE.md CHANGED
@@ -8,6 +8,8 @@ To perform a release:
8
8
  * Ensure no uncommitted changes.
9
9
  * `gem build golly-utils.gemspec`
10
10
  * `gem push golly-utils-x.x.x.gem`
11
+ * `git tag -s x.x.x`
11
12
  * Update `version.rb`
13
+ * `bundle install`
12
14
  * Commit
13
15
 
data/Rakefile CHANGED
@@ -1,6 +1,6 @@
1
1
  raise "Rake must not be run directly. Either run via Bundler (bundle exec rake) or via bin/rake." unless defined?(Bundler)
2
2
  APP_ROOT ||= File.expand_path(File.dirname(__FILE__))
3
- require 'corvid/rake/tasks'
3
+ require 'corvid/builtin/rake/tasks'
4
4
 
5
5
  # Set default task to test
6
6
  task :default => :test
@@ -2,16 +2,25 @@
2
2
  require File.expand_path('../lib/golly-utils/version', __FILE__)
3
3
 
4
4
  Gem::Specification.new do |gem|
5
- gem.name = "golly-utils"
6
- gem.version = GollyUtils::VERSION
7
- gem.date = Date.today.to_s
8
- gem.summary = %q{Golly's Utils: Reusable Ruby utility code.}
9
- gem.description = %q{This contains a bunch of shared, utility code that has no external dependencies apart from Ruby stdlib.}
10
- gem.authors = ["David Barri"]
11
- gem.email = ["japgolly@gmail.com"]
12
- gem.homepage = "https://github.com/japgolly/golly-utils"
5
+ gem.name = "golly-utils"
6
+ gem.version = GollyUtils::VERSION.dup
7
+ gem.date = Time.new.strftime '%Y-%m-%d'
8
+ gem.summary = %q{Golly's Utils: Reusable Ruby utility code.}
9
+ gem.description = %q{This contains a bunch of shared, utility code that has no external dependencies apart from Ruby stdlib.}
10
+ gem.authors = ["David Barri"]
11
+ gem.email = ["japgolly@gmail.com"]
12
+ gem.homepage = "https://github.com/japgolly/golly-utils"
13
13
 
14
- gem.files = File.exists?('.git') ? `git ls-files`.split($\) : Dir['*']
15
- gem.require_paths = %w[lib]
14
+ gem.files = File.exists?('.git') ? `git ls-files`.split($\) : \
15
+ Dir['**/*'].reject{|f| !File.file? f or %r!^(?:target|resources/latest)/! === f}.sort
16
16
  gem.test_files = gem.files.grep(/^test\//)
17
+ gem.require_paths = %w[lib]
18
+ gem.bindir = 'bin'
19
+ gem.executables = %w[]
20
+
21
+ gem.add_development_dependency 'corvid'
22
+ gem.add_development_dependency 'rake'
23
+ gem.add_development_dependency 'minitest'
24
+ gem.add_development_dependency 'rspec'
17
25
  end
26
+
@@ -1,15 +1,69 @@
1
1
  module GollyUtils
2
+ # Creates instance accessors much like `attr_accessor` with the following additional properties:
3
+ #
4
+ # 1. Default values can be specified.
5
+ # 1. Defaults can be overridden in subclasses.
6
+ # 1. Values can be declared, failing-fast on typos.
7
+ # 1. Explicit declaration of an attribute value can be required and enforced. (Optional)
8
+ #
9
+ # i.e. if an attribute is read before a value has been specified, then an error will be thrown.
10
+ #
11
+ # Attributes ending in `?` or `!` will have said suffix removed in the instance variable, and writer method names.
12
+ #
13
+ # @example
14
+ # require 'golly-utils/attr_declarative'
15
+ #
16
+ # class Plugin
17
+ # attr_declarative :name, :author
18
+ # attr_declarative version: 10
19
+ # end
20
+ #
21
+ # class BluePlugin < Plugin
22
+ # name 'The Blue Plugin'
23
+ # end
24
+ #
25
+ # p = BluePlugin.new
26
+ # puts p.name # => The Blue Plugin
27
+ # puts p.author # => nil
28
+ # puts p.version # => 10
29
+ #
30
+ # @example Typos fail-fast:
31
+ # class RedPlugin < Plugin
32
+ # aauthor 'Homer' # This will fail to parse
33
+ #
34
+ # def nnname() # Alternatively, this wouldn't fail until runtime and even then only if
35
+ # 'The Red Plugin' # name() was defined in the superclass to fail by default.
36
+ # end
37
+ # end
38
+ #
39
+ # @example Attribute value declaration can be required and enfored:
40
+ # class Plugin
41
+ # attr_declarative :name, required: true
42
+ # end
43
+ #
44
+ # class BluePlugin < Plugin
45
+ # end
46
+ #
47
+ # p = BluePlugin.new
48
+ # puts p.name # => RuntimeError: Attribute 'name' required by Plugin but not set in BluePlugin.
49
+ #
50
+ # @example Attibutes with special naming rules
51
+ # class SpecialNames
52
+ # attr_declarative :happy?
53
+ # attr_declarative :my_fist!
54
+ #
55
+ # happy? false # Class-level declaration includes suffix
56
+ # my_fist! :its_amazing # Class-level declaration includes suffix
57
+ # end
58
+ #
59
+ # puts SpecialNames.new.happy? # Instance method includes suffix
60
+ # puts SpecialNames.new.my_fist! # Instance method includes suffix
61
+ #
62
+ # SpecialNames.new.happy= false # Instance-level declaration replaces suffix with =
63
+ # SpecialNames.new.my_fist= nil # Instance-level declaration replaces suffix with =
2
64
  module AttrDeclarative
3
65
 
4
- def self.included(base)
5
- base.send :include, InstanceMethods
6
- base.send :include, InstanceAndClassMethods
7
- base.extend InstanceAndClassMethods
8
- base.extend ClassMethods
9
- end
10
-
11
- private
12
-
66
+ # @!visibility private
13
67
  def self.get_default(key, clazz)
14
68
  while clazz
15
69
  if clazz.instance_variables.include?(key)
@@ -20,55 +74,72 @@ module GollyUtils
20
74
  nil
21
75
  end
22
76
 
23
- #-------------------------------------------------------------------------------------------------------------------
77
+ # Declares one or more attributes.
78
+ #
79
+ # @overload attr_declarative(hash_of_names_to_defaults)
80
+ # @param [Hash<String|Symbol, Object>] hash_of_names_to_defaults A hash with keys being attribute names, and
81
+ # values being corresponding default values.
82
+ #
83
+ # @overload attr_declarative(*names, options={})
84
+ # @param [Array<String|Symbol>] names The attribute names.
85
+ # @option options [Object] :default (nil) The default value for all specified attributes.
86
+ # @option options [Object] :required (false) If required, an attribute will raise an error if it is read before it
87
+ # has been set.
88
+ # @return [nil]
89
+ def attr_declarative(first,*args)
90
+ # Parse args
91
+ names= ([first] + args).flatten.uniq
92
+ options= names.last.is_a?(Hash) ? names.pop.clone : {}
24
93
 
25
- # TODO default should be default_value/default_proc
26
- # TODO add an option to specify whether proc should be eval'd everytime or once and return value cached
27
-
28
- module ClassMethods
29
- def attr_declarative(first,*args)
30
- names= ([first] + args).flatten.uniq
31
- options= names.last.is_a?(Hash) ? names.pop.clone : {}
32
- default= options.delete :default
33
- raise "Unknown options: #{options.keys}" unless options.empty?
34
- names.each do |name|
35
- dvar= "@_#{name}_default"
36
- class_eval <<-EOB
94
+ # Accept <name>: <default> syntax
95
+ if names.empty? and not options.empty?
96
+ options.each do |name,default|
97
+ attr_declarative name, default: default
98
+ end
99
+ return nil
100
+ end
37
101
 
38
- def #{name}
39
- if block_given?
40
- @#{name}= yield
41
- elsif not @#{name}.nil?
42
- @#{name}
43
- elsif d= ::GollyUtils::AttrDeclarative.get_default(:#{dvar}, self.class)
44
- @#{name}= d.call
45
- else
46
- nil
47
- end
48
- end
102
+ # Validate options
103
+ default= options.delete :default
104
+ required= (options.has_key? :required) ? options.delete(:required): false
105
+ raise "Unknown options: #{options.keys}" unless options.empty?
49
106
 
50
- def self.#{name}(&block)
51
- instance_variable_set :#{dvar}, block
52
- end
107
+ # Create attributes
108
+ names.each do |name|
109
+ raise "Invalid attribute name: #{name.inspect}" unless name.is_a?(String) or name.is_a?(Symbol)
110
+ safe_name= name.to_s.sub /[!?]$/, ''
111
+ dvar= "@__gu_attr_decl_#{safe_name}_default"
112
+ ivar= "@#{safe_name}"
113
+ meth_w= "#{safe_name}="
114
+ meth_r= name
53
115
 
54
- EOB
55
- instance_variable_set dvar.to_sym, lambda{ default } unless default.nil?
56
- end
116
+ class_eval <<-EOB
57
117
 
118
+ def #{meth_w}(value)
119
+ #{ivar} = value
120
+ end
58
121
 
59
- #{default.nil? ? '' : "instance_variable_set :#{dvar}, block
60
- #{default.nil? ? 'nil' : "@#{name}= (#{default.inspect})"}
61
- end
62
- end
122
+ def #{meth_r}
123
+ if instance_variable_defined? :#{ivar}
124
+ #{ivar}
125
+ elsif d= ::GollyUtils::AttrDeclarative.get_default(:#{dvar}, self.class)
126
+ #{ivar}= d
127
+ else
128
+ #{required ? %[raise "Attribute '#{name}' required by #{self} but not set in #\{self.class}."] : 'nil'}
129
+ end
130
+ end
63
131
 
64
- #-------------------------------------------------------------------------------------------------------------------
132
+ def self.#{meth_r}(value)
133
+ instance_variable_set :#{dvar}, value
134
+ end
65
135
 
66
- module InstanceAndClassMethods
136
+ EOB
137
+ instance_variable_set dvar.to_sym, default unless default.nil?
138
+ end
139
+ nil
67
140
  end
68
141
 
69
- #-------------------------------------------------------------------------------------------------------------------
70
-
71
- module InstanceMethods
72
- end
73
142
  end
74
143
  end
144
+
145
+ Object.extend GollyUtils::AttrDeclarative