ffi-enum-generator 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +91 -0
- data/lib/ffi/enum_generator.rb +150 -0
- metadata +166 -0
data/README.md
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
Enums in C are weird. They like `#define` constant, with delusions of being
|
2
|
+
a "real type". Behind the scenes, however, they're just a number. And
|
3
|
+
there's no guarantee that the number won't change.
|
4
|
+
|
5
|
+
FFI comes with a neat "constant generator", that will avoid the problem of
|
6
|
+
constants changing by getting the constant values out of the source code, at
|
7
|
+
runtime. This is great, but enums have exactly the same problem as
|
8
|
+
`#define`d constants, and the constant generator doesn't work on enums. So,
|
9
|
+
I adapted the `FFI::ConstGenerator` code into this handy-dandy enum
|
10
|
+
generator. As a bonus, since you often want to be able to refer to enum
|
11
|
+
values as constants, you can turn all the symbols in an enum into constants
|
12
|
+
on a module.
|
13
|
+
|
14
|
+
|
15
|
+
# Installation
|
16
|
+
|
17
|
+
It's a gem:
|
18
|
+
|
19
|
+
gem install ffi-constant-generator
|
20
|
+
|
21
|
+
If you're the sturdy type that likes to run from git:
|
22
|
+
|
23
|
+
rake build; gem install pkg/ffi-enum-generator-<whatever>.gem
|
24
|
+
|
25
|
+
Or, if you've eschewed the convenience of Rubygems, then you presumably know
|
26
|
+
what to do already.
|
27
|
+
|
28
|
+
|
29
|
+
# Usage
|
30
|
+
|
31
|
+
To generate an enum, use the `generate_enum` method in your
|
32
|
+
`FFI::Library`-using class:
|
33
|
+
|
34
|
+
require 'ffi/enum_generator'
|
35
|
+
|
36
|
+
class MyFFI
|
37
|
+
extend ::FFI::Library
|
38
|
+
ffi_lib "foo.so"
|
39
|
+
|
40
|
+
generate_enum :foo_bar_opts do |eg|
|
41
|
+
# The enum is defined in here, so we need to know that so we can
|
42
|
+
# get the values
|
43
|
+
eg.include "foo/bar.h"
|
44
|
+
|
45
|
+
eg.symbol("FOO_BAR_FROB", "FROB")
|
46
|
+
eg.symbol("FOO_BAR_BAZ", "BAZ")
|
47
|
+
eg.symbol("FOO_BAR_WOMBAT", "WOMBAT")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
This will create an enum named `:foo_bar_opts`, with the symbols `:FROB`,
|
52
|
+
`:BAZ`, and `:WOMBAT` with values equal to the C enum's values for
|
53
|
+
`FOO_BAR_FROB`, `FOO_BAR_BAZ`, and `FOO_BAR_WOMBAT`, respectively. If you
|
54
|
+
leave off the second argument to `eg.symbol`, the symbols in your enum will
|
55
|
+
be the same as the original names, but since anyone sane namespaces their
|
56
|
+
enum values with prefixes, it's rare that you'll want to do that.
|
57
|
+
|
58
|
+
Once your enum is generated, you can use it in exactly the same way as you
|
59
|
+
would any other typed enum. You can refer to it in your `attach_function`
|
60
|
+
calls:
|
61
|
+
|
62
|
+
class MyFFI
|
63
|
+
attach_function :frobber,
|
64
|
+
[:pointer, :foo_bar_opts],
|
65
|
+
:int
|
66
|
+
end
|
67
|
+
|
68
|
+
Or retrieve it at will using `MyFFI.enum_type`:
|
69
|
+
|
70
|
+
puts "foo_bar_opts[:wombat] is #{MyFFI.enum_type(:foo_bar_opts)[:WOMBAT]}"
|
71
|
+
|
72
|
+
Since going through all that rigamarole is a lot of typing, and not very
|
73
|
+
Rubyesque, you can set all the enum symbols up as constants on a module,
|
74
|
+
like so:
|
75
|
+
|
76
|
+
module FooBarOpts; end
|
77
|
+
|
78
|
+
MyFFI.enum_type(:foo_bar_opts).set_consts(FooBarOpts)
|
79
|
+
|
80
|
+
puts "foo_bar_opts[:wombat] is #{FooBarOpts::WOMBAT}"
|
81
|
+
|
82
|
+
Neat, huh?
|
83
|
+
|
84
|
+
|
85
|
+
# Contributing
|
86
|
+
|
87
|
+
Bug reports should be sent to the [Github issue
|
88
|
+
tracker](https://github.com/mpalmer/ffi-enum-generator/issues), or
|
89
|
+
[e-mailed](mailto:theshed+ffi-enum-generator@hezmatt.org). Patches can be
|
90
|
+
sent as a Github pull request, or
|
91
|
+
[e-mailed](mailto:theshed+ffi-enum-generator@hezmatt.org).
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
require 'open3'
|
3
|
+
|
4
|
+
module FFI
|
5
|
+
module Library
|
6
|
+
# Generate an `FFI::Enum` from C enum values
|
7
|
+
#
|
8
|
+
# @example A simple example for a days-of-the-week enum
|
9
|
+
#
|
10
|
+
# # C:
|
11
|
+
# enum {
|
12
|
+
# SUNDAY, # Automatically given the value '0'
|
13
|
+
# MONDAY,
|
14
|
+
# TUESDAY,
|
15
|
+
# WEDNESDAY,
|
16
|
+
# THURSDAY,
|
17
|
+
# FRIDAY,
|
18
|
+
# SATURDAY
|
19
|
+
# }
|
20
|
+
#
|
21
|
+
# # Ruby:
|
22
|
+
# enum = FFI::EnumGenerator(:weekdays) do |gen|
|
23
|
+
# gen.symbol(:MONDAY)
|
24
|
+
# gen.symbol('TUESDAY')
|
25
|
+
# gen.symbol(:WEDNESDAY, :WED) # this enum value's symbol will be :WED
|
26
|
+
# gen.symbol(:THURSDAY),
|
27
|
+
# gen.symbol(:FRIDAY)
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# enum.class # => FFI::Enum
|
31
|
+
# enum[:MONDAY] # => 1
|
32
|
+
# enum[:TUESDAY] # => 2
|
33
|
+
# enum[:WED] # => 3
|
34
|
+
# enum[:WEDNESDAY] # => nil
|
35
|
+
# enum[:ohai] # => nil
|
36
|
+
#
|
37
|
+
# @note Specifying a symbol that isn't known, will cause assplosions.
|
38
|
+
# Specifying a symbol that isn't an enum value may work, but is not
|
39
|
+
# guaranteed to continue to work.
|
40
|
+
#
|
41
|
+
# @param name [#to_s] The name of the enum to create.
|
42
|
+
#
|
43
|
+
# @return [FFI::Enum] The newly-created enum.
|
44
|
+
#
|
45
|
+
# @yieldparam gen [FFI::EnumGenerator] The generator is passed to the
|
46
|
+
# block, so you can use {FFI::EnumGenerator#include} and
|
47
|
+
# {FFI::EnumGenerator#symbol} to define the enum before it is
|
48
|
+
# generated.
|
49
|
+
#
|
50
|
+
def generate_enum(name, &blk)
|
51
|
+
enum name, ::FFI::EnumGenerator.new(&blk).generate
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class EnumGenerator
|
56
|
+
# Creates a new enum generator.
|
57
|
+
#
|
58
|
+
# You probably don't want to instantiate one of these directly. Take
|
59
|
+
# a look at {FFI::Library.generate_enum} instead.
|
60
|
+
#
|
61
|
+
def initialize
|
62
|
+
@includes = ["stdio.h"]
|
63
|
+
@enum_symbols = {}
|
64
|
+
|
65
|
+
yield self if block_given?
|
66
|
+
end
|
67
|
+
|
68
|
+
# Add a symbolic value to the enum.
|
69
|
+
#
|
70
|
+
# @param name [#to_s] The name of the enum value to lookup.
|
71
|
+
#
|
72
|
+
# @param ruby_name [#to_sym] The name that the enum value will have
|
73
|
+
# inside the Ruby enum. This is useful to remove namespacing
|
74
|
+
# prefixes that are unnecessary in Ruby, or to make the symbols more
|
75
|
+
# pleasing to the eye (camel-casing, for instance).
|
76
|
+
#
|
77
|
+
def symbol(name, ruby_name = nil)
|
78
|
+
ruby_name ||= name
|
79
|
+
@enum_symbols[name] = ruby_name
|
80
|
+
end
|
81
|
+
|
82
|
+
# Add additional C include file(s) to use to lookup the enum values.
|
83
|
+
#
|
84
|
+
# You should use this method to add the `.h` files needed to fully
|
85
|
+
# define the enum(s) you wish to extract values from.
|
86
|
+
#
|
87
|
+
# @param i [List<String>, Array<String>] The additional file(s) to
|
88
|
+
# include.
|
89
|
+
#
|
90
|
+
# @return [Array<String>] The complete array of included files.
|
91
|
+
#
|
92
|
+
def include(*i)
|
93
|
+
@includes += i.flatten
|
94
|
+
end
|
95
|
+
|
96
|
+
# Generate an array of enum symbols and values.
|
97
|
+
#
|
98
|
+
# You probably don't ever want to call this yourself. We use it to
|
99
|
+
# feed the enum creation code.
|
100
|
+
#
|
101
|
+
# @return [Array] Alternating symbols and values for the new enum.
|
102
|
+
#
|
103
|
+
def generate
|
104
|
+
binary = File.join Dir.tmpdir, "rb_ffi_enum_gen_bin_#{Process.pid}"
|
105
|
+
|
106
|
+
Tempfile.open("#{@name}.enum_generator") do |f|
|
107
|
+
@includes.each do |inc|
|
108
|
+
f.puts "#include <#{inc}>"
|
109
|
+
end
|
110
|
+
f.puts "\nint main(void)\n{"
|
111
|
+
|
112
|
+
@enum_symbols.each do |name, ruby_name|
|
113
|
+
f.puts <<-EOF.gsub(/^\t{6}/, '')
|
114
|
+
printf("#{ruby_name} %d\\n", #{name});
|
115
|
+
EOF
|
116
|
+
end
|
117
|
+
|
118
|
+
f.puts "\n\treturn 0;\n}"
|
119
|
+
f.flush
|
120
|
+
|
121
|
+
output = `gcc -x c -Wall -Werror #{f.path} -o #{binary} 2>&1`
|
122
|
+
|
123
|
+
unless $?.success? then
|
124
|
+
output = output.split("\n").map { |l| "\t#{l}" }.join "\n"
|
125
|
+
raise "Compilation error generating constants #{@prefix}:\n#{output}"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
output = `#{binary}`
|
130
|
+
File.unlink(binary + (FFI::Platform.windows? ? ".exe" : ""))
|
131
|
+
output.split("\n").inject([]) do |a, l|
|
132
|
+
l =~ /^(\S+)\s(.*)$/
|
133
|
+
a += [$1.to_sym, Integer($2)]
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
class Enum
|
139
|
+
# Set constants on the given module for each symbol in this enum
|
140
|
+
#
|
141
|
+
# This will blow up if a constant of the same name is already defined
|
142
|
+
# on `mod`, so you probably don't want to do that.
|
143
|
+
#
|
144
|
+
# @param mod [Module] The module upon which the constants will be set.
|
145
|
+
#
|
146
|
+
def set_consts(mod)
|
147
|
+
symbols.each { |s| mod.const_set(s, self[s]) }
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
metadata
ADDED
@@ -0,0 +1,166 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ffi-enum-generator
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Matt Palmer
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-11-03 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: git-version-bump
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0.10'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0.10'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: ffi
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '1.9'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '1.9'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: bundler
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: github-release
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: rake
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: redcarpet
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: yard
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
description:
|
127
|
+
email:
|
128
|
+
executables: []
|
129
|
+
extensions: []
|
130
|
+
extra_rdoc_files:
|
131
|
+
- README.md
|
132
|
+
files:
|
133
|
+
- lib/ffi/enum_generator.rb
|
134
|
+
- README.md
|
135
|
+
homepage: http://theshed.hezmatt.org/ffi-enum-generator
|
136
|
+
licenses: []
|
137
|
+
post_install_message:
|
138
|
+
rdoc_options: []
|
139
|
+
require_paths:
|
140
|
+
- lib
|
141
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
142
|
+
none: false
|
143
|
+
requirements:
|
144
|
+
- - ! '>='
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
version: '0'
|
147
|
+
segments:
|
148
|
+
- 0
|
149
|
+
hash: -4477082046855175583
|
150
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
151
|
+
none: false
|
152
|
+
requirements:
|
153
|
+
- - ! '>='
|
154
|
+
- !ruby/object:Gem::Version
|
155
|
+
version: '0'
|
156
|
+
segments:
|
157
|
+
- 0
|
158
|
+
hash: -4477082046855175583
|
159
|
+
requirements: []
|
160
|
+
rubyforge_project:
|
161
|
+
rubygems_version: 1.8.23
|
162
|
+
signing_key:
|
163
|
+
specification_version: 3
|
164
|
+
summary: Generate values for FFI enums directly
|
165
|
+
test_files: []
|
166
|
+
has_rdoc:
|