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