golly-utils 0.0.1 → 0.6.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.
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