tidy_ffi 0.0.3 → 0.0.4

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/CHANGELOG CHANGED
@@ -1,3 +1,4 @@
1
+ v0.0.4. Options validation
1
2
  v0.0.3. Add method “errors” to return errors after cleanup
2
3
  v0.0.2. Do not require matchy, rr and context as development dependencies. A user does not need them.
3
4
  v0.0.1. Options support.
@@ -84,4 +84,108 @@ class TidyFFI::Interface
84
84
  end
85
85
  end
86
86
  private :tidy_buf_object
87
+
88
+ class <<self
89
+ # Returns enumeration for opt.
90
+ #
91
+ # Some tidy options might try to trespass as integer, and in order to caught
92
+ # perpertraitors we need to call tidyOptGetPickList
93
+ def pic_list_for(opt)
94
+ iterator = LibTidy.tidyOptGetPickList(opt)
95
+
96
+ return nil if iterator.null?
97
+
98
+ pic_list = []
99
+
100
+ MemoryPointer.new(:pointer) do |pointer|
101
+ pointer.put_pointer(0, iterator)
102
+ until iterator.null?
103
+ pic_list << LibTidy.tidyOptGetNextPick(opt, pointer)
104
+ iterator = pointer.get_pointer(0)
105
+ end
106
+ end
107
+
108
+ pic_list
109
+ end
110
+ private :pic_list_for
111
+
112
+ # Loads default options.
113
+ def load_default_options
114
+ return if @default_options
115
+
116
+ doc = LibTidy.tidyCreate
117
+ iterator = LibTidy.tidyGetOptionList(doc)
118
+
119
+ @default_options = {}
120
+
121
+ MemoryPointer.new(:pointer) do |pointer|
122
+ pointer.put_pointer(0, iterator)
123
+
124
+ until iterator.null?
125
+ opt = LibTidy.tidyGetNextOption(doc, pointer)
126
+
127
+ option = {}
128
+
129
+ option[:name] = LibTidy.tidyOptGetName(opt).gsub('-', '_').intern
130
+ option[:readonly?] = LibTidy.tidyOptIsReadOnly(opt) != 0
131
+ option[:type] = LibTidy::TIDY_OPTION_TYPE[LibTidy.tidyOptGetType(opt)]
132
+ option[:default] = case option[:type]
133
+ when :string
134
+ (LibTidy.tidyOptGetDefault(opt) rescue "")
135
+ when :integer
136
+ if pic_list = pic_list_for(opt)
137
+ option[:type] = :enum
138
+ option[:values] = pic_list
139
+ pic_list[LibTidy.tidyOptGetDefaultInt(opt)]
140
+ else
141
+ LibTidy.tidyOptGetDefaultInt(opt)
142
+ end
143
+ when :boolean
144
+ LibTidy.tidyOptGetDefaultBool(opt) != 0
145
+ end
146
+
147
+ @default_options[option[:name]] = option
148
+
149
+ iterator = pointer.get_pointer(0)
150
+ end
151
+
152
+ @default_options.freeze
153
+ end
154
+ ensure
155
+ LibTidy.tidyRelease(doc)
156
+ end
157
+ private :load_default_options
158
+ end
159
+
160
+ # Returns a hash that represents default options for tidy.
161
+ # Key for hash is the option name, value is also a hash...
162
+ # Possible values are:
163
+ # * :type - either :string, :integer, :boolean or :enum
164
+ # * :readonly?
165
+ # * :default - default value of an option
166
+ # * :values - possible values for :enum
167
+ # * :name
168
+ def self.default_options
169
+ @default_options ||= load_default_options
170
+ end
171
+
172
+ # Returns true if value is valid for +option+ and false otherwise.
173
+ def self.option_valid?(option, value)
174
+ return false unless spec = default_options[option]
175
+
176
+ case spec[:type]
177
+ when :boolean
178
+ true == value || false == value || value == 0 || value == 1 || %w(on off true false 0 1 yes no).include?(value.downcase)
179
+ when :integer
180
+ Integer === value || !!(value =~ /^\d+$/)
181
+ when :enum
182
+ if Integer === value
183
+ !!spec[:values][value]
184
+ else
185
+ spec[:values].include?(value)
186
+ end
187
+ when :string
188
+ String === value || Symbol === value
189
+ end
190
+ end
87
191
  end
@@ -24,6 +24,30 @@ class TidyFFI::LibTidy #:nodoc:
24
24
  attach_function :tidyGetOptionByName, [:pointer, :string], :pointer
25
25
  attach_function :tidyOptGetId, [:pointer], :int
26
26
  attach_function :tidyOptSetValue, [:pointer, :int, :string], :int
27
+
28
+ # iterators
29
+ attach_function :tidyGetOptionList, [:pointer], :pointer
30
+ attach_function :tidyGetNextOption, [:pointer, :pointer], :pointer
31
+ attach_function :tidyOptGetName, [:pointer], :string
32
+ attach_function :tidyOptGetType, [:pointer], :int
33
+ attach_function :tidyOptGetDefault, [:pointer], :string
34
+ attach_function :tidyOptGetDefaultInt, [:pointer], :ulong
35
+ attach_function :tidyOptGetDefaultBool, [:pointer], :int
36
+ attach_function :tidyOptIsReadOnly, [:pointer], :int
37
+ attach_function :tidyOptGetPickList, [:pointer], :pointer
38
+ attach_function :tidyOptGetNextPick, [:pointer, :pointer], :string
39
+
40
+ #types
41
+ # /** Option data types
42
+ # */
43
+ # typedef enum
44
+ # {
45
+ # TidyString, /**< String */
46
+ # TidyInteger, /**< Integer or enumeration */
47
+ # TidyBoolean /**< Boolean flag */
48
+ # } TidyOptionType;
49
+ TIDY_OPTION_TYPE = [:string, :integer, :boolean].freeze
50
+
27
51
  end
28
52
 
29
53
  class TidyFFI::LibTidy::TidyBuf < FFI::Struct #:nodoc:
@@ -1,4 +1,5 @@
1
1
  class TidyFFI::OptionsContainer #:nodoc:
2
+
2
3
  def initialize(ops = nil)
3
4
  if ops
4
5
  @options = ops.to_hash!
@@ -14,6 +15,7 @@ class TidyFFI::OptionsContainer #:nodoc:
14
15
  def merge_with_options(options)
15
16
  options.each do |key, val|
16
17
  key = key.intern unless Symbol === key
18
+ validate_option(key, val)
17
19
  @options[key] = val
18
20
  end
19
21
  end
@@ -35,12 +37,26 @@ class TidyFFI::OptionsContainer #:nodoc:
35
37
 
36
38
  def method_missing(method, *args)
37
39
  if method.to_s =~ /=$/
38
- @options[method.to_s.sub(/=$/, '').intern] = args.first
40
+ key, val = method.to_s.sub(/=$/, '').intern, args.first
41
+ validate_option(key, val)
42
+ @options[key] = val
39
43
  else
40
44
  @options[method]
41
45
  end
42
46
  end
43
-
47
+
48
+ # It's a kinda bad method: it uses TidyFFI::Interface.option_valid and TidyFFI::Tidy.validate_options?
49
+ # Also it do second lookup into default options
50
+ def validate_option(key, value)
51
+ if TidyFFI::Tidy.validate_options? && !TidyFFI::Interface.option_valid?(key, value)
52
+ if TidyFFI::Interface.default_options[key]
53
+ raise TidyFFI::Tidy::InvalidOptionValue, "#{value} is not valid for #{key}"
54
+ else
55
+ raise TidyFFI::Tidy::InvalidOptionName, "#{key} is invalid option name"
56
+ end
57
+ end
58
+ end
59
+
44
60
  class Proxy #:nodoc:
45
61
  attr_reader :options
46
62
 
data/lib/tidy_ffi/tidy.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  # Clean and simple interface to Tidy
2
2
  class TidyFFI::Tidy
3
3
  OptionsContainer = TidyFFI::OptionsContainer
4
+ class InvalidOptionName < ArgumentError; end
5
+ class InvalidOptionValue < ArgumentError; end
4
6
 
5
7
  #Initializing object.
6
8
  #
@@ -66,5 +68,11 @@ class TidyFFI::Tidy
66
68
  def with_options(options)
67
69
  OptionsContainer::Proxy.new(self, @default_options, options)
68
70
  end
71
+
72
+ # When true it validates name and option type (default is true).
73
+ def validate_options?
74
+ @validate_options != false
75
+ end
76
+ attr_writer :validate_options
69
77
  end
70
78
  end
data/test/test_helper.rb CHANGED
@@ -9,4 +9,5 @@ require 'context'
9
9
 
10
10
  class Test::Unit::TestCase
11
11
  alias method_name name unless instance_methods.include?('method_name')
12
+ include RR::Adapters::TestUnit unless instance_methods.include?('stub')
12
13
  end
@@ -0,0 +1,86 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ class TestSimple < Test::Unit::TestCase
4
+ I = TidyFFI::Interface
5
+ context "TidyFFI::Interface" do
6
+ context "default_options" do
7
+ it "returns a hash" do
8
+ I.default_options.is_a?(Hash)
9
+ end
10
+
11
+ it "has show force_output option" do
12
+ I.default_options[:force_output][:name].should == :force_output
13
+ I.default_options[:force_output][:type].should == :boolean
14
+ end
15
+ end
16
+
17
+ context "option_valid" do
18
+ it "returns false when unknown options is specified" do
19
+ I.option_valid?(:gobbledygook, '1').should == false
20
+ end
21
+
22
+ it "accepts yes, no, 1, 0, true, false as boolean values" do
23
+ stub(I).default_options { {:gobbledygook => {:type => :boolean}} }
24
+
25
+ %w{yes no 1 0 true false on off YeS}.each do |val|
26
+ I.option_valid?(:gobbledygook, val).should == true
27
+ end
28
+ I.option_valid?(:gobbledygook, 1).should == true
29
+ I.option_valid?(:gobbledygook, 0).should == true
30
+ I.option_valid?(:gobbledygook, true).should == true
31
+ I.option_valid?(:gobbledygook, false).should == true
32
+
33
+ I.option_valid?(:gobbledygook, "gobbledygook").should == false
34
+ end
35
+
36
+ it "accepts a number as a valid integer value" do
37
+ stub(I).default_options { {:gobbledygook => {:type => :integer}} }
38
+
39
+ I.option_valid?(:gobbledygook, 1).should == true
40
+ I.option_valid?(:gobbledygook, 0).should == true
41
+ I.option_valid?(:gobbledygook, "0").should == true
42
+ I.option_valid?(:gobbledygook, "1").should == true
43
+ I.option_valid?(:gobbledygook, "42").should == true
44
+
45
+ I.option_valid?(:gobbledygook, "gobbledygook").should == false
46
+ I.option_valid?(:gobbledygook, "true").should == false
47
+ end
48
+
49
+ it "accepts any string as a valid string value" do
50
+ stub(I).default_options { {:gobbledygook => {:type => :string}} }
51
+
52
+ I.option_valid?(:gobbledygook, 1).should == false
53
+ I.option_valid?(:gobbledygook, 0).should == false
54
+
55
+ I.option_valid?(:gobbledygook, "0").should == true
56
+ I.option_valid?(:gobbledygook, "1").should == true
57
+ I.option_valid?(:gobbledygook, "42").should == true
58
+ I.option_valid?(:gobbledygook, "gobbledygook").should == true
59
+ I.option_valid?(:gobbledygook, "true").should == true
60
+
61
+ end
62
+
63
+ it "accepts a symbols as a valid string value" do
64
+ stub(I).default_options { {:gobbledygook => {:type => :string}} }
65
+
66
+ I.option_valid?(:gobbledygook, :test).should == true
67
+ end
68
+
69
+ it "accepts number in range as a valid enum value" do
70
+ stub(I).default_options { {:gobbledygook => {:type => :enum, :values => ["one", "two"]}} }
71
+
72
+ I.option_valid?(:gobbledygook, 1).should == true
73
+ I.option_valid?(:gobbledygook, 0).should == true
74
+ I.option_valid?(:gobbledygook, 3).should == false
75
+ end
76
+
77
+ it "accepts string representation of enum value" do
78
+ stub(I).default_options { {:gobbledygook => {:type => :enum, :values => ["one", "two"]}} }
79
+
80
+ I.option_valid?(:gobbledygook, "one").should == true
81
+ I.option_valid?(:gobbledygook, "two").should == true
82
+ I.option_valid?(:gobbledygook, "three").should == false
83
+ end
84
+ end
85
+ end
86
+ end
data/test/test_options.rb CHANGED
@@ -3,7 +3,7 @@ require File.join(File.dirname(__FILE__), 'test_helper')
3
3
  class TestOptions < Test::Unit::TestCase
4
4
  T = TidyFFI::Tidy
5
5
  context "public interface" do
6
- [:default_options, :default_options=, :with_options].each do |method|
6
+ [:default_options, :default_options=, :with_options, :validate_options].each do |method|
7
7
  it "responds to #{method}" do
8
8
  T.respond_to?(method)
9
9
  end
@@ -13,6 +13,7 @@ class TestOptions < Test::Unit::TestCase
13
13
  context "default_options method" do
14
14
  before :each do
15
15
  T.default_options.clear!
16
+ T.validate_options = false
16
17
  end
17
18
 
18
19
  context "equals version" do
@@ -38,7 +39,7 @@ class TestOptions < Test::Unit::TestCase
38
39
  T.default_options.option = 1
39
40
  T.default_options.option.should == 1
40
41
  end
41
-
42
+
42
43
  it "sets optons after creation" do
43
44
  T.new('test').options.option.should == nil
44
45
  T.default_options.option = 1
@@ -48,6 +49,7 @@ class TestOptions < Test::Unit::TestCase
48
49
 
49
50
  context "options method" do
50
51
  before :each do
52
+ T.validate_options = false
51
53
  T.default_options.clear!
52
54
  @t = T.new('test')
53
55
  end
@@ -68,7 +70,7 @@ class TestOptions < Test::Unit::TestCase
68
70
  @t.options.should == {:test => 1, :test2 => 42, :test3 => 3}
69
71
  end
70
72
  end
71
-
73
+
72
74
  context "clear! method" do
73
75
  it "clears options' options" do
74
76
  @t.options.test = 1
@@ -89,12 +91,12 @@ class TestOptions < Test::Unit::TestCase
89
91
  @t.options.test.should == nil
90
92
  end
91
93
  end
92
-
94
+
93
95
  it "saves options" do
94
96
  @t.options.option = 1
95
97
  @t.options.option.should == 1
96
98
  end
97
-
99
+
98
100
  it "passes options to libtidy" do
99
101
  @t.options.show_body_only = 1
100
102
  @t.clean.should == "test\n"
@@ -102,20 +104,24 @@ class TestOptions < Test::Unit::TestCase
102
104
  end
103
105
 
104
106
  context "with_options proxy class" do
107
+ before :each do
108
+ T.validate_options = false
109
+ end
110
+
105
111
  it "has options method" do
106
112
  T.with_options(:test => 1).options.test.should == 1
107
113
  end
108
-
114
+
109
115
  it "has clear! method" do
110
116
  T.with_options(:test => 1).clear!.options.test.should == nil
111
117
  end
112
-
118
+
113
119
  it "chain methods" do
114
120
  proxy = T.with_options(:test => 1).with_options(:test2 => 2)
115
121
  proxy.options.test.should == 1
116
122
  proxy.options.test2.should == 2
117
123
  end
118
-
124
+
119
125
  it "passes options to object" do
120
126
  T.with_options(:test => 1).new('test').options.test.should == 1
121
127
  end
data/test/test_simple.rb CHANGED
@@ -27,5 +27,22 @@ class TestSimple < Test::Unit::TestCase
27
27
  t.errors.should == "line 1 column 1 - Warning: missing <!DOCTYPE> declaration\nline 1 column 1 - Warning: plain text isn't allowed in <head> elements\nline 1 column 1 - Warning: inserting missing 'title' element\n"
28
28
  end
29
29
  end
30
+
31
+ # Commented out, because of broken upstream matchy gem :(
32
+ # context "options validation" do
33
+ # it "raises error on invalid option name" do
34
+ # TidyFFI::Tidy.validate_options = true
35
+ # lambda do
36
+ # TidyFFI::Tidy.default_options = {:complete_garbage => true}
37
+ # end.should raise_error(TidyFFI::Tidy::InvalidOptionName)
38
+ # end
39
+ #
40
+ # it "raises error on invalid option value" do
41
+ # TidyFFI::Tidy.validate_options = true
42
+ # lambda do
43
+ # TidyFFI::Tidy.default_options = {:force_output => "utter trash"}
44
+ # end.should raise_error(TidyFFI::Tidy::InvalidOptionValue)
45
+ # end
46
+ # end
30
47
  end
31
48
  end
data/tidy_ffi.gemspec CHANGED
@@ -2,15 +2,15 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{tidy_ffi}
5
- s.version = "0.0.3"
5
+ s.version = "0.0.4"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Eugene Pimenov"]
9
- s.date = %q{2009-03-02}
9
+ s.date = %q{2009-03-14}
10
10
  s.description = %q{Tidy library interface via FFI}
11
11
  s.email = %q{}
12
12
  s.extra_rdoc_files = ["CHANGELOG", "LICENSE", "README", "lib/tidy_ffi/interface.rb", "lib/tidy_ffi/lib_tidy.rb", "lib/tidy_ffi/options_container.rb", "lib/tidy_ffi/tidy.rb", "lib/tidy_ffi/tidy_ffi_extensions.rb", "lib/tidy_ffi.rb"]
13
- s.files = ["CHANGELOG", "LICENSE", "Manifest", "README", "Rakefile", "lib/tidy_ffi/interface.rb", "lib/tidy_ffi/lib_tidy.rb", "lib/tidy_ffi/options_container.rb", "lib/tidy_ffi/tidy.rb", "lib/tidy_ffi/tidy_ffi_extensions.rb", "lib/tidy_ffi.rb", "test/test_helper.rb", "test/test_options.rb", "test/test_simple.rb", "tidy_ffi.gemspec"]
13
+ s.files = ["CHANGELOG", "LICENSE", "Manifest", "README", "Rakefile", "lib/tidy_ffi/interface.rb", "lib/tidy_ffi/lib_tidy.rb", "lib/tidy_ffi/options_container.rb", "lib/tidy_ffi/tidy.rb", "lib/tidy_ffi/tidy_ffi_extensions.rb", "lib/tidy_ffi.rb", "test/test_helper.rb", "test/test_options.rb", "test/test_simple.rb", "tidy_ffi.gemspec", "test/test_lowlevel.rb"]
14
14
  s.has_rdoc = true
15
15
  s.homepage = %q{http://github.com/libc/tidy_ffi}
16
16
  s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Tidy_ffi", "--main", "README"]
@@ -18,7 +18,7 @@ Gem::Specification.new do |s|
18
18
  s.rubyforge_project = %q{tidy-ffi}
19
19
  s.rubygems_version = %q{1.3.1}
20
20
  s.summary = %q{Tidy library interface via FFI}
21
- s.test_files = ["test/test_helper.rb", "test/test_options.rb", "test/test_simple.rb"]
21
+ s.test_files = ["test/test_helper.rb", "test/test_lowlevel.rb", "test/test_options.rb", "test/test_simple.rb"]
22
22
 
23
23
  if s.respond_to? :specification_version then
24
24
  current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tidy_ffi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eugene Pimenov
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-03-02 00:00:00 +03:00
12
+ date: 2009-03-14 00:00:00 +03:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -90,5 +90,6 @@ specification_version: 2
90
90
  summary: Tidy library interface via FFI
91
91
  test_files:
92
92
  - test/test_helper.rb
93
+ - test/test_lowlevel.rb
93
94
  - test/test_options.rb
94
95
  - test/test_simple.rb