sane-ffi 0.1.0
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/.gitignore +4 -0
- data/Gemfile +4 -0
- data/README.rdoc +36 -0
- data/Rakefile +2 -0
- data/lib/sane/api.rb +89 -0
- data/lib/sane/device.rb +133 -0
- data/lib/sane/error.rb +10 -0
- data/lib/sane/version.rb +3 -0
- data/lib/sane.rb +177 -0
- data/sane-ffi.gemspec +21 -0
- metadata +74 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
= SANE FFI
|
2
|
+
|
3
|
+
Scanner Access Now Easier in Ruby using FFI. This gem provides
|
4
|
+
bindings to SANE library using Ruby FFI. You can easily access to the
|
5
|
+
device state (i.e. buttons), setup device parameters, scan images,
|
6
|
+
etc.
|
7
|
+
|
8
|
+
== Usage
|
9
|
+
|
10
|
+
>> require "sane"
|
11
|
+
=> true
|
12
|
+
>> Sane.open { |sane| puts sane.devices.inspect }
|
13
|
+
[#<Sane::Device:"genesys:libusb:002:032">]
|
14
|
+
=> nil
|
15
|
+
>> Sane.open { |sane| puts sane.devices.first.name }
|
16
|
+
genesys:libusb:002:032
|
17
|
+
=> nil
|
18
|
+
>> Sane.open { |sane| puts sane.devices.first.vendor }
|
19
|
+
Canon
|
20
|
+
=> nil
|
21
|
+
>> Sane.open { |sane| puts sane.devices.first.model }
|
22
|
+
LiDE 100
|
23
|
+
=> nil
|
24
|
+
>> Sane.open { |sane| puts sane.devices.first.type }
|
25
|
+
flatbed scanner
|
26
|
+
=> nil
|
27
|
+
>> Sane.open { |sane| sane.devices.first.open { |device| puts device.describe(:copy) } }
|
28
|
+
{:name=>"copy", :title=>"Copy button", :desc=>"Copy button", :type=>:bool, :unit=>:none, :size=>4, :cap=>70}
|
29
|
+
=> nil
|
30
|
+
>> Sane.open { |sane| sane.devices.first.open { |device| puts device[:copy] } }
|
31
|
+
false
|
32
|
+
=> nil
|
33
|
+
|
34
|
+
== Copyright
|
35
|
+
|
36
|
+
Copyright (c) 2011 Jakub Kuźma
|
data/Rakefile
ADDED
data/lib/sane/api.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
class Sane
|
2
|
+
module API
|
3
|
+
extend FFI::Library
|
4
|
+
|
5
|
+
ffi_lib "sane"
|
6
|
+
|
7
|
+
enum :status, [:good, 0, :unsupported, :cancelled, :device_busy, :inval, :eof, :jammed, :no_docs, :cover_open, :io_error, :no_mem, :access_denied]
|
8
|
+
enum :value_type, [:bool, 0, :int, :fixed, :string, :button, :group]
|
9
|
+
enum :unit, [:none, 0, :pixel, :bit, :mm, :dpi, :percent, :microsecond]
|
10
|
+
enum :action, [:get_value, 0, :set_value, :set_auto]
|
11
|
+
enum :frame, [:gray, :rgb, :red, :green, :blue]
|
12
|
+
|
13
|
+
class Device < FFI::Struct
|
14
|
+
layout :name, :string, :vendor, :string, :model, :string, :type, :string
|
15
|
+
|
16
|
+
def to_hash
|
17
|
+
{
|
18
|
+
:name => self[:name],
|
19
|
+
:vendor => self[:vendor],
|
20
|
+
:model => self[:model],
|
21
|
+
:type => self[:type]
|
22
|
+
}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class OptionDescriptor < FFI::Struct
|
27
|
+
class ConstraintType < FFI::Union
|
28
|
+
layout :string_list, :pointer, :word_list, :pointer, :range, :pointer
|
29
|
+
end
|
30
|
+
layout :name, :string, :title, :string, :desc, :string, :type, :value_type, :unit, :unit, :size, :int, :cap, :int, :constraint_type, ConstraintType
|
31
|
+
|
32
|
+
def to_hash;
|
33
|
+
{
|
34
|
+
:name => self[:name],
|
35
|
+
:title => self[:title],
|
36
|
+
:desc => self[:desc],
|
37
|
+
:type => self[:type],
|
38
|
+
:unit => self[:unit],
|
39
|
+
:size => self[:size],
|
40
|
+
:cap => self[:cap]
|
41
|
+
}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class Parameters < FFI::Struct
|
46
|
+
layout :format, :frame, :last_frame, :int, :bytes_per_line, :int, :pixels_per_line, :int, :lines, :int, :depth, :int
|
47
|
+
|
48
|
+
def to_hash
|
49
|
+
{
|
50
|
+
:format => self[:format],
|
51
|
+
:last_frame => self[:last_frame],
|
52
|
+
:bytes_per_line => self[:bytes_per_line],
|
53
|
+
:pixels_per_line => self[:pixels_per_line],
|
54
|
+
:lines => self[:lines],
|
55
|
+
:depth => self[:depth]
|
56
|
+
}
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# extern SANE_Status sane_init (SANE_Int * version_code, SANE_Auth_Callback authorize);
|
61
|
+
attach_function :sane_init, [:pointer, :pointer], :status
|
62
|
+
# extern void sane_exit (void);
|
63
|
+
attach_function :sane_exit, [], :void
|
64
|
+
# extern SANE_Status sane_get_devices (const SANE_Device *** device_list, SANE_Bool local_only);
|
65
|
+
attach_function :sane_get_devices, [:pointer, :int], :status
|
66
|
+
# extern SANE_Status sane_open (SANE_String_Const devicename, SANE_Handle * handle);
|
67
|
+
attach_function :sane_open, [:string, :pointer], :status
|
68
|
+
# extern void sane_close (SANE_Handle handle);
|
69
|
+
attach_function :sane_close, [:pointer], :void
|
70
|
+
# extern const SANE_Option_Descriptor * sane_get_option_descriptor (SANE_Handle handle, SANE_Int option);
|
71
|
+
attach_function :sane_get_option_descriptor, [:pointer, :int], :pointer
|
72
|
+
# extern SANE_Status sane_control_option (SANE_Handle handle, SANE_Int option, SANE_Action action, void *value, SANE_Int * info);
|
73
|
+
attach_function :sane_control_option, [:pointer, :int, :action, :pointer, :pointer], :status
|
74
|
+
# extern SANE_Status sane_get_parameters (SANE_Handle handle, SANE_Parameters * params);
|
75
|
+
attach_function :sane_get_parameters, [:pointer, :pointer], :status
|
76
|
+
# extern SANE_Status sane_start (SANE_Handle handle);
|
77
|
+
attach_function :sane_start, [:pointer], :status
|
78
|
+
# extern SANE_Status sane_read (SANE_Handle handle, SANE_Byte * data, SANE_Int max_length, SANE_Int * length);
|
79
|
+
attach_function :sane_read, [:pointer, :pointer, :int, :pointer], :status
|
80
|
+
# extern void sane_cancel (SANE_Handle handle);
|
81
|
+
attach_function :sane_cancel, [:pointer], :void
|
82
|
+
# extern SANE_Status sane_set_io_mode (SANE_Handle handle, SANE_Bool non_blocking);
|
83
|
+
attach_function :sane_set_io_mode, [:pointer, :int], :status
|
84
|
+
# extern SANE_Status sane_get_select_fd (SANE_Handle handle, SANE_Int * fd);
|
85
|
+
attach_function :sane_get_select_fd, [:pointer, :pointer], :status
|
86
|
+
# extern SANE_String_Const sane_strstatus (SANE_Status status);
|
87
|
+
attach_function :sane_strstatus, [:status], :string
|
88
|
+
end
|
89
|
+
end
|
data/lib/sane/device.rb
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
class Sane
|
2
|
+
class Device
|
3
|
+
attr_reader :name, :vendor, :model, :type
|
4
|
+
|
5
|
+
def initialize(options)
|
6
|
+
@name = options[:name]
|
7
|
+
@vendor = options[:vendor]
|
8
|
+
@model = options[:model]
|
9
|
+
@type = options[:type]
|
10
|
+
@handle = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def closed?
|
14
|
+
@handle.nil?
|
15
|
+
end
|
16
|
+
|
17
|
+
def open?
|
18
|
+
!closed?
|
19
|
+
end
|
20
|
+
|
21
|
+
def open
|
22
|
+
ensure_closed!
|
23
|
+
@handle = Sane.instance.send(:open, @name)
|
24
|
+
if block_given?
|
25
|
+
begin
|
26
|
+
yield(self)
|
27
|
+
ensure
|
28
|
+
close
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def close
|
34
|
+
ensure_open!
|
35
|
+
Sane.instance.send(:close, @handle)
|
36
|
+
@handle = nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def start
|
40
|
+
ensure_open!
|
41
|
+
Sane.instance.start(@handle)
|
42
|
+
end
|
43
|
+
|
44
|
+
def read
|
45
|
+
ensure_open!
|
46
|
+
Sane.instance.send(:read, @handle)
|
47
|
+
end
|
48
|
+
|
49
|
+
def cancel
|
50
|
+
ensure_open!
|
51
|
+
Sane.instance.send(:cancel, @handle)
|
52
|
+
end
|
53
|
+
|
54
|
+
def sync
|
55
|
+
ensure_open!
|
56
|
+
Sane.instance.send(:set_io_mode, @handle, false)
|
57
|
+
end
|
58
|
+
|
59
|
+
def async
|
60
|
+
ensure_open!
|
61
|
+
Sane.instance.send(:set_io_mode, @handle, true)
|
62
|
+
end
|
63
|
+
|
64
|
+
def option_count
|
65
|
+
ensure_open!
|
66
|
+
Sane.instance.send(:get_option, @handle, 0)
|
67
|
+
end
|
68
|
+
|
69
|
+
def parameters
|
70
|
+
ensure_open!
|
71
|
+
Sane.instance.send(:get_parameters, @handle)
|
72
|
+
end
|
73
|
+
|
74
|
+
def [](option)
|
75
|
+
ensure_open!
|
76
|
+
Sane.instance.send(:get_option, @handle, option_lookup(option))
|
77
|
+
end
|
78
|
+
|
79
|
+
def []=(option, value)
|
80
|
+
ensure_open!
|
81
|
+
Sane.instance.send(:set_option, @handle, option_lookup(option), value)
|
82
|
+
end
|
83
|
+
|
84
|
+
def option_descriptors
|
85
|
+
option_count.times.map { |i| Sane.instance.send(:get_option_descriptor, @handle, i) }
|
86
|
+
end
|
87
|
+
|
88
|
+
def option_names
|
89
|
+
option_descriptors.map { |option| option[:name] }
|
90
|
+
end
|
91
|
+
|
92
|
+
def option_values
|
93
|
+
option_count.times.map do |i|
|
94
|
+
begin
|
95
|
+
self[i]
|
96
|
+
rescue Error => e
|
97
|
+
if e.status == :inval
|
98
|
+
nil # we can't read values of some options (i.e. buttons), ignore them
|
99
|
+
else
|
100
|
+
raise e
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def options
|
107
|
+
Hash[*option_names.zip(option_values).flatten]
|
108
|
+
end
|
109
|
+
|
110
|
+
def option_lookup(option_name)
|
111
|
+
return option_name if (0..option_count).include?(option_name)
|
112
|
+
option_descriptors.index { |option| option[:name] == option_name.to_s } or raise(ArgumentError, "Option not found: #{option_name}")
|
113
|
+
end
|
114
|
+
|
115
|
+
def describe(option)
|
116
|
+
option_descriptors[option_lookup(option)]
|
117
|
+
end
|
118
|
+
|
119
|
+
def inspect
|
120
|
+
%Q{#<#{self.class.name}:"#{name}">}
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
def ensure_closed!
|
126
|
+
raise("Device is already open") if open?
|
127
|
+
end
|
128
|
+
|
129
|
+
def ensure_open!
|
130
|
+
raise("Device is closed") if closed?
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
data/lib/sane/error.rb
ADDED
data/lib/sane/version.rb
ADDED
data/lib/sane.rb
ADDED
@@ -0,0 +1,177 @@
|
|
1
|
+
require "singleton"
|
2
|
+
require "forwardable"
|
3
|
+
|
4
|
+
require "ffi"
|
5
|
+
|
6
|
+
require "sane"
|
7
|
+
require "sane/api"
|
8
|
+
require "sane/error"
|
9
|
+
require "sane/device"
|
10
|
+
|
11
|
+
class Sane
|
12
|
+
include Singleton
|
13
|
+
|
14
|
+
attr_reader :version
|
15
|
+
|
16
|
+
def self.open
|
17
|
+
instance.send(:init)
|
18
|
+
yield(instance)
|
19
|
+
ensure
|
20
|
+
instance.send(:exit)
|
21
|
+
end
|
22
|
+
|
23
|
+
def devices
|
24
|
+
get_devices.map { |device| Device.new(device) }
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def not_initialized?
|
30
|
+
version.nil?
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialized?
|
34
|
+
!not_initialized?
|
35
|
+
end
|
36
|
+
|
37
|
+
def init
|
38
|
+
ensure_not_initialized!
|
39
|
+
version_code = FFI::MemoryPointer.new(:int)
|
40
|
+
check_status!(API.sane_init(version_code, FFI::Pointer::NULL))
|
41
|
+
@version = version_code.read_int
|
42
|
+
end
|
43
|
+
|
44
|
+
def exit
|
45
|
+
ensure_initialized!
|
46
|
+
API.sane_exit
|
47
|
+
@version = nil
|
48
|
+
end
|
49
|
+
|
50
|
+
def get_devices
|
51
|
+
ensure_initialized!
|
52
|
+
devices_pointer_pointer = FFI::MemoryPointer.new(:pointer)
|
53
|
+
check_status!(API.sane_get_devices(devices_pointer_pointer, 0))
|
54
|
+
devices_pointer = devices_pointer_pointer.read_pointer
|
55
|
+
result = []
|
56
|
+
until devices_pointer.read_pointer.null?
|
57
|
+
result << API::Device.new(devices_pointer.read_pointer).to_hash
|
58
|
+
devices_pointer += FFI.type_size(:pointer)
|
59
|
+
end
|
60
|
+
result
|
61
|
+
end
|
62
|
+
|
63
|
+
def open(device_name)
|
64
|
+
ensure_initialized!
|
65
|
+
device_handle_pointer = FFI::MemoryPointer.new(:pointer)
|
66
|
+
check_status!(API.sane_open(device_name, device_handle_pointer))
|
67
|
+
device_handle_pointer.read_pointer
|
68
|
+
end
|
69
|
+
|
70
|
+
def close(device_handle)
|
71
|
+
ensure_initialized!
|
72
|
+
API.sane_close(device_handle)
|
73
|
+
end
|
74
|
+
|
75
|
+
def get_option_descriptor(device_handle, option)
|
76
|
+
ensure_initialized!
|
77
|
+
result = API.sane_get_option_descriptor(device_handle, option)
|
78
|
+
API::OptionDescriptor.new(result).to_hash
|
79
|
+
end
|
80
|
+
|
81
|
+
def get_option(device_handle, option)
|
82
|
+
ensure_initialized!
|
83
|
+
descriptor = get_option_descriptor(device_handle, option)
|
84
|
+
|
85
|
+
case descriptor[:type]
|
86
|
+
when :string
|
87
|
+
value_pointer = FFI::MemoryPointer.new(:pointer)
|
88
|
+
when :bool, :int, :fixed
|
89
|
+
value_pointer = FFI::MemoryPointer.new(:int)
|
90
|
+
else
|
91
|
+
return nil
|
92
|
+
end
|
93
|
+
|
94
|
+
check_status!(API.sane_control_option(device_handle, option, :get_value, value_pointer, FFI::Pointer::NULL))
|
95
|
+
|
96
|
+
case descriptor[:type]
|
97
|
+
when :string
|
98
|
+
value_pointer.read_string
|
99
|
+
when :bool
|
100
|
+
!value_pointer.read_int.zero?
|
101
|
+
when :int, :fixed
|
102
|
+
value_pointer.read_int
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def set_option(device_handle, option, value)
|
107
|
+
ensure_initialized!
|
108
|
+
descriptor = get_option_descriptor(device_handle, option)
|
109
|
+
|
110
|
+
case descriptor[:type]
|
111
|
+
when :string
|
112
|
+
value_pointer = FFI::MemoryPointer.from_string(value)
|
113
|
+
when :int, :fixed
|
114
|
+
value_pointer = FFI::MemoryPointer.new(:int).write_int(value)
|
115
|
+
when :bool
|
116
|
+
value_pointer = FFI::MemoryPointer.new(:int).write_int(value ? 1 : 0)
|
117
|
+
else
|
118
|
+
return nil
|
119
|
+
end
|
120
|
+
|
121
|
+
check_status!(API.sane_control_option(device_handle, option, :set_value, value_pointer, FFI::Pointer::NULL))
|
122
|
+
|
123
|
+
case descriptor[:type]
|
124
|
+
when :string
|
125
|
+
value_pointer.read_string
|
126
|
+
when :bool
|
127
|
+
!value_pointer.read_int.zero?
|
128
|
+
when :int, :fixed
|
129
|
+
value_pointer.read_int
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def start(handle)
|
134
|
+
ensure_initialized!
|
135
|
+
API.sane_start(handle)
|
136
|
+
end
|
137
|
+
|
138
|
+
def read(handle, size = 64 * 1024)
|
139
|
+
ensure_initialized!
|
140
|
+
data_pointer = FFI::MemoryPointer.new(:char, size)
|
141
|
+
length_pointer = FFI::MemoryPointer.new(:int)
|
142
|
+
check_status!(API.sane_read(handle, data_pointer, size, length_pointer))
|
143
|
+
data_pointer.read_string(length_pointer.read_int)
|
144
|
+
end
|
145
|
+
|
146
|
+
def strstatus(status)
|
147
|
+
ensure_initialized!
|
148
|
+
API.sane_strstatus(status)
|
149
|
+
end
|
150
|
+
|
151
|
+
def get_parameters(handle)
|
152
|
+
ensure_initialized!
|
153
|
+
parameters_pointer = FFI::MemoryPointer.new(API::Parameters.size)
|
154
|
+
check_status!(API.sane_get_parameters(handle, parameters_pointer))
|
155
|
+
API::Parameters.new(parameters_pointer).to_hash
|
156
|
+
end
|
157
|
+
|
158
|
+
def set_io_mode(handle, non_blocking)
|
159
|
+
check_status!(API.sane_set_io_mode(handle, non_blocking ? 1 : 0))
|
160
|
+
end
|
161
|
+
|
162
|
+
def cancel(handle)
|
163
|
+
API.sane_cancel(handle)
|
164
|
+
end
|
165
|
+
|
166
|
+
def ensure_not_initialized!
|
167
|
+
raise("SANE library is already initialized") if initialized?
|
168
|
+
end
|
169
|
+
|
170
|
+
def ensure_initialized!
|
171
|
+
raise("SANE library is not initialized") if not_initialized?
|
172
|
+
end
|
173
|
+
|
174
|
+
def check_status!(status)
|
175
|
+
raise(Error.new(strstatus(status), status)) unless status == :good
|
176
|
+
end
|
177
|
+
end
|
data/sane-ffi.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "sane/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "sane-ffi"
|
7
|
+
s.version = Sane::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Jakub Kuźma"]
|
10
|
+
s.email = ["qoobaa@gmail.com"]
|
11
|
+
s.homepage = ""
|
12
|
+
s.summary = %q{SANE bindings}
|
13
|
+
s.description = %q{Scanner Access now Easier}
|
14
|
+
|
15
|
+
s.add_dependency "ffi"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sane-ffi
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.1.0
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- "Jakub Ku\xC5\xBAma"
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-06-01 00:00:00 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: ffi
|
17
|
+
prerelease: false
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
type: :runtime
|
25
|
+
version_requirements: *id001
|
26
|
+
description: Scanner Access now Easier
|
27
|
+
email:
|
28
|
+
- qoobaa@gmail.com
|
29
|
+
executables: []
|
30
|
+
|
31
|
+
extensions: []
|
32
|
+
|
33
|
+
extra_rdoc_files: []
|
34
|
+
|
35
|
+
files:
|
36
|
+
- .gitignore
|
37
|
+
- Gemfile
|
38
|
+
- README.rdoc
|
39
|
+
- Rakefile
|
40
|
+
- lib/sane.rb
|
41
|
+
- lib/sane/api.rb
|
42
|
+
- lib/sane/device.rb
|
43
|
+
- lib/sane/error.rb
|
44
|
+
- lib/sane/version.rb
|
45
|
+
- sane-ffi.gemspec
|
46
|
+
homepage: ""
|
47
|
+
licenses: []
|
48
|
+
|
49
|
+
post_install_message:
|
50
|
+
rdoc_options: []
|
51
|
+
|
52
|
+
require_paths:
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: "0"
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: "0"
|
66
|
+
requirements: []
|
67
|
+
|
68
|
+
rubyforge_project:
|
69
|
+
rubygems_version: 1.8.4
|
70
|
+
signing_key:
|
71
|
+
specification_version: 3
|
72
|
+
summary: SANE bindings
|
73
|
+
test_files: []
|
74
|
+
|