ns-options 0.4.1 → 1.0.0.rc1

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 (51) hide show
  1. data/.gitignore +3 -0
  2. data/Gemfile +2 -1
  3. data/LICENSE +1 -1
  4. data/README.md +187 -301
  5. data/Rakefile +1 -1
  6. data/lib/ns-options/assert_macros.rb +9 -12
  7. data/lib/ns-options/boolean.rb +2 -0
  8. data/lib/ns-options/namespace.rb +34 -134
  9. data/lib/ns-options/namespace_advisor.rb +35 -0
  10. data/lib/ns-options/namespace_data.rb +166 -0
  11. data/lib/ns-options/namespaces.rb +23 -12
  12. data/lib/ns-options/option.rb +50 -24
  13. data/lib/ns-options/options.rb +23 -49
  14. data/lib/ns-options/proxy.rb +40 -53
  15. data/lib/ns-options/proxy_method.rb +54 -0
  16. data/lib/ns-options/root_methods.rb +77 -0
  17. data/lib/ns-options/version.rb +1 -1
  18. data/lib/ns-options.rb +18 -8
  19. data/ns-options.gemspec +3 -4
  20. data/test/helper.rb +3 -10
  21. data/test/support/app.rb +3 -1
  22. data/test/support/proxy.rb +4 -0
  23. data/test/support/type_class_proxy.rb +29 -0
  24. data/test/support/user.rb +5 -5
  25. data/test/{integration/app_test.rb → system/app_tests.rb} +8 -6
  26. data/test/{integration/proxy_test.rb → system/proxy_tests.rb} +12 -0
  27. data/test/system/type_class_proxy_tests.rb +108 -0
  28. data/test/system/user_tests.rb +146 -0
  29. data/test/unit/{ns-options/boolean_test.rb → boolean_tests.rb} +5 -4
  30. data/test/unit/namespace_advisor_tests.rb +69 -0
  31. data/test/unit/namespace_data_tests.rb +336 -0
  32. data/test/unit/namespace_tests.rb +205 -0
  33. data/test/unit/namespaces_tests.rb +99 -0
  34. data/test/unit/{ns-options/option_test.rb → option_tests.rb} +155 -93
  35. data/test/unit/options_tests.rb +152 -0
  36. data/test/unit/proxy_method_tests.rb +87 -0
  37. data/test/unit/{ns-options/proxy_test.rb → proxy_tests.rb} +52 -0
  38. data/test/unit/root_methods_tests.rb +126 -0
  39. metadata +58 -63
  40. data/lib/ns-options/errors/invalid_name.rb +0 -15
  41. data/lib/ns-options/has_options.rb +0 -53
  42. data/lib/ns-options/helper/advisor.rb +0 -88
  43. data/lib/ns-options/helper.rb +0 -87
  44. data/test/integration/user_test.rb +0 -94
  45. data/test/unit/ns-options/has_options_test.rb +0 -90
  46. data/test/unit/ns-options/helper/advisor_test.rb +0 -148
  47. data/test/unit/ns-options/helper_test.rb +0 -56
  48. data/test/unit/ns-options/namespace_test.rb +0 -432
  49. data/test/unit/ns-options/namespaces_test.rb +0 -55
  50. data/test/unit/ns-options/options_test.rb +0 -221
  51. /data/test/unit/{ns-options/assert_macros_test.rb → assert_macros_tests.rb} +0 -0
@@ -1,76 +1,50 @@
1
+ require 'ns-options/option'
2
+
1
3
  module NsOptions
2
4
 
3
- class Options < Hash
4
- attr_accessor :key, :parent, :namespaces
5
+ class Options
5
6
 
6
- def initialize(key, parent = nil)
7
- self.key = key.to_s
8
- self.parent = parent
9
- self.namespaces = NsOptions::Namespaces.new
7
+ def initialize
8
+ @hash = Hash.new
10
9
  end
11
10
 
12
- def [](name)
13
- super(name.to_sym)
14
- end
15
- def []=(name, value)
16
- super(name.to_sym, value)
17
- end
11
+ # for hash with indifferent access behavior
12
+ def [](name); @hash[name.to_s]; end
13
+ def []=(name, value); @hash[name.to_s] = value; end
18
14
 
19
- def add(*args)
20
- option = NsOptions::Option.new(*args)
21
- self[option.name] = option
15
+ def keys(*args, &block); @hash.keys(*args, &block); end
16
+ def each(*args, &block); @hash.each(*args, &block); end
17
+ def empty?(*args, &block); @hash.empty?(*args, &block); end
18
+
19
+ def add(name, opt)
20
+ self[name] = opt
22
21
  end
23
22
 
24
- def del(name)
25
- self[name] = nil
23
+ def rm(name)
24
+ @hash.delete(name.to_s)
26
25
  end
27
- alias :remove :del
28
26
 
29
27
  def get(name)
30
- option = self[name]
31
- option and option.value
28
+ (option = self[name]) ? option.value : nil
32
29
  end
33
30
 
34
31
  def set(name, new_value)
35
32
  self[name].value = new_value
36
- self[name]
37
- end
38
-
39
- def is_defined?(name)
40
- !!self[name]
41
33
  end
42
34
 
43
35
  def required_set?
44
- self.values.reject{|option| !option.required? }.inject(true) do |bool, option|
45
- bool && option.is_set?
46
- end
36
+ are_all_these_options_set? required_options
47
37
  end
48
38
 
49
- def add_namespace(name, key = nil, parent = nil, &block)
50
- key ||= name
51
- self.namespaces.add(name, key, parent, &block)
52
- end
39
+ private
53
40
 
54
- def get_namespace(name)
55
- self.namespaces[name]
41
+ def are_all_these_options_set?(options)
42
+ options.inject(true) {|bool, opt| bool && opt.is_set?}
56
43
  end
57
44
 
58
- def is_namespace_defined?(name)
59
- !!self.get_namespace(name)
60
- end
61
-
62
- def build_from(options, namespace)
63
- options.each do |key, option|
64
- self.add(option.name, option.type_class, option.rules)
65
- NsOptions::Helper.find_and_define_option(namespace, option.name)
66
- end
67
- options.namespaces.each do |name, ns|
68
- new_namespace = self.add_namespace(name, ns.options.key, ns.options.parent)
69
- NsOptions::Helper.find_and_define_namespace(namespace, name)
70
- new_namespace.options.build_from(ns.options, new_namespace)
71
- end
45
+ def required_options
46
+ @hash.values.reject{|opt| !opt.required? }
72
47
  end
73
48
 
74
49
  end
75
-
76
50
  end
@@ -1,4 +1,5 @@
1
1
  require 'ns-options'
2
+ require 'ns-options/proxy_method'
2
3
 
3
4
  module NsOptions::Proxy
4
5
 
@@ -8,89 +9,75 @@ module NsOptions::Proxy
8
9
 
9
10
  NAMESPACE = "__proxy_options__"
10
11
 
11
- class << self
12
+ def self.included(receiver)
13
+ NsOptions::RootMethods.new(receiver, NAMESPACE).define
14
+ receiver.class_eval { extend ProxyMethods }
15
+ receiver.class_eval { include ProxyMethods } if receiver.kind_of?(Class)
12
16
 
13
- def included(receiver)
14
- NsOptions::Helper.define_root_namespace_methods(receiver, NAMESPACE)
15
- receiver.class_eval { extend ProxyMethods }
16
- receiver.class_eval { include ProxyMethods } if receiver.kind_of?(Class)
17
+ if receiver.kind_of?(Class)
18
+ receiver.class_eval do
17
19
 
18
- if receiver.kind_of?(Class)
19
- receiver.class_eval do
20
-
21
- # default initializer method
22
- def initialize(configs={})
23
- self.apply(configs || {})
24
- end
20
+ # This hook copies the proxy definition to any subclasses
21
+ def self.inherited(subclass)
22
+ subclass.__proxy_options__.build_from(self.__proxy_options__)
23
+ end
25
24
 
26
- # equality method override for class-instance proxies
27
- def ==(other_proxy_instance)
28
- __proxy_options__ == other_proxy_instance.__proxy_options__
29
- end
25
+ # default initializer method
26
+ def initialize(configs=nil)
27
+ self.apply(configs || {})
28
+ end
30
29
 
30
+ # equality method override for class-instance proxies
31
+ def ==(other_proxy_instance)
32
+ __proxy_options__ == other_proxy_instance.__proxy_options__
31
33
  end
32
- else # Module
33
- receiver.class_eval do
34
34
 
35
- # default initializer method
36
- def self.new(configs={})
37
- self.apply(configs || {})
38
- end
35
+ end
36
+ else # Module
37
+ receiver.class_eval do
39
38
 
39
+ # default initializer method
40
+ def self.new(configs=nil)
41
+ self.apply(configs || {})
40
42
  end
43
+
41
44
  end
42
45
  end
43
-
44
46
  end
45
47
 
46
48
  module ProxyMethods
47
49
 
48
50
  # pass thru namespace methods to the proxied NAMESPACE handler
49
51
 
50
- def option(*args, &block)
51
- __proxy_options__.option(*args, &block)
52
+ def option(name, *args, &block)
53
+ __proxy_options__.option(name, *args, &block)
54
+ NsOptions::ProxyMethod.new(self, name, 'an option').define($stdout, caller)
52
55
  end
53
56
  alias_method :opt, :option
54
57
 
55
- def namespace(*args, &block)
56
- __proxy_options__.namespace(*args, &block)
58
+ def namespace(name, *args, &block)
59
+ __proxy_options__.namespace(name, *args, &block)
60
+ NsOptions::ProxyMethod.new(self, name, 'a namespace').define($stdout, caller)
57
61
  end
58
62
  alias_method :ns, :namespace
59
63
 
60
- def apply(*args, &block)
61
- __proxy_options__.apply(*args, &block)
62
- end
63
-
64
- def to_hash(*args, &block)
65
- __proxy_options__.to_hash(*args, &block)
66
- end
67
-
68
- def each(*args, &block)
69
- __proxy_options__.each(*args, &block)
70
- end
71
-
72
- def define(*args, &block)
73
- __proxy_options__.define(*args, &block)
74
- end
75
-
76
- def required_set?(*args, &block)
77
- __proxy_options__.required_set?(*args, &block)
78
- end
64
+ def apply(*args, &block); __proxy_options__.apply(*args, &block); end
65
+ def to_hash(*args, &block); __proxy_options__.to_hash(*args, &block); end
66
+ def each(*args, &block); __proxy_options__.each(*args, &block); end
67
+ def define(*args, &block); __proxy_options__.define(*args, &block); end
79
68
 
80
- def valid?(*args, &block)
81
- __proxy_options__.valid?(*args, &block)
82
- end
69
+ def required_set?(*args, &block); __proxy_options__.required_set?(*args, &block); end
70
+ def valid?(*args, &block); __proxy_options__.valid?(*args, &block); end
83
71
 
84
72
  def inspect(*args, &block)
85
- # __proxy_options__.inspect(*args, &block)
86
- "#<#{self.class}:#{'0x%x' % (self.object_id << 1)}:#{__proxy_options__.options.key} #{__proxy_options__.to_hash.inspect}>"
73
+ "#<#{self.class}:#{'0x%x' % (self.object_id << 1)}:#{__proxy_options__.__name__} #{__proxy_options__.to_hash.inspect}>"
87
74
  end
88
75
 
89
76
  # for everything else, send to the proxied NAMESPACE handler
90
- # at this point it really just enables setting dynamic options
77
+ # at this point it really just enables dynamic options writers
91
78
 
92
79
  def method_missing(meth, *args, &block)
93
- if (po = self.__proxy_options__) && po.respond_to?(meth.to_s)
80
+ if (po = __proxy_options__) && po.respond_to?(meth.to_s)
94
81
  po.send(meth.to_s, *args, &block)
95
82
  else
96
83
  super
@@ -0,0 +1,54 @@
1
+ require 'ns-options/namespace'
2
+ require 'ns-options/option'
3
+
4
+ module NsOptions
5
+ class ProxyMethod
6
+
7
+ # This class handles defining proxy class methods for classes or modules
8
+ # and an additional instance method for classes.
9
+
10
+ def initialize(define_on, name, kind)
11
+ @define_on, @name, @kind = define_on, name, kind
12
+ @meth_extension_mixin = Module.new
13
+ @meth_extension_mixin.class_eval proxy_meth_code
14
+ end
15
+
16
+ def define_on_class?
17
+ !!@define_on.kind_of?(::Class)
18
+ end
19
+
20
+ def define(io=nil, from_caller=nil)
21
+ validate(io || $stdout, from_caller || caller)
22
+
23
+ # covers defining the class-level method on Modules or Classes
24
+ @define_on.send :extend, @meth_extension_mixin
25
+
26
+ # covers defining the instance-level method on Classes
27
+ @define_on.send :include, @meth_extension_mixin if define_on_class?
28
+ end
29
+
30
+ def validate(io, from_caller)
31
+ return true unless not_recommended_meth_names.include?(@name.to_sym)
32
+
33
+ io.puts "WARNING: Defining #{@kind} with the name `#{@name}' overwrites a"\
34
+ " method NsOptions::Proxy depends on and may cause it to not"\
35
+ " behave correctly."
36
+ io.puts from_caller.first
37
+ false
38
+ end
39
+
40
+ private
41
+
42
+ def proxy_meth_code
43
+ "def #{@name}(*args, &block); __proxy_options__.#{@name}(*args, &block); end"
44
+ end
45
+
46
+ def not_recommended_meth_names
47
+ [ :option, :opt, :namespace, :ns,
48
+ :define, :inspect, :method_missing, :respond_to?,
49
+ :apply, :to_hash, :each, :required_set?, :valid?,
50
+ ]
51
+ end
52
+
53
+ end
54
+ end
@@ -0,0 +1,77 @@
1
+ require 'ns-options/namespace'
2
+
3
+ module NsOptions
4
+ class RootMethods
5
+
6
+ # This class handles defining a root namespace class method for classes
7
+ # or modules and an additional instance method for classes. For classes, the
8
+ # instance method will build an entirely new namespace, distinct from the
9
+ # class level namespace but with an identical definition
10
+
11
+ def initialize(define_on, name)
12
+ @define_on, @name = define_on, name
13
+ @class_meth_extension = Module.new
14
+ @instance_meth_mixin = Module.new
15
+ end
16
+
17
+ def define_on_class?
18
+ !!@define_on.kind_of?(::Class)
19
+ end
20
+
21
+ def define(io=nil, from_caller=nil)
22
+ validate(io || $stdout, from_caller || caller)
23
+
24
+ # covers defining the class-level method on Modules or Classes
25
+ @class_meth_extension.class_eval class_meth_extension_code
26
+ @define_on.send :extend, @class_meth_extension
27
+
28
+ if define_on_class?
29
+ # covers defining the instance-level method on Classes
30
+ @instance_meth_mixin.class_eval instance_meth_mixin_code
31
+ @define_on.send :include, @instance_meth_mixin
32
+ end
33
+ end
34
+
35
+ def validate(io, from_caller)
36
+ return true unless [:options, :opts, :namespace, :ns].include?(@name.to_sym)
37
+
38
+ io.puts "WARNING: Defining an option namespace with the name `#{@name}'"\
39
+ " overwrites a method NsOptions depends on. You won't be able to"\
40
+ " define any additional option namespaces using the `#{@name}'"\
41
+ " method."
42
+ io.puts from_caller.first
43
+ false
44
+ end
45
+
46
+ private
47
+
48
+ def class_meth_extension_code
49
+ %{
50
+ def #{@name}(*args, &block)
51
+ unless @#{@name}
52
+ @#{@name} = NsOptions::Namespace.new('#{@name}', *args, &block)
53
+ if respond_to?('superclass') && superclass &&
54
+ superclass.respond_to?('#{@name}') &&
55
+ superclass.#{@name}.kind_of?(NsOptions::Namespace)
56
+ @#{@name}.build_from(superclass.#{@name})
57
+ end
58
+ end
59
+ @#{@name}
60
+ end
61
+ }
62
+ end
63
+
64
+ def instance_meth_mixin_code
65
+ %{
66
+ def #{@name}(*args, &block)
67
+ unless @#{@name}
68
+ @#{@name} = NsOptions::Namespace.new('#{@name}', *args, &block)
69
+ @#{@name}.build_from(self.class.#{@name})
70
+ end
71
+ @#{@name}
72
+ end
73
+ }
74
+ end
75
+
76
+ end
77
+ end
@@ -1,3 +1,3 @@
1
1
  module NsOptions
2
- VERSION = "0.4.1"
2
+ VERSION = "1.0.0.rc1"
3
3
  end
data/lib/ns-options.rb CHANGED
@@ -1,16 +1,26 @@
1
- require 'ns-options/has_options'
2
- require 'ns-options/helper'
3
- require 'ns-options/namespace'
4
- require 'ns-options/namespaces'
5
- require 'ns-options/option'
6
- require 'ns-options/options'
1
+ require 'ns-options/root_methods'
7
2
  require 'ns-options/proxy'
8
- require 'ns-options/version' if !defined?(NsOptions::VERSION)
9
3
 
10
4
  module NsOptions
11
5
 
12
6
  def self.included(receiver)
13
- receiver.send(:include, HasOptions)
7
+ receiver.class_eval { extend NsOptions::DSL }
8
+ end
9
+
10
+ module DSL
11
+
12
+ # This is the main DSL method for creating a namespace of options for your
13
+ # class/module. This will define a class method for classes/modules and
14
+ # an additional instance method for classes.
15
+
16
+ def options(name, *args, &block)
17
+ NsOptions::RootMethods.new(self, name).define($stdout, caller)
18
+ self.send(name, *args, &block)
19
+ end
20
+ alias_method :opts, :options
21
+ alias_method :namespace, :options
22
+ alias_method :ns, :options
23
+
14
24
  end
15
25
 
16
26
  end
data/ns-options.gemspec CHANGED
@@ -4,8 +4,8 @@ require File.expand_path('../lib/ns-options/version', __FILE__)
4
4
  Gem::Specification.new do |gem|
5
5
  gem.authors = ["Collin Redding"]
6
6
  gem.email = ["collin.redding@reelfx.com"]
7
- gem.description = %q{Define and use namespaced options with a clean interface.}
8
- gem.summary = %q{Define and use namespaced options with a clean interface.}
7
+ gem.description = %q{A DSL for defining, organizing and accessing options.}
8
+ gem.summary = %q{A DSL for defining, organizing and accessing options.}
9
9
 
10
10
  gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
11
11
  gem.files = `git ls-files`.split("\n")
@@ -14,6 +14,5 @@ Gem::Specification.new do |gem|
14
14
  gem.require_paths = ["lib"]
15
15
  gem.version = NsOptions::VERSION
16
16
 
17
- gem.add_development_dependency("assert", ["~>0.7"])
18
- gem.add_development_dependency("assert-mocha", ["~>0.1"])
17
+ gem.add_development_dependency("assert", ["~>0.8"])
19
18
  end
data/test/helper.rb CHANGED
@@ -1,21 +1,14 @@
1
1
  $LOAD_PATH.unshift(File.expand_path("../..", __FILE__))
2
2
 
3
- require 'logger'
4
- require 'ns-options'
5
-
6
- require 'test/support/app'
7
- require 'test/support/user'
8
-
9
- require 'assert-mocha'
10
-
11
3
  module NsOptions
12
4
  module TestOutput
13
5
 
14
6
  module_function
15
7
 
16
8
  def capture
17
- out = StringIO.new
18
- $stdout = out
9
+ out = ""
10
+ io = StringIO.new(out)
11
+ $stdout = io
19
12
  yield
20
13
  return out
21
14
  ensure
data/test/support/app.rb CHANGED
@@ -1,11 +1,13 @@
1
+ require 'ns-options'
1
2
  require 'ns-options/boolean'
3
+ require 'logger'
2
4
 
3
5
  module App
4
6
 
5
7
  # mixin on just the top-level NsOptions variant
6
8
  include NsOptions
7
9
 
8
- options(:settings, "settings:app") do
10
+ options(:settings) do
9
11
  option :root, Pathname
10
12
  option :stage
11
13
  option :logger, Logger
@@ -1,3 +1,5 @@
1
+ require 'ns-options'
2
+
1
3
  module SomeProxy
2
4
  include NsOptions::Proxy
3
5
 
@@ -14,6 +16,8 @@ module SomeProxy
14
16
 
15
17
  end
16
18
 
19
+ class SomeOtherThing < SomeThing; end
20
+
17
21
  opt :some, SomeThing, :default => { :value1 => 1 }
18
22
  opt :some_prime, SomeThing, :default => { :value1 => 'one' }
19
23
  opt :stuff, :default => []
@@ -0,0 +1,29 @@
1
+ require 'ns-options'
2
+
3
+ class DefaultTypeClass < Struct.new(:value); end
4
+
5
+ class TypeClassProxy
6
+ include NsOptions::Proxy
7
+
8
+ option_type_class DefaultTypeClass
9
+
10
+ opt :value1 # test that DefaultTypeClass is used
11
+
12
+ ns :more do
13
+ opt :more1 # that it is used recursively
14
+ end
15
+
16
+ ns :strings do
17
+ opt_type_class String
18
+ opt :string1 # that it can be over written
19
+ end
20
+
21
+ ns :objs, Object do
22
+ type_class Object
23
+ opt :obj1 # and that it can be reset to the default
24
+ end
25
+
26
+ end
27
+
28
+ class InheritedTypeClassProxy < TypeClassProxy; end
29
+ class DoubleInheritedTypeClassProxy < InheritedTypeClassProxy; end
data/test/support/user.rb CHANGED
@@ -1,14 +1,14 @@
1
+ require 'ns-options'
1
2
  require 'ns-options/boolean'
2
3
 
3
4
  class User
4
5
 
5
- # mixin using the specific HasOptions variant
6
- include NsOptions::HasOptions
6
+ include NsOptions
7
7
 
8
- options(:preferences, 'user-preferences') do
8
+ options(:preferences) do
9
9
  option :home_url
10
- option :show_messages, NsOptions::Boolean, :require => true
11
- option :font_size, Integer, :default => 12
10
+ option :show_messages, NsOptions::Boolean, :required => true
11
+ option :font_size, Integer, :default => 12
12
12
 
13
13
  namespace :view do
14
14
  option :color
@@ -1,8 +1,9 @@
1
1
  require 'assert'
2
+ require 'test/support/app'
2
3
 
3
4
  module App
4
5
 
5
- class BaseTest < Assert::Context
6
+ class BaseTests < Assert::Context
6
7
  desc "the App module"
7
8
  setup do
8
9
  @module = App
@@ -13,7 +14,7 @@ module App
13
14
 
14
15
  end
15
16
 
16
- class DefineTest < BaseTest
17
+ class DefineTests < BaseTests
17
18
  desc "defined"
18
19
  setup do
19
20
  stage = @stage = "test"
@@ -32,9 +33,9 @@ module App
32
33
  end
33
34
  subject{ @module.settings }
34
35
 
35
- should have_instance_methods :namespace, :option, :define, :options, :metaclass
36
+ should have_instance_methods :namespace, :option, :define
36
37
  should have_accessors :stage, :root, :logger
37
- should have_instance_methods :sub
38
+ should have_readers :sub
38
39
 
39
40
  should "have set the stage to 'test'" do
40
41
  assert_equal @stage, subject.stage
@@ -46,7 +47,7 @@ module App
46
47
 
47
48
  should "have set the logger to the passed logger" do
48
49
  assert_equal @logger, subject.logger
49
- assert_same @logger, subject.logger
50
+ assert_same @logger, subject.logger
50
51
  end
51
52
 
52
53
  should "have set its self_stage option to its stage" do
@@ -56,13 +57,14 @@ module App
56
57
 
57
58
  end
58
59
 
59
- class SubNamespaceTest < DefineTest
60
+ class SubNamespaceTests < DefineTests
60
61
  desc "the sub namespace"
61
62
  subject{ @module.settings.sub }
62
63
 
63
64
  should "have set the run_commands option" do
64
65
  assert_equal @run, subject.run_commands
65
66
  end
67
+
66
68
  end
67
69
 
68
70
  end
@@ -39,3 +39,15 @@ class SomeProxySomeThingTests < SomeProxyIntTests
39
39
  end
40
40
 
41
41
  end
42
+
43
+ class SomeProxySomeOtherThingTests < SomeProxyIntTests
44
+ desc "that has a class inherited from another proxy object"
45
+ setup do
46
+ @proxy = SomeProxy::SomeOtherThing.new
47
+ end
48
+
49
+ # should "have the same definition as its superclass"
50
+ should have_namespace :more
51
+ should have_options :value1, :value2
52
+
53
+ end