ffi-enum-generator 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/README.md +91 -0
  2. data/lib/ffi/enum_generator.rb +150 -0
  3. metadata +166 -0
@@ -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: