ns-options 0.4.1 → 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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