dominikh-tidy_ffi 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +7 -0
- data/LICENSE +21 -0
- data/Manifest +16 -0
- data/README.rdoc +31 -0
- data/Rakefile +24 -0
- data/lib/tidy_ffi.rb +11 -0
- data/lib/tidy_ffi/interface.rb +186 -0
- data/lib/tidy_ffi/lib_tidy.rb +67 -0
- data/lib/tidy_ffi/options_container.rb +90 -0
- data/lib/tidy_ffi/tidy.rb +83 -0
- data/lib/tidy_ffi/tidy_ffi_extensions.rb +26 -0
- data/test/test_helper.rb +13 -0
- data/test/test_lowlevel.rb +86 -0
- data/test/test_options.rb +133 -0
- data/test/test_simple.rb +52 -0
- data/tidy_ffi.gemspec +34 -0
- metadata +93 -0
data/CHANGELOG
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
v0.1.1. Fixes for new version of FFI gem
|
2
|
+
v0.1.0. Clean class method.
|
3
|
+
v0.0.4. Options validation
|
4
|
+
v0.0.3. Add method “errors” to return errors after cleanup
|
5
|
+
v0.0.2. Do not require matchy, rr and context as development dependencies. A user does not need them.
|
6
|
+
v0.0.1. Options support.
|
7
|
+
v0.0.0. Initial release.
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright (c) 2009 Eugene Pimenov
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
data/Manifest
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
CHANGELOG
|
2
|
+
LICENSE
|
3
|
+
Manifest
|
4
|
+
README.rdoc
|
5
|
+
Rakefile
|
6
|
+
lib/tidy_ffi/interface.rb
|
7
|
+
lib/tidy_ffi/lib_tidy.rb
|
8
|
+
lib/tidy_ffi/options_container.rb
|
9
|
+
lib/tidy_ffi/tidy.rb
|
10
|
+
lib/tidy_ffi/tidy_ffi_extensions.rb
|
11
|
+
lib/tidy_ffi.rb
|
12
|
+
test/test_helper.rb
|
13
|
+
test/test_lowlevel.rb
|
14
|
+
test/test_options.rb
|
15
|
+
test/test_simple.rb
|
16
|
+
tidy_ffi.gemspec
|
data/README.rdoc
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
= Tidy FFI
|
2
|
+
|
3
|
+
== What is it all about?
|
4
|
+
|
5
|
+
I wanna clean and simple tidy library. For example:
|
6
|
+
TidyFFI::Tidy.new('a string').clean
|
7
|
+
|
8
|
+
For now it can't do anything else than clean (and saves errors from it) :)
|
9
|
+
|
10
|
+
== Options
|
11
|
+
|
12
|
+
You can use different ways to set up options. These examples are produces the same output:
|
13
|
+
|
14
|
+
TidyFFI::Tidy.default_options.show_body_only = true
|
15
|
+
TidyFFI::Tidy.new('test').clean
|
16
|
+
|
17
|
+
TidyFFI::Tidy.with_options(:show_body_only => true).new('test').clean
|
18
|
+
|
19
|
+
tidy = TidyFFI::Tidy.new('test')
|
20
|
+
tidy.options.show_body_only = true
|
21
|
+
tidy.clean
|
22
|
+
|
23
|
+
TidyFFI::Tidy.new('test', :show_body_only => true).clean
|
24
|
+
|
25
|
+
TidyFFI::Tidy.clean('test', :show_body_only => 1)
|
26
|
+
|
27
|
+
== Links
|
28
|
+
|
29
|
+
* Source code: http://github.com/libc/tidy_ffi
|
30
|
+
* Bug tracker: http://rubyforge.org/tracker/?atid=30230&group_id=7805&func=browse
|
31
|
+
* Rubyforge project: http://rubyforge.org/projects/tidy-ffi
|
data/Rakefile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
begin
|
3
|
+
require 'echoe'
|
4
|
+
Echoe.new('tidy_ffi') do |p|
|
5
|
+
p.author = 'Eugene Pimenov'
|
6
|
+
p.summary = 'Tidy library interface via FFI'
|
7
|
+
p.url = 'http://github.com/libc/tidy_ffi'
|
8
|
+
p.runtime_dependencies = ['ffi >=0.3.5']
|
9
|
+
# p.development_dependencies = ['rr', 'matchy', 'context']
|
10
|
+
p.project = 'tidy-ffi'
|
11
|
+
p.email = 'libc@libc.st'
|
12
|
+
p.rdoc_pattern = /^(lib|bin|tasks|ext)|^README\.rdoc|^CHANGELOG|^TODO|^LICENSE|^COPYING$/
|
13
|
+
p.retain_gemspec = true
|
14
|
+
end
|
15
|
+
|
16
|
+
# My common error
|
17
|
+
task :rdoc => :doc do; end
|
18
|
+
rescue LoadError => boom
|
19
|
+
puts "You are missing a dependency required for meta-operations on this gem."
|
20
|
+
puts "#{boom.to_s.capitalize}."
|
21
|
+
|
22
|
+
desc 'No effect.'
|
23
|
+
task :default do; end
|
24
|
+
end
|
data/lib/tidy_ffi.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'ffi'
|
3
|
+
|
4
|
+
module TidyFFI
|
5
|
+
self.autoload :LibTidy, 'tidy_ffi/lib_tidy'
|
6
|
+
self.autoload :Interface, 'tidy_ffi/interface'
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'tidy_ffi/options_container'
|
10
|
+
require 'tidy_ffi/tidy'
|
11
|
+
require 'tidy_ffi/tidy_ffi_extensions'
|
@@ -0,0 +1,186 @@
|
|
1
|
+
# Low level interface to libtidy.
|
2
|
+
class TidyFFI::Interface
|
3
|
+
LibTidy = TidyFFI::LibTidy
|
4
|
+
|
5
|
+
# Returns a TidyFFI::Interface with initialized interface.
|
6
|
+
def self.with_doc
|
7
|
+
doc = LibTidy.tidyCreate
|
8
|
+
nd = new(doc)
|
9
|
+
nd.with_redirected_error_buffer { yield nd }
|
10
|
+
ensure
|
11
|
+
LibTidy.tidyRelease(doc)
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(doc) #:nodoc:
|
15
|
+
@doc = doc
|
16
|
+
end
|
17
|
+
|
18
|
+
# Apply options
|
19
|
+
def apply_options(options)
|
20
|
+
options.each do |key, value|
|
21
|
+
k = key.to_s.gsub('_', '-')
|
22
|
+
|
23
|
+
option = LibTidy.tidyGetOptionByName(@doc, k)
|
24
|
+
raise ArgumentError, "don't know about option #{key}" if option.null?
|
25
|
+
id = LibTidy.tidyOptGetId(option)
|
26
|
+
raise ArgumentError, "can't setup option #{key} to #{value}" if LibTidy.tidyOptSetValue(@doc, id, value.to_s) == 0
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Sets string to tidy
|
31
|
+
def string=(str)
|
32
|
+
LibTidy.tidyParseString(@doc, str)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Cleans string
|
36
|
+
def clean
|
37
|
+
@output = nil
|
38
|
+
LibTidy.tidyCleanAndRepair(@doc)
|
39
|
+
end
|
40
|
+
alias :clean_and_repair :clean
|
41
|
+
alias :repair :clean
|
42
|
+
|
43
|
+
# Returns output from tidy library
|
44
|
+
def output
|
45
|
+
@output ||= begin
|
46
|
+
with_buffer_pointer do |buf|
|
47
|
+
LibTidy.tidySaveBuffer(@doc, buf)
|
48
|
+
buf[:bp]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def errors
|
54
|
+
@error_buffer[:bp]
|
55
|
+
end
|
56
|
+
|
57
|
+
# Redirects error buffer
|
58
|
+
def with_redirected_error_buffer
|
59
|
+
with_buffer_pointer do |buf|
|
60
|
+
@error_buffer = buf
|
61
|
+
LibTidy.tidySetErrorBuffer(@doc, buf)
|
62
|
+
yield
|
63
|
+
end
|
64
|
+
ensure
|
65
|
+
@error_buffer = nil
|
66
|
+
end
|
67
|
+
|
68
|
+
# Yields block with new buffer
|
69
|
+
def with_buffer_pointer
|
70
|
+
buf = tidy_buf_object.new
|
71
|
+
yield buf
|
72
|
+
ensure
|
73
|
+
LibTidy.tidyBufFree(buf)
|
74
|
+
end
|
75
|
+
|
76
|
+
def tidy_buf_object
|
77
|
+
@tidy_buf_object ||= begin
|
78
|
+
TidyFFI::LibTidy::TidyBufWithAllocator
|
79
|
+
end
|
80
|
+
end
|
81
|
+
private :tidy_buf_object
|
82
|
+
|
83
|
+
class <<self
|
84
|
+
# Returns enumeration for opt.
|
85
|
+
#
|
86
|
+
# Some tidy options might try to trespass as integer, and in order to caught
|
87
|
+
# perpertraitors we need to call tidyOptGetPickList
|
88
|
+
def pick_list_for(opt)
|
89
|
+
iterator = LibTidy.tidyOptGetPickList(opt)
|
90
|
+
|
91
|
+
return nil if iterator.null?
|
92
|
+
|
93
|
+
pick_list = []
|
94
|
+
|
95
|
+
FFI::MemoryPointer.new(:pointer, 1) do |pointer|
|
96
|
+
pointer.put_pointer(0, iterator)
|
97
|
+
until iterator.null?
|
98
|
+
pick_list << LibTidy.tidyOptGetNextPick(opt, pointer)
|
99
|
+
iterator = pointer.get_pointer(0)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
pick_list
|
104
|
+
end
|
105
|
+
private :pick_list_for
|
106
|
+
|
107
|
+
# Loads default options.
|
108
|
+
def load_default_options
|
109
|
+
return if @default_options
|
110
|
+
|
111
|
+
doc = LibTidy.tidyCreate
|
112
|
+
iterator = LibTidy.tidyGetOptionList(doc)
|
113
|
+
|
114
|
+
@default_options = {}
|
115
|
+
|
116
|
+
FFI::MemoryPointer.new(:pointer, 1) do |pointer|
|
117
|
+
pointer.put_pointer(0, iterator)
|
118
|
+
|
119
|
+
until iterator.null?
|
120
|
+
opt = LibTidy.tidyGetNextOption(doc, pointer)
|
121
|
+
|
122
|
+
option = {}
|
123
|
+
|
124
|
+
option[:name] = LibTidy.tidyOptGetName(opt).gsub('-', '_').intern
|
125
|
+
option[:readonly?] = LibTidy.tidyOptIsReadOnly(opt) != 0
|
126
|
+
option[:type] = LibTidy::TIDY_OPTION_TYPE[LibTidy.tidyOptGetType(opt)]
|
127
|
+
option[:default] = case option[:type]
|
128
|
+
when :string
|
129
|
+
(LibTidy.tidyOptGetDefault(opt) rescue "")
|
130
|
+
when :integer
|
131
|
+
if pick_list = pick_list_for(opt)
|
132
|
+
option[:type] = :enum
|
133
|
+
option[:values] = pick_list
|
134
|
+
pick_list[LibTidy.tidyOptGetDefaultInt(opt)]
|
135
|
+
else
|
136
|
+
LibTidy.tidyOptGetDefaultInt(opt)
|
137
|
+
end
|
138
|
+
when :boolean
|
139
|
+
LibTidy.tidyOptGetDefaultBool(opt) != 0
|
140
|
+
end
|
141
|
+
|
142
|
+
@default_options[option[:name]] = option
|
143
|
+
|
144
|
+
iterator = pointer.get_pointer(0)
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
@default_options.freeze
|
149
|
+
ensure
|
150
|
+
LibTidy.tidyRelease(doc)
|
151
|
+
end
|
152
|
+
private :load_default_options
|
153
|
+
end
|
154
|
+
|
155
|
+
# Returns a hash that represents default options for tidy.
|
156
|
+
# Key for hash is the option name, value is also a hash...
|
157
|
+
# Possible values are:
|
158
|
+
# * :type - either :string, :integer, :boolean or :enum
|
159
|
+
# * :readonly?
|
160
|
+
# * :default - default value of an option
|
161
|
+
# * :values - possible values for :enum
|
162
|
+
# * :name
|
163
|
+
def self.default_options
|
164
|
+
@default_options ||= load_default_options
|
165
|
+
end
|
166
|
+
|
167
|
+
# Returns true if value is valid for +option+ and false otherwise.
|
168
|
+
def self.option_valid?(option, value)
|
169
|
+
return false unless spec = default_options[option]
|
170
|
+
|
171
|
+
case spec[:type]
|
172
|
+
when :boolean
|
173
|
+
true == value || false == value || value == 0 || value == 1 || %w(on off true false 0 1 yes no).include?(value.downcase)
|
174
|
+
when :integer
|
175
|
+
Integer === value || !!(value =~ /^\d+$/)
|
176
|
+
when :enum
|
177
|
+
if Integer === value
|
178
|
+
!!spec[:values][value]
|
179
|
+
else
|
180
|
+
spec[:values].include?(value)
|
181
|
+
end
|
182
|
+
when :string
|
183
|
+
String === value || Symbol === value
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# Very low level interface to tidy
|
2
|
+
|
3
|
+
# This file must be lazy loaded!
|
4
|
+
class TidyFFI::LibTidy #:nodoc:
|
5
|
+
extend FFI::Library
|
6
|
+
ffi_lib TidyFFI.library_path
|
7
|
+
|
8
|
+
attach_function :tidyReleaseDate, [], :string
|
9
|
+
|
10
|
+
attach_function :tidyCreate, [], :pointer
|
11
|
+
attach_function :tidyRelease, [:pointer], :void
|
12
|
+
|
13
|
+
attach_function :tidyCleanAndRepair, [:pointer], :int
|
14
|
+
attach_function :tidyRunDiagnostics, [:pointer], :int
|
15
|
+
|
16
|
+
attach_function :tidyParseString, [:pointer, :string], :int
|
17
|
+
|
18
|
+
attach_function :tidySaveBuffer, [:pointer, :pointer], :int
|
19
|
+
|
20
|
+
attach_function :tidySetErrorBuffer, [:pointer, :pointer], :int
|
21
|
+
|
22
|
+
attach_function :tidyBufFree, [:pointer], :void
|
23
|
+
|
24
|
+
attach_function :tidyGetOptionByName, [:pointer, :string], :pointer
|
25
|
+
attach_function :tidyOptGetId, [:pointer], :int
|
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
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
class TidyFFI::LibTidy::TidyBuf < FFI::Struct #:nodoc:
|
54
|
+
layout :bp, :string,
|
55
|
+
:size, :uint,
|
56
|
+
:allocated, :uint,
|
57
|
+
:next, :uint
|
58
|
+
end
|
59
|
+
|
60
|
+
class TidyFFI::LibTidy::TidyBufWithAllocator < FFI::Struct #:nodoc:
|
61
|
+
layout :allocator, :pointer,
|
62
|
+
:bp, :string,
|
63
|
+
:size, :uint,
|
64
|
+
:allocated, :uint,
|
65
|
+
:next, :uint
|
66
|
+
end
|
67
|
+
|
@@ -0,0 +1,90 @@
|
|
1
|
+
class TidyFFI::OptionsContainer #:nodoc:
|
2
|
+
|
3
|
+
def initialize(ops = nil)
|
4
|
+
if ops
|
5
|
+
@options = ops.to_hash!
|
6
|
+
else
|
7
|
+
@options = {}
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_hash!
|
12
|
+
@options.dup
|
13
|
+
end
|
14
|
+
|
15
|
+
def merge_with_options(options)
|
16
|
+
options.each do |key, val|
|
17
|
+
key = key.intern unless Symbol === key
|
18
|
+
validate_option(key, val)
|
19
|
+
@options[key] = val
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def ==(obj)
|
24
|
+
if obj.is_a?(Hash)
|
25
|
+
@options == obj
|
26
|
+
elsif obj.is_a?(OptionsContainer)
|
27
|
+
obj == @options
|
28
|
+
else
|
29
|
+
false
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def clear!
|
34
|
+
@options = {}
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
def method_missing(method, *args)
|
39
|
+
if method.to_s =~ /=$/
|
40
|
+
key, val = method.to_s.sub(/=$/, '').intern, args.first
|
41
|
+
validate_option(key, val)
|
42
|
+
@options[key] = val
|
43
|
+
else
|
44
|
+
@options[method]
|
45
|
+
end
|
46
|
+
end
|
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
|
+
|
60
|
+
class Proxy #:nodoc:
|
61
|
+
attr_reader :options
|
62
|
+
|
63
|
+
define_method :new do |*args|
|
64
|
+
@obj.new(args.first, options.to_hash!.merge(args[1] || {}))
|
65
|
+
end
|
66
|
+
|
67
|
+
def clean(str, opts = {})
|
68
|
+
@obj.clean(str, options.to_hash!.merge(opts))
|
69
|
+
end
|
70
|
+
|
71
|
+
def initialize(obj, options1, options2)
|
72
|
+
@obj = obj
|
73
|
+
@options = TidyFFI::OptionsContainer.new(options1)
|
74
|
+
@options.merge_with_options(options2)
|
75
|
+
end
|
76
|
+
|
77
|
+
def with_options(options)
|
78
|
+
Proxy.new(@obj, @options, options)
|
79
|
+
end
|
80
|
+
|
81
|
+
def clear!
|
82
|
+
@options.clear!
|
83
|
+
self
|
84
|
+
end
|
85
|
+
|
86
|
+
def method_missing(meth, *args)
|
87
|
+
@obj.send(meth, *args)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# Clean and simple interface to Tidy
|
2
|
+
class TidyFFI::Tidy
|
3
|
+
OptionsContainer = TidyFFI::OptionsContainer
|
4
|
+
class InvalidOptionName < ArgumentError; end
|
5
|
+
class InvalidOptionValue < ArgumentError; end
|
6
|
+
|
7
|
+
#Initializing object.
|
8
|
+
#
|
9
|
+
#* str is a string to tidy
|
10
|
+
#* options are options for tidy
|
11
|
+
def initialize(str, options = {})
|
12
|
+
@string = str
|
13
|
+
@options = OptionsContainer.new(self.class.default_options)
|
14
|
+
self.options = options
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns cleaned string
|
18
|
+
def clean
|
19
|
+
@clean ||= TidyFFI::Interface.with_doc do |doc|
|
20
|
+
doc.apply_options(@options.to_hash!)
|
21
|
+
doc.string = @string
|
22
|
+
doc.clean
|
23
|
+
@errors = doc.errors
|
24
|
+
doc.output
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns cleaned string
|
29
|
+
def self.clean(str, options = {})
|
30
|
+
new(str, options).clean
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns errors for string
|
34
|
+
def errors
|
35
|
+
@errors ||= begin
|
36
|
+
clean
|
37
|
+
@errors
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Assigns options for tidy.
|
42
|
+
# It merges options, not deletes old ones.
|
43
|
+
# tidy.options= {:wrap_asp => true}
|
44
|
+
# tidy.options= {:show_body_only => true}
|
45
|
+
# Will send to tidy both options.
|
46
|
+
def options=(options)
|
47
|
+
@options.merge_with_options(options)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Proxy for options. Supports set and get
|
51
|
+
#
|
52
|
+
# tidy.options.show_body_only #=> nil
|
53
|
+
# tidy.options.show_body_only = true
|
54
|
+
# tidy.options.show_body_only #=> true
|
55
|
+
def options
|
56
|
+
@options
|
57
|
+
end
|
58
|
+
|
59
|
+
class <<self
|
60
|
+
# Default options for tidy. Works just like options method
|
61
|
+
def default_options
|
62
|
+
@default_options ||= OptionsContainer.new
|
63
|
+
end
|
64
|
+
|
65
|
+
# Default options for tidy. Works just like options= method
|
66
|
+
def default_options=(options)
|
67
|
+
@default_options.merge_with_options(options)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns a proxy class with options.
|
71
|
+
# Example:
|
72
|
+
# TidyFFI::Tidy.with_options(:show_body_only => true).with_options(:wrap_asp => true).new('test)
|
73
|
+
def with_options(options)
|
74
|
+
OptionsContainer::Proxy.new(self, @default_options, options)
|
75
|
+
end
|
76
|
+
|
77
|
+
# When true it validates name and option type (default is true).
|
78
|
+
def validate_options?
|
79
|
+
@validate_options != false
|
80
|
+
end
|
81
|
+
attr_writer :validate_options
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module TidyFFI::TidyFFIExtensions #:nodoc:
|
2
|
+
# Sets path to libtidy.{dylib,so}
|
3
|
+
def library_path=(path)
|
4
|
+
@libtidy_path = path
|
5
|
+
end
|
6
|
+
|
7
|
+
# Returns path to libtidy.{dylib,so}
|
8
|
+
def library_path
|
9
|
+
@libtidy_path ||= find_tidy
|
10
|
+
end
|
11
|
+
|
12
|
+
def find_tidy
|
13
|
+
fnames = ['libtidy.dylib', 'libtidy.so']
|
14
|
+
pathes = []
|
15
|
+
pathes += ENV['LD_LIBRARY_PATH'].split(':') if ENV['LD_LIBRARY_PATH']
|
16
|
+
pathes += ENV['DYLD_LIBRARY_PATH'].split(':') if ENV['DYLD_LIBRARY_PATH']
|
17
|
+
pathes += ENV['PATH'].split(':').reject { |a| a['sbin'] }.map { |a| a.sub('/bin', '/lib') } if ENV['PATH']
|
18
|
+
pathes = ['/usr/lib', '/usr/local/lib'] if pathes.size == 0
|
19
|
+
for path in pathes.uniq
|
20
|
+
fnames.each { |fname| return File.join(path, fname) if File.exists?(File.join(path, fname)) }
|
21
|
+
end
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
private :find_tidy
|
25
|
+
end
|
26
|
+
TidyFFI.extend TidyFFI::TidyFFIExtensions
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
|
3
|
+
require 'tidy_ffi'
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'rr'
|
7
|
+
require 'matchy'
|
8
|
+
require 'context'
|
9
|
+
|
10
|
+
class Test::Unit::TestCase
|
11
|
+
alias method_name name unless instance_methods.include?('method_name')
|
12
|
+
include RR::Adapters::TestUnit unless instance_methods.include?('stub') || instance_methods.include?(:stub)
|
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
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
|
3
|
+
class TestOptions < Test::Unit::TestCase
|
4
|
+
T = TidyFFI::Tidy
|
5
|
+
context "public interface" do
|
6
|
+
[:default_options, :default_options=, :with_options, :validate_options].each do |method|
|
7
|
+
it "responds to #{method}" do
|
8
|
+
T.respond_to?(method)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
context "default_options method" do
|
14
|
+
before :each do
|
15
|
+
T.default_options.clear!
|
16
|
+
T.validate_options = false
|
17
|
+
end
|
18
|
+
|
19
|
+
context "equals version" do
|
20
|
+
it "passes arguments to default_options.merge_with_options" do
|
21
|
+
T.default_options.expects(:merge_with_options)
|
22
|
+
T.default_options = {:test => 1, :test2 => 2}
|
23
|
+
end
|
24
|
+
|
25
|
+
it "merges options with existing" do
|
26
|
+
T.default_options = {:test => 1, :test2 => 2}
|
27
|
+
T.default_options = {:test3 => 3, :test2 => 42}
|
28
|
+
T.default_options.should == {:test => 1, :test2 => 42, :test3 => 3}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
it "has clear! method to clear anything" do
|
33
|
+
T.default_options.test = 1
|
34
|
+
T.default_options.clear!
|
35
|
+
T.default_options.test.should == nil
|
36
|
+
end
|
37
|
+
|
38
|
+
it "saves options" do
|
39
|
+
T.default_options.option = 1
|
40
|
+
T.default_options.option.should == 1
|
41
|
+
end
|
42
|
+
|
43
|
+
it "sets optons after creation" do
|
44
|
+
T.new('test').options.option.should == nil
|
45
|
+
T.default_options.option = 1
|
46
|
+
T.new('test').options.option.should == 1
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "options method" do
|
51
|
+
before :each do
|
52
|
+
T.validate_options = false
|
53
|
+
T.default_options.clear!
|
54
|
+
@t = T.new('test')
|
55
|
+
end
|
56
|
+
|
57
|
+
context "equal version" do
|
58
|
+
before :each do
|
59
|
+
@t.options.clear!
|
60
|
+
end
|
61
|
+
|
62
|
+
it "passes arguments to options.merge_with_options" do
|
63
|
+
@t.options.expects(:merge_with_options)
|
64
|
+
@t.options = {:test => 1, :test2 => 2}
|
65
|
+
end
|
66
|
+
|
67
|
+
it "merges options with existing" do
|
68
|
+
@t.options = {:test => 1, :test2 => 2}
|
69
|
+
@t.options = {:test3 => 3, :test2 => 42}
|
70
|
+
@t.options.should == {:test => 1, :test2 => 42, :test3 => 3}
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context "clear! method" do
|
75
|
+
it "clears options' options" do
|
76
|
+
@t.options.test = 1
|
77
|
+
@t.options.clear!
|
78
|
+
@t.options.test.should == nil
|
79
|
+
end
|
80
|
+
|
81
|
+
it "clears default_options's options" do
|
82
|
+
T.default_options.test = 1
|
83
|
+
@t = T.new('test')
|
84
|
+
@t.options.clear!
|
85
|
+
@t.options.test.should == nil
|
86
|
+
end
|
87
|
+
|
88
|
+
it "clears with_options's options" do
|
89
|
+
@t = T.with_options(:test => 1).new('test')
|
90
|
+
@t.options.clear!
|
91
|
+
@t.options.test.should == nil
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
it "saves options" do
|
96
|
+
@t.options.option = 1
|
97
|
+
@t.options.option.should == 1
|
98
|
+
end
|
99
|
+
|
100
|
+
it "passes options to libtidy" do
|
101
|
+
@t.options.show_body_only = 1
|
102
|
+
@t.clean.should == "test\n"
|
103
|
+
end
|
104
|
+
|
105
|
+
it "passes options to clean class method" do
|
106
|
+
T.with_options(:show_body_only => true).clean("test").should == "test\n"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
context "with_options proxy class" do
|
111
|
+
before :each do
|
112
|
+
T.validate_options = false
|
113
|
+
end
|
114
|
+
|
115
|
+
it "has options method" do
|
116
|
+
T.with_options(:test => 1).options.test.should == 1
|
117
|
+
end
|
118
|
+
|
119
|
+
it "has clear! method" do
|
120
|
+
T.with_options(:test => 1).clear!.options.test.should == nil
|
121
|
+
end
|
122
|
+
|
123
|
+
it "chain methods" do
|
124
|
+
proxy = T.with_options(:test => 1).with_options(:test2 => 2)
|
125
|
+
proxy.options.test.should == 1
|
126
|
+
proxy.options.test2.should == 2
|
127
|
+
end
|
128
|
+
|
129
|
+
it "passes options to object" do
|
130
|
+
T.with_options(:test => 1).new('test').options.test.should == 1
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
data/test/test_simple.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
|
3
|
+
class TestSimple < Test::Unit::TestCase
|
4
|
+
T = TidyFFI::Tidy
|
5
|
+
context "TidyFFI::Tidy" do
|
6
|
+
context "public interface" do
|
7
|
+
[[:initialize, -2],
|
8
|
+
[:clean, 0],
|
9
|
+
[:errors, 0]].each do |method, arity|
|
10
|
+
it "method #{method} has arity #{arity}" do
|
11
|
+
T.instance_method(method).arity.should == arity
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context "simple cleanup" do
|
17
|
+
it "clean up text" do
|
18
|
+
T.new("test").clean.should =~ %r{<body>\s+test\s+</body>}
|
19
|
+
T.new("test").clean.should =~ %r{<meta name="generator" content=.+?Tidy.+?>}m
|
20
|
+
|
21
|
+
T.clean("test").should =~ %r{<body>\s+test\s+</body>}
|
22
|
+
T.clean("test").should =~ %r{<meta name="generator" content=.+?Tidy.+?>}m
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context "should have method for errors" do
|
27
|
+
it "have method for errors" do
|
28
|
+
t = T.new("test")
|
29
|
+
t.clean
|
30
|
+
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"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
# Commented out, because of broken upstream matchy gem :(
|
36
|
+
# context "options validation" do
|
37
|
+
# it "raises error on invalid option name" do
|
38
|
+
# TidyFFI::Tidy.validate_options = true
|
39
|
+
# lambda do
|
40
|
+
# TidyFFI::Tidy.default_options = {:complete_garbage => true}
|
41
|
+
# end.should raise_error(TidyFFI::Tidy::InvalidOptionName)
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# it "raises error on invalid option value" do
|
45
|
+
# TidyFFI::Tidy.validate_options = true
|
46
|
+
# lambda do
|
47
|
+
# TidyFFI::Tidy.default_options = {:force_output => "utter trash"}
|
48
|
+
# end.should raise_error(TidyFFI::Tidy::InvalidOptionValue)
|
49
|
+
# end
|
50
|
+
# end
|
51
|
+
end
|
52
|
+
end
|
data/tidy_ffi.gemspec
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{tidy_ffi}
|
5
|
+
s.version = "0.1.2"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Eugene Pimenov"]
|
9
|
+
s.date = %q{2009-06-07}
|
10
|
+
s.description = %q{Tidy library interface via FFI}
|
11
|
+
s.email = %q{libc@libc.st}
|
12
|
+
s.extra_rdoc_files = ["CHANGELOG", "LICENSE", "README.rdoc", "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.rdoc", "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_lowlevel.rb", "test/test_options.rb", "test/test_simple.rb", "tidy_ffi.gemspec"]
|
14
|
+
s.homepage = %q{http://github.com/libc/tidy_ffi}
|
15
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Tidy_ffi", "--main", "README.rdoc"]
|
16
|
+
s.require_paths = ["lib"]
|
17
|
+
s.rubyforge_project = %q{tidy-ffi}
|
18
|
+
s.rubygems_version = %q{1.3.4}
|
19
|
+
s.summary = %q{Tidy library interface via FFI}
|
20
|
+
s.test_files = ["test/test_helper.rb", "test/test_lowlevel.rb", "test/test_options.rb", "test/test_simple.rb"]
|
21
|
+
|
22
|
+
if s.respond_to? :specification_version then
|
23
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
24
|
+
s.specification_version = 3
|
25
|
+
|
26
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
27
|
+
s.add_runtime_dependency(%q<ffi>, [">= 0.3.5"])
|
28
|
+
else
|
29
|
+
s.add_dependency(%q<ffi>, [">= 0.3.5"])
|
30
|
+
end
|
31
|
+
else
|
32
|
+
s.add_dependency(%q<ffi>, [">= 0.3.5"])
|
33
|
+
end
|
34
|
+
end
|
metadata
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dominikh-tidy_ffi
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Eugene Pimenov
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-06-07 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: ffi
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.3.5
|
24
|
+
version:
|
25
|
+
description: Tidy library interface via FFI
|
26
|
+
email: libc@libc.st
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- CHANGELOG
|
33
|
+
- LICENSE
|
34
|
+
- README.rdoc
|
35
|
+
- lib/tidy_ffi/interface.rb
|
36
|
+
- lib/tidy_ffi/lib_tidy.rb
|
37
|
+
- lib/tidy_ffi/options_container.rb
|
38
|
+
- lib/tidy_ffi/tidy.rb
|
39
|
+
- lib/tidy_ffi/tidy_ffi_extensions.rb
|
40
|
+
- lib/tidy_ffi.rb
|
41
|
+
files:
|
42
|
+
- CHANGELOG
|
43
|
+
- LICENSE
|
44
|
+
- Manifest
|
45
|
+
- README.rdoc
|
46
|
+
- Rakefile
|
47
|
+
- lib/tidy_ffi/interface.rb
|
48
|
+
- lib/tidy_ffi/lib_tidy.rb
|
49
|
+
- lib/tidy_ffi/options_container.rb
|
50
|
+
- lib/tidy_ffi/tidy.rb
|
51
|
+
- lib/tidy_ffi/tidy_ffi_extensions.rb
|
52
|
+
- lib/tidy_ffi.rb
|
53
|
+
- test/test_helper.rb
|
54
|
+
- test/test_lowlevel.rb
|
55
|
+
- test/test_options.rb
|
56
|
+
- test/test_simple.rb
|
57
|
+
- tidy_ffi.gemspec
|
58
|
+
has_rdoc: false
|
59
|
+
homepage: http://github.com/libc/tidy_ffi
|
60
|
+
post_install_message:
|
61
|
+
rdoc_options:
|
62
|
+
- --line-numbers
|
63
|
+
- --inline-source
|
64
|
+
- --title
|
65
|
+
- Tidy_ffi
|
66
|
+
- --main
|
67
|
+
- README.rdoc
|
68
|
+
require_paths:
|
69
|
+
- lib
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: "0"
|
75
|
+
version:
|
76
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: "1.2"
|
81
|
+
version:
|
82
|
+
requirements: []
|
83
|
+
|
84
|
+
rubyforge_project: tidy-ffi
|
85
|
+
rubygems_version: 1.2.0
|
86
|
+
signing_key:
|
87
|
+
specification_version: 3
|
88
|
+
summary: Tidy library interface via FFI
|
89
|
+
test_files:
|
90
|
+
- test/test_helper.rb
|
91
|
+
- test/test_lowlevel.rb
|
92
|
+
- test/test_options.rb
|
93
|
+
- test/test_simple.rb
|