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.
- data/.corvid/Gemfile +28 -0
- data/.corvid/features.yml +4 -0
- data/.corvid/plugins.yml +4 -0
- data/.corvid/stats_cfg.rb +13 -0
- data/.corvid/todo_cfg.rb +15 -0
- data/.corvid/versions.yml +2 -0
- data/.simplecov +6 -3
- data/CHANGELOG.md +45 -2
- data/Gemfile +6 -3
- data/Gemfile.lock +34 -37
- data/Guardfile +10 -4
- data/RELEASE.md +2 -0
- data/Rakefile +1 -1
- data/golly-utils.gemspec +19 -10
- data/lib/golly-utils/attr_declarative.rb +120 -49
- data/lib/golly-utils/callbacks.rb +211 -19
- data/lib/golly-utils/child_process.rb +28 -8
- data/lib/golly-utils/delegator.rb +14 -3
- data/lib/golly-utils/ruby_ext/classes_and_types.rb +120 -0
- data/lib/golly-utils/ruby_ext/enumerable.rb +16 -0
- data/lib/golly-utils/ruby_ext/env_helpers.rb +17 -0
- data/lib/golly-utils/ruby_ext/kernel.rb +18 -0
- data/lib/golly-utils/ruby_ext/options.rb +28 -0
- data/lib/golly-utils/ruby_ext/pretty_error_messages.rb +1 -1
- data/lib/golly-utils/singleton.rb +130 -0
- data/lib/golly-utils/testing/dynamic_fixtures.rb +268 -0
- data/lib/golly-utils/testing/file_helpers.rb +117 -0
- data/lib/golly-utils/testing/helpers_base.rb +20 -0
- data/lib/golly-utils/testing/rspec/arrays.rb +85 -0
- data/lib/golly-utils/testing/rspec/base.rb +9 -0
- data/lib/golly-utils/testing/rspec/deferrable_specs.rb +111 -0
- data/lib/golly-utils/testing/rspec/files.rb +262 -0
- data/lib/golly-utils/{test/spec → testing/rspec}/within_time.rb +17 -7
- data/lib/golly-utils/version.rb +2 -1
- data/test/bootstrap/all.rb +8 -1
- data/test/bootstrap/spec.rb +1 -1
- data/test/bootstrap/unit.rb +1 -1
- data/test/spec/child_process_spec.rb +1 -1
- data/test/spec/testing/dynamic_fixtures_spec.rb +131 -0
- data/test/spec/testing/rspec/arrays_spec.rb +33 -0
- data/test/spec/testing/rspec/files_spec.rb +300 -0
- data/test/unit/attr_declarative_test.rb +79 -13
- data/test/unit/callbacks_test.rb +103 -5
- data/test/unit/delegator_test.rb +25 -1
- data/test/unit/ruby_ext/classes_and_types_test.rb +103 -0
- data/test/unit/ruby_ext/enumerable_test.rb +12 -0
- data/test/unit/ruby_ext/options_test.rb +29 -0
- data/test/unit/singleton_test.rb +59 -0
- metadata +100 -10
- data/Gemfile.corvid +0 -27
- data/lib/golly-utils/ruby_ext.rb +0 -2
- data/lib/golly-utils/ruby_ext/subclasses.rb +0 -17
- data/lib/golly-utils/test/spec/deferrable_specs.rb +0 -85
- data/test/unit/ruby_ext/subclasses_test.rb +0 -24
data/.corvid/Gemfile
ADDED
@@ -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
|
data/.corvid/plugins.yml
ADDED
@@ -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
|
data/.corvid/todo_cfg.rb
ADDED
@@ -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
|
+
#]
|
data/.simplecov
CHANGED
@@ -1,14 +1,17 @@
|
|
1
1
|
SimpleCov.start do
|
2
|
-
|
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
|
-
#
|
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:
|
data/CHANGELOG.md
CHANGED
@@ -1,9 +1,52 @@
|
|
1
1
|
Golly's Utils: Changelog
|
2
2
|
========================
|
3
3
|
|
4
|
-
## 0.0
|
4
|
+
## 0.6.0 (2012-09-17)
|
5
5
|
|
6
|
-
|
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
|
6
|
-
eval File.read(
|
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
|
data/Gemfile.lock
CHANGED
@@ -1,61 +1,59 @@
|
|
1
1
|
GIT
|
2
2
|
remote: git://github.com/japgolly/corvid.git
|
3
|
-
revision:
|
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
|
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.
|
17
|
-
ci_reporter (1.7.
|
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.
|
29
|
-
|
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
|
39
|
+
guard-rspec (1.2.1)
|
36
40
|
guard (>= 1.1)
|
37
|
-
|
38
|
-
listen (0.
|
39
|
-
|
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.
|
52
|
-
rspec-core (~> 2.
|
53
|
-
rspec-expectations (~> 2.
|
54
|
-
rspec-mocks (~> 2.
|
55
|
-
rspec-core (2.
|
56
|
-
rspec-expectations (2.
|
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.
|
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/
|
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$')
|
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
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
|
data/golly-utils.gemspec
CHANGED
@@ -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
|
6
|
-
gem.version
|
7
|
-
gem.date
|
8
|
-
gem.summary
|
9
|
-
gem.description
|
10
|
-
gem.authors
|
11
|
-
gem.email
|
12
|
-
gem.homepage
|
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($\) :
|
15
|
-
|
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
|
-
|
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
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
-
|
51
|
-
|
52
|
-
|
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
|
-
|
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
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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
|
-
|
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
|