ffidb 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/AUTHORS +1 -0
  3. data/CHANGES.md +7 -0
  4. data/CREDITS.md +2 -0
  5. data/README.md +201 -0
  6. data/UNLICENSE +24 -0
  7. data/VERSION +1 -0
  8. data/bin/ffidb +387 -0
  9. data/etc/mappings/dart.yaml +35 -0
  10. data/etc/mappings/java.yaml +36 -0
  11. data/etc/mappings/lisp.yaml +35 -0
  12. data/etc/mappings/python.yaml +35 -0
  13. data/etc/mappings/ruby.yaml +35 -0
  14. data/etc/templates/c.erb +46 -0
  15. data/etc/templates/cpp.erb +45 -0
  16. data/etc/templates/dart.erb +64 -0
  17. data/etc/templates/go.erb +50 -0
  18. data/etc/templates/java.erb +56 -0
  19. data/etc/templates/lisp.erb +49 -0
  20. data/etc/templates/python.erb +59 -0
  21. data/etc/templates/ruby.erb +48 -0
  22. data/lib/ffidb.rb +34 -0
  23. data/lib/ffidb/enum.rb +37 -0
  24. data/lib/ffidb/errors.rb +64 -0
  25. data/lib/ffidb/exporter.rb +141 -0
  26. data/lib/ffidb/exporters.rb +28 -0
  27. data/lib/ffidb/exporters/c.rb +52 -0
  28. data/lib/ffidb/exporters/cpp.rb +13 -0
  29. data/lib/ffidb/exporters/csharp.rb +6 -0
  30. data/lib/ffidb/exporters/csv.rb +24 -0
  31. data/lib/ffidb/exporters/dart.rb +60 -0
  32. data/lib/ffidb/exporters/go.rb +16 -0
  33. data/lib/ffidb/exporters/haskell.rb +3 -0
  34. data/lib/ffidb/exporters/java.rb +39 -0
  35. data/lib/ffidb/exporters/json.rb +38 -0
  36. data/lib/ffidb/exporters/julia.rb +3 -0
  37. data/lib/ffidb/exporters/lisp.rb +41 -0
  38. data/lib/ffidb/exporters/luajit.rb +3 -0
  39. data/lib/ffidb/exporters/nim.rb +4 -0
  40. data/lib/ffidb/exporters/nodejs.rb +4 -0
  41. data/lib/ffidb/exporters/ocaml.rb +4 -0
  42. data/lib/ffidb/exporters/php.rb +4 -0
  43. data/lib/ffidb/exporters/python.rb +35 -0
  44. data/lib/ffidb/exporters/racket.rb +3 -0
  45. data/lib/ffidb/exporters/ruby.rb +33 -0
  46. data/lib/ffidb/exporters/rust.rb +5 -0
  47. data/lib/ffidb/exporters/yaml.rb +31 -0
  48. data/lib/ffidb/exporters/zig.rb +3 -0
  49. data/lib/ffidb/function.rb +70 -0
  50. data/lib/ffidb/glob.rb +28 -0
  51. data/lib/ffidb/header.rb +19 -0
  52. data/lib/ffidb/header_parser.rb +339 -0
  53. data/lib/ffidb/library.rb +120 -0
  54. data/lib/ffidb/library_parser.rb +132 -0
  55. data/lib/ffidb/location.rb +17 -0
  56. data/lib/ffidb/parameter.rb +35 -0
  57. data/lib/ffidb/registry.rb +87 -0
  58. data/lib/ffidb/release.rb +14 -0
  59. data/lib/ffidb/struct.rb +41 -0
  60. data/lib/ffidb/symbol_table.rb +90 -0
  61. data/lib/ffidb/symbolic.rb +67 -0
  62. data/lib/ffidb/sysexits.rb +21 -0
  63. data/lib/ffidb/type.rb +214 -0
  64. data/lib/ffidb/typedef.rb +38 -0
  65. data/lib/ffidb/union.rb +37 -0
  66. data/lib/ffidb/version.rb +21 -0
  67. metadata +197 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 832a289161ba25e274a8a6d3a20a1592ec40bff4e3774400e6d7f5b3286335c5
4
+ data.tar.gz: 6b27cb31d52109e2a44c5c8a72f830b772d306206acec33add96eb8faf23e464
5
+ SHA512:
6
+ metadata.gz: 3b6bc06df8f1d94ded39f0370874d6c02c2f6dc33321f8a7affc51a3f53e2db5300b9291447b5528758a9ba5b22e3402a45b01f5ee17cca5acb642db531298ea
7
+ data.tar.gz: 4fba31365dbb2b0567dd381075b8f36c3d3bcf07324c9edf143f19e7472e7eabe1c6a17a00fff7b945e55ca47db33b81d1c7eb034e28de32753bff0adb1da820
data/AUTHORS ADDED
@@ -0,0 +1 @@
1
+ * Arto Bendiken <arto@bendiken.net>
@@ -0,0 +1,7 @@
1
+ Changelog
2
+ =========
3
+
4
+ All notable changes to this project will be documented in this file.
5
+
6
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
7
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
@@ -0,0 +1,2 @@
1
+ Credits
2
+ =======
@@ -0,0 +1,201 @@
1
+ FFI DB Command-Line Interface (CLI)
2
+ ===================================
3
+
4
+ [![Project license](https://img.shields.io/badge/license-Public%20Domain-blue.svg)](https://unlicense.org)
5
+ [![RubyGems gem version](https://img.shields.io/gem/v/ffidb.svg)](https://rubygems.org/gems/ffidb)
6
+
7
+ Installation
8
+ ------------
9
+
10
+ The tool can be installed quickly and easily on any computer that has
11
+ [Ruby](https://www.ruby-lang.org/en/) available:
12
+
13
+ $ gem install ffidb
14
+
15
+ After installation, download and initialize the FFI DB registry as follows:
16
+
17
+ $ ffidb init
18
+
19
+ Your local FFI DB registry is located at the path `$HOME/.ffidb/`.
20
+
21
+ Examples (API)
22
+ --------------
23
+
24
+ ### Loading the library
25
+
26
+ require 'ffidb'
27
+
28
+ ### Enumerating FFI functions
29
+
30
+ FFIDB::Registry.open do |registry|
31
+ registry.open_library(:zlib) do |library|
32
+ library.each_function do |function|
33
+ p function
34
+ end
35
+ end
36
+ end
37
+
38
+ Reference (CLI)
39
+ ---------------
40
+
41
+ Commands:
42
+ ffidb export LIBRARY|SYMBOL... # Generate C/C++/Go/Java/Python/Ruby/etc code
43
+ ffidb help [COMMAND] # Describe available commands or one specific command
44
+ ffidb init # Initialize the registry (at: ~/.ffidb)
45
+ ffidb list [LIBRARY] # List FFI libraries and symbols
46
+ ffidb parse HEADER... # Parse .h header files
47
+ ffidb search PATTERN # Search for FFI symbols using a glob pattern
48
+ ffidb show SYMBOL # Show FFI symbol information
49
+ ffidb update # Fetch updates to the registry (at: ~/.ffidb)
50
+
51
+ Options:
52
+ -d, [--debug], [--no-debug] # Enable debugging
53
+ -v, [--verbose], [--no-verbose] # Be verbose (print warnings)
54
+ -q, [--quiet], [--no-quiet] # Be quiet (silence non-fatal errors)
55
+
56
+ ### Initializing the Registry
57
+
58
+ Usage:
59
+ ffidb init
60
+
61
+ Options:
62
+ -d, [--debug], [--no-debug] # Enable debugging
63
+ -v, [--verbose], [--no-verbose] # Be verbose (print warnings)
64
+ -q, [--quiet], [--no-quiet] # Be quiet (silence non-fatal errors)
65
+
66
+ Description:
67
+ Initializes the local FFIDB registry, a prerequisite for using FFIDB.
68
+
69
+ Your local FFIDB registry is located at $HOME/.ffidb.
70
+
71
+ This command is equivalent to:
72
+
73
+ $ git clone --depth=1 https://github.com/ffidb/ffidb.git $HOME/.ffidb
74
+
75
+ ### Updating the Registry
76
+
77
+ Usage:
78
+ ffidb update
79
+
80
+ Options:
81
+ -d, [--debug], [--no-debug] # Enable debugging
82
+ -v, [--verbose], [--no-verbose] # Be verbose (print warnings)
83
+ -q, [--quiet], [--no-quiet] # Be quiet (silence non-fatal errors)
84
+
85
+ Description:
86
+ Updates the local FFIDB registry, pulling updates from GitHub.
87
+
88
+ Your local FFIDB registry is located at $HOME/.ffidb.
89
+
90
+ This command is equivalent to:
91
+
92
+ $ cd $HOME/.ffidb && git pull
93
+
94
+ ### Listing Libraries and Symbols
95
+
96
+ Usage:
97
+ ffidb list [LIBRARY]
98
+
99
+ Options:
100
+ -d, [--debug], [--no-debug] # Enable debugging
101
+ -v, [--verbose], [--no-verbose] # Be verbose (print warnings)
102
+ -q, [--quiet], [--no-quiet] # Be quiet (silence non-fatal errors)
103
+
104
+ Description:
105
+ Lists libraries with their FFI symbols (such as functions).
106
+
107
+ For example:
108
+
109
+ $ ffidb list lua
110
+
111
+ ### Searching Libraries and Symbols
112
+
113
+ Usage:
114
+ ffidb search PATTERN
115
+
116
+ Options:
117
+ -d, [--debug], [--no-debug] # Enable debugging
118
+ -v, [--verbose], [--no-verbose] # Be verbose (print warnings)
119
+ -q, [--quiet], [--no-quiet] # Be quiet (silence non-fatal errors)
120
+
121
+ Description:
122
+ Searches for FFI symbols (for example, functions) using a glob pattern.
123
+
124
+ For example:
125
+
126
+ $ ffidb search sqlite3_*_blob
127
+
128
+ ### Viewing Symbol Information
129
+
130
+ Usage:
131
+ ffidb show SYMBOL
132
+
133
+ Options:
134
+ -d, [--debug], [--no-debug] # Enable debugging
135
+ -v, [--verbose], [--no-verbose] # Be verbose (print warnings)
136
+ -q, [--quiet], [--no-quiet] # Be quiet (silence non-fatal errors)
137
+
138
+ Description:
139
+ Shows information about an FFI symbol (for example, a function).
140
+
141
+ For example:
142
+
143
+ $ ffidb show lua_callk
144
+
145
+ ### Generating FFI Bindings
146
+
147
+ Usage:
148
+ ffidb export LIBRARY|SYMBOL...
149
+
150
+ Options:
151
+ -f, [--format=FORMAT] # Specify the output FORMAT (for example: java)
152
+ -L, [--library-path=DIRECTORY] # Load all libraries from DIRECTORY
153
+ [--exclude=PATTERN] # Exclude symbols matching the glob PATTERN
154
+ [--exclude-from=FILE] # Read exclude patterns from FILE
155
+ -d, [--debug], [--no-debug] # Enable debugging
156
+ -v, [--verbose], [--no-verbose] # Be verbose (print warnings)
157
+ -q, [--quiet], [--no-quiet] # Be quiet (silence non-fatal errors)
158
+
159
+ Description:
160
+ Generates code for a target language (e.g., C/C++/Java/Python/Ruby/etc).
161
+
162
+ Currently supported target formats and programming languages:
163
+
164
+ --format=c # C99
165
+ --format=c++ # C++11
166
+ --format=dart # Dart & Flutter
167
+ --format=go # Go (cgo)
168
+ --format=java # Java (JNA)
169
+ --format=json # JSON
170
+ --format=lisp # Common Lisp (CFFI)
171
+ --format=python # Python (ctypes)
172
+ --format=ruby # Ruby (FFI)
173
+ --format=yaml # YAML
174
+
175
+ For example:
176
+
177
+ $ ffidb export lua -f=c # Export Lua bindings as C code
178
+ $ ffidb export lua -f=cpp # Export Lua bindings as C++ code
179
+ $ ffidb export lua -f=java # Export Lua bindings as Java JNA code
180
+ $ ffidb export lua -f=python # Export Lua bindings as Python ctypes code
181
+ $ ffidb export lua -f=ruby # Export Lua bindings as Ruby FFI code
182
+
183
+ ### Parsing C Header Files
184
+
185
+ Usage:
186
+ ffidb parse HEADER...
187
+
188
+ Options:
189
+ -C, [--config=FILE] # Use a library.yaml configuration FILE
190
+ -D, [--define=VAR[=VAL]] # Define VAR as a preprocessor symbol
191
+ -I, [--include=DIRECTORY] # Add DIRECTORY to the headers search path
192
+ -d, [--debug], [--no-debug] # Enable debugging
193
+ -v, [--verbose], [--no-verbose] # Be verbose (print warnings)
194
+ -q, [--quiet], [--no-quiet] # Be quiet (silence non-fatal errors)
195
+
196
+ Description:
197
+ Parses .h header files, outputting YAML using the FFIDB schema.
198
+
199
+ Note: parsing requires installation of the 'ffi-clang' library:
200
+
201
+ $ gem install ffi-clang
@@ -0,0 +1,24 @@
1
+ This is free and unencumbered software released into the public domain.
2
+
3
+ Anyone is free to copy, modify, publish, use, compile, sell, or
4
+ distribute this software, either in source code form or as a compiled
5
+ binary, for any purpose, commercial or non-commercial, and by any
6
+ means.
7
+
8
+ In jurisdictions that recognize copyright laws, the author or authors
9
+ of this software dedicate any and all copyright interest in the
10
+ software to the public domain. We make this dedication for the benefit
11
+ of the public at large and to the detriment of our heirs and
12
+ successors. We intend this dedication to be an overt act of
13
+ relinquishment in perpetuity of all present and future rights to this
14
+ software under copyright law.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ For more information, please refer to <https://unlicense.org/>
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.12.0
@@ -0,0 +1,387 @@
1
+ #!/usr/bin/env ruby -W1
2
+ # This is free and unencumbered software released into the public domain.
3
+
4
+ require_relative '../lib/ffidb'
5
+
6
+ require 'thor' # https://rubygems.org/gems/thor
7
+
8
+ require 'pathname'
9
+ require 'yaml'
10
+
11
+ class CLI < Thor
12
+ include FFIDB::Sysexits
13
+
14
+ def self.exit_on_failure?() true end
15
+
16
+ class_option :debug, aliases: '-d', type: :boolean, desc: "Enable debugging"
17
+ class_option :verbose, aliases: '-v', type: :boolean, desc: "Be verbose (print warnings)"
18
+ class_option :quiet, aliases: '-q', type: :boolean, desc: "Be quiet (silence non-fatal errors)"
19
+
20
+ desc "export LIBRARY|SYMBOL...", "Generate C/C++/Go/Java/Python/Ruby/etc code"
21
+ long_desc <<~EOS
22
+ Generates code for a target language (e.g., C/C++/Java/Python/Ruby/etc).
23
+
24
+ Currently supported target formats and programming languages:
25
+
26
+ --format=c # C99
27
+ --format=c++ # C++11
28
+ --format=dart # Dart & Flutter
29
+ --format=go # Go (cgo)
30
+ --format=java # Java (JNA)
31
+ --format=json # JSON
32
+ --format=lisp # Common Lisp (CFFI)
33
+ --format=python # Python (ctypes)
34
+ --format=ruby # Ruby (FFI)
35
+ --format=yaml # YAML
36
+
37
+ For example:
38
+
39
+ $ ffidb export lua -f=c # Export Lua bindings as C code
40
+ $ ffidb export lua -f=cpp # Export Lua bindings as C++ code
41
+ $ ffidb export lua -f=java # Export Lua bindings as Java JNA code
42
+ $ ffidb export lua -f=python # Export Lua bindings as Python ctypes code
43
+ $ ffidb export lua -f=ruby # Export Lua bindings as Ruby FFI code
44
+ EOS
45
+ option :format, aliases: '-f', default: nil, desc: "Specify the output FORMAT (for example: java)"
46
+ option :type, aliases: '-t', default: nil, desc: "Specify the symbol TYPE (enum/struct/function)"
47
+ option :header, type: :boolean, default: true, desc: "Output the file header, or not"
48
+ option :module, aliases: '-M', default: nil, desc: "Provide name for the top-level module or namespace"
49
+ option :library_path, aliases: '-L', banner: 'DIRECTORY', desc: "Load all libraries from DIRECTORY"
50
+ option :exclude, repeatable: true, banner: 'PATTERN', desc: "Exclude symbols matching the glob PATTERN"
51
+ option :exclude_from, repeatable: true, banner: 'FILE', desc: "Read exclude patterns from FILE"
52
+ option :disable, repeatable: true, banner: 'PATTERN', desc: "Disable symbols matching the glob PATTERN"
53
+ option :disable_from, repeatable: true, banner: 'FILE', desc: "Read disable patterns from FILE"
54
+ def export(pattern, *patterns)
55
+ patterns.prepend(pattern)
56
+ symbol_kind = self.options[:type] ? self.options[:type].to_sym : nil
57
+
58
+ format = (self.options[:format] || 'yaml').to_sym
59
+
60
+ excludes = []
61
+ (self.options[:exclude] || []).each do |symbol|
62
+ excludes += symbol.include?(',') ? symbol.split(',') : [symbol]
63
+ end
64
+ (self.options[:exclude_from] || []).map do |exclude_path|
65
+ exclude_path = Pathname(exclude_path)
66
+ raise Errno::ENOENT, exclude_path.to_s unless exclude_path.exist?
67
+ exclude_path.each_line { |line| excludes << line.chomp }
68
+ end
69
+ excludes.sort!.uniq!
70
+ excludes.map! do |exclude_pattern|
71
+ FFIDB::Glob.new(exclude_pattern, ignore_case: false, match_substring: false)
72
+ end
73
+
74
+ disables = []
75
+ (self.options[:disable] || []).each do |symbol|
76
+ disables += symbol.include?(',') ? symbol.split(',') : [symbol]
77
+ end
78
+ (self.options[:disable_from] || []).map do |disable_path|
79
+ disable_path = Pathname(disable_path)
80
+ raise Errno::ENOENT, disable_path.to_s unless disable_path.exist?
81
+ disable_path.each_line { |line| disables << line.chomp }
82
+ end
83
+ disables.sort!.uniq!
84
+ disables.map! do |disable_pattern|
85
+ FFIDB::Glob.new(disable_pattern, ignore_case: false, match_substring: false)
86
+ end
87
+
88
+ self.check_registry_exists!
89
+ FFIDB::Registry.open do |registry|
90
+ exports = []
91
+ patterns.each do |pattern|
92
+ if library = registry.open_library(pattern)
93
+ library.each_symbol do |symbol|
94
+ next if symbol_kind && symbol_kind != symbol.kind
95
+ exports << [library, symbol.kind_weight, symbol] unless excludes.any? { |x| x === symbol.name }
96
+ end
97
+ else
98
+ matcher = FFIDB::Glob.new(pattern, ignore_case: true, match_substring: false)
99
+ registry.find_symbols(matcher) do |symbol, library|
100
+ next if symbol_kind && symbol_kind != symbol.kind
101
+ exports << [library, symbol.kind_weight, symbol] unless excludes.any? { |x| x === symbol.name }
102
+ end
103
+ end
104
+ end
105
+
106
+ FFIDB::Exporter.for(format).new(**options).emit do |export|
107
+ prev_library = nil
108
+ exports.sort!.uniq.each do |library, _, symbol|
109
+ if library != prev_library
110
+ export.finish_library if prev_library
111
+ export.begin_library(library)
112
+ prev_library = library
113
+ end
114
+ disabled = disables.any? { |pattern| pattern === symbol.name }
115
+ export.export_symbol(symbol, disabled: disabled)
116
+ end
117
+ export.finish_library if prev_library
118
+ end
119
+ end
120
+ rescue => error
121
+ raise error if debug?
122
+ warn "#{$0}: #{set_color(error, :red)}"
123
+ exit error.respond_to?(:exit_code) ? error.exit_code : EX_SOFTWARE
124
+ end
125
+
126
+ desc "init", "Initialize the registry (at: ~/.ffidb)"
127
+ long_desc <<~EOS
128
+ Initializes the local FFIDB registry, a prerequisite for using FFIDB.
129
+
130
+ Your local FFIDB registry is located at $HOME/.ffidb (#{FFIDB::Registry.default_path}).
131
+
132
+ This command is equivalent to:
133
+
134
+ $ git clone --depth=1 #{FFIDB::Registry::GIT_HTTPS_URL} $HOME/.ffidb
135
+ EOS
136
+ def init
137
+ registry_path = FFIDB::Registry.default_path
138
+ if registry_path.exist?
139
+ error = "The registry at #{registry_path} has already been initialized."
140
+ warn "#{$0}: #{set_color(error, :yellow)}" if verbose?
141
+ return
142
+ end
143
+ git_command = %Q(git clone --depth=1 #{FFIDB::Registry::GIT_HTTPS_URL} #{registry_path})
144
+ system git_command # TODO: improve this
145
+ if $?.exitstatus.nonzero?
146
+ error = "Failed to execute `#{git_command}`: exit code #{$?.exitstatus}."
147
+ warn "#{$0}: #{set_color(error, :red)}"
148
+ exit $?.exitstatus
149
+ end
150
+ Dir.chdir registry_path
151
+ rescue => error
152
+ raise error if debug?
153
+ warn "#{$0}: #{set_color(error, :red)}"
154
+ exit error.respond_to?(:exit_code) ? error.exit_code : EX_SOFTWARE
155
+ end
156
+
157
+ desc "list [LIBRARY]", "List FFI libraries and symbols"
158
+ long_desc <<~EOS
159
+ Lists libraries with their FFI symbols (such as functions).
160
+
161
+ For example:
162
+
163
+ $ ffidb list lua
164
+ EOS
165
+ option :type, aliases: '-t', default: nil, desc: "Specify the symbol TYPE (enum/struct/function)"
166
+ def list(library_name = nil)
167
+ symbol_kind = self.options[:type] ? self.options[:type].to_sym : nil
168
+ self.check_registry_exists!
169
+ FFIDB::Registry.open do |registry|
170
+ registry.each_library do |library|
171
+ next if library_name && library_name != library.name
172
+ p library if debug?
173
+ library.each_symbol do |symbol|
174
+ next if symbol_kind && symbol_kind != symbol.kind
175
+ p symbol if debug?
176
+ print "#{library.name}"
177
+ print verbose? ? "@#{library.version}" : '' # TODO: resolve stable symlinks
178
+ print "\t#{symbol.name}"
179
+ if verbose?
180
+ print "\t#{symbol.kind}"
181
+ print "\t\t// #{symbol.definition.to_s}" if symbol.function?
182
+ end
183
+ puts
184
+ end
185
+ end
186
+ end
187
+ rescue => error
188
+ raise error if debug?
189
+ warn "#{$0}: #{set_color(error, :red)}"
190
+ exit error.respond_to?(:exit_code) ? error.exit_code : EX_SOFTWARE
191
+ end
192
+
193
+ desc "parse HEADER...", "Parse .h header files"
194
+ long_desc <<~EOS
195
+ Parses .h header files, outputting YAML using the FFIDB schema.
196
+
197
+ Note: parsing requires installation of the 'ffi-clang' library:
198
+
199
+ $ gem install ffi-clang
200
+ EOS
201
+ option :config, aliases: '-C', banner: 'FILE', desc: "Use a library.yaml configuration FILE"
202
+ option :define, aliases: '-D', repeatable: true, banner: 'VAR[=VAL]', desc: "Define VAR as a preprocessor symbol"
203
+ option :include, aliases: '-I', repeatable: true, banner: 'DIRECTORY', desc: "Add DIRECTORY to the headers search path"
204
+ option :format, aliases: '-f', default: nil, desc: "Specify the output FORMAT (default: yaml)"
205
+ def parse(path, *paths)
206
+ base_directory = nil
207
+ paths.prepend(path)
208
+ paths = paths.inject([]) do |paths, path|
209
+ path = Pathname(path)
210
+ case
211
+ when !path.exist?
212
+ raise "Path does not exist: #{path}"
213
+ when path.directory?
214
+ base_directory = path if base_directory.nil?
215
+ paths.concat(Dir["#{path}/**/*.h"].sort.map { |p| Pathname(p) })
216
+ else paths << path
217
+ end
218
+ end
219
+ base_directory = paths.first.dirname if base_directory.nil?
220
+
221
+ begin
222
+ $VERBOSE = nil # suppress deprecation warnings from ffi-clang
223
+ require 'ffi/clang' # https://rubygems.org/gems/ffi-clang
224
+ $VERBOSE = false
225
+ rescue LoadError => error
226
+ raise error if debug?
227
+ warn "#{$0}: #{set_color(error, :red)}"
228
+ exit EX_UNAVAILABLE
229
+ end
230
+
231
+ FFIDB::HeaderParser.new(base_directory: base_directory, debug: verbose? || debug?).tap do |parser|
232
+ # Parse a library.yaml configuration file, if given:
233
+ library = nil
234
+ if config_path = self.options[:config]
235
+ config_path = Pathname(config_path)
236
+ raise Errno::ENOENT, config_path.to_s unless config_path.exist?
237
+ config = YAML.load(config_path.read).transform_keys(&:to_sym)
238
+ (config[:configure] || []).each do |var_and_val|
239
+ parser.parse_macro! var_and_val
240
+ end
241
+ (config[:exclude] || []).each do |symbol|
242
+ parser.exclude_symbols[symbol] = true
243
+ end
244
+ (config[:include] || []).each do |symbol|
245
+ parser.include_symbols[symbol] = true
246
+ end
247
+ end
248
+
249
+ # Parse and define all specified -D preprocessor symbols:
250
+ (self.options[:define] || []).each do |var_and_val|
251
+ parser.parse_macro! var_and_val
252
+ end
253
+
254
+ # Add all specified -I directories to the headers search path:
255
+ (self.options[:include] || []).each do |dir_path|
256
+ parser.add_include_path! Pathname(dir_path).expand_path
257
+ end
258
+
259
+ FFIDB::Exporter.for(self.options[:format] || :yaml).new(**options).emit do |export|
260
+ export.begin_library(library)
261
+ paths.each do |path|
262
+ header = parser.parse_header(path) do |exception|
263
+ case exception
264
+ when FFIDB::ParseError
265
+ warn "#{$0}: #{set_color(exception.to_s, :red)}" unless quiet?
266
+ when FFIDB::ParseWarning
267
+ warn "#{$0}: #{set_color(exception.to_s, :yellow)}" if verbose?
268
+ else raise exception
269
+ end
270
+ end
271
+ export.export_header(header)
272
+ end
273
+ export.finish_library
274
+ end
275
+ end
276
+ rescue => error
277
+ raise error if debug?
278
+ warn "#{$0}: #{set_color(error, :red)}"
279
+ exit error.respond_to?(:exit_code) ? error.exit_code : EX_SOFTWARE
280
+ end
281
+
282
+ desc "search PATTERN", "Search for FFI symbols using a glob pattern"
283
+ long_desc <<~EOS
284
+ Searches for FFI symbols (for example, functions) using a glob pattern.
285
+
286
+ For example:
287
+
288
+ $ ffidb search sqlite3_*_blob
289
+ EOS
290
+ option :type, aliases: '-t', default: nil, desc: "Specify the symbol TYPE (enum/struct/function)"
291
+ def search(pattern)
292
+ symbol_kind = self.options[:type] ? self.options[:type].to_sym : nil
293
+ matcher = FFIDB::Glob.new(pattern, ignore_case: true, match_substring: true)
294
+ p self.options, matcher if debug?
295
+ self.check_registry_exists!
296
+ FFIDB::Registry.open do |registry|
297
+ registry.find_symbols(matcher, kind: symbol_kind) do |symbol, library|
298
+ puts "#{library.name}\t#{symbol.name}" # TODO: improve formatting
299
+ end
300
+ end
301
+ rescue => error
302
+ raise error if debug?
303
+ warn "#{$0}: #{set_color(error, :red)}"
304
+ exit error.respond_to?(:exit_code) ? error.exit_code : EX_SOFTWARE
305
+ end
306
+
307
+ desc "show SYMBOL", "Show FFI symbol information"
308
+ long_desc <<~EOS
309
+ Shows information about an FFI symbol (for example, a function).
310
+
311
+ For example:
312
+
313
+ $ ffidb show lua_callk
314
+ EOS
315
+ option :type, aliases: '-t', default: nil, desc: "Specify the symbol TYPE (enum/struct/function)"
316
+ def show(symbol_name)
317
+ symbol_kind = self.options[:type] ? self.options[:type].to_sym : nil
318
+ p self.options, symbol_name if debug?
319
+ self.check_registry_exists!
320
+ FFIDB::Registry.open do |registry|
321
+ found = registry.find_symbols(symbol_name, kind: symbol_kind) do |symbol, library|
322
+ puts symbol.to_yaml
323
+ puts
324
+ end
325
+ raise FFIDB::SymbolNotFound.new(symbol_kind || :symbol, symbol_name) unless found
326
+ end
327
+ rescue => error
328
+ raise error if debug?
329
+ warn "#{$0}: #{set_color(error, :red)}"
330
+ exit error.respond_to?(:exit_code) ? error.exit_code : EX_SOFTWARE
331
+ end
332
+
333
+ desc "update", "Fetch updates to the registry (at: ~/.ffidb)"
334
+ long_desc <<~EOS
335
+ Updates the local FFIDB registry, pulling updates from GitHub.
336
+
337
+ Your local FFIDB registry is located at $HOME/.ffidb (#{FFIDB::Registry.default_path}).
338
+
339
+ This command is equivalent to:
340
+
341
+ $ cd $HOME/.ffidb && git pull
342
+ EOS
343
+ def update
344
+ registry_path = self.check_registry_exists!
345
+ Dir.chdir registry_path
346
+ git_command = %Q(git pull)
347
+ system git_command # TODO: improve this
348
+ if $?.exitstatus.nonzero?
349
+ error = "Failed to execute `#{git_command}` in #{registry_path}: exit code #{$?.exitstatus}."
350
+ warn "#{$0}: #{set_color(error, :red)}"
351
+ exit $?.exitstatus
352
+ end
353
+ rescue => error
354
+ raise error if debug?
355
+ warn "#{$0}: #{set_color(error, :red)}"
356
+ exit error.respond_to?(:exit_code) ? error.exit_code : EX_SOFTWARE
357
+ end
358
+
359
+ protected
360
+
361
+ def check_registry_exists!
362
+ registry_path = FFIDB::Registry.default_path
363
+ raise FFIDB::RegistryError, "Registry at #{registry_path} not initialized (run `#{$0} init` first?)" unless registry_path.exist?
364
+ registry_path
365
+ end
366
+
367
+ def debug?() self.options[:debug] end
368
+ def verbose?() self.options[:verbose] || self.debug? end
369
+ def quiet?() self.options[:quiet] end
370
+ end # CLI
371
+
372
+ # Fix for https://github.com/erikhuda/thor/issues/398
373
+ class Thor::Shell::Basic
374
+ def print_wrapped(message, options = {})
375
+ indent = (options[:indent] || 0).to_i
376
+ if indent.zero?
377
+ self.stdout.puts message
378
+ else
379
+ message.each_line do |message_line|
380
+ self.stdout.print ' ' * indent
381
+ self.stdout.puts message_line.chomp
382
+ end
383
+ end
384
+ end
385
+ end # Thor::Shell::Basic
386
+
387
+ CLI.start(ARGV)