ffidb 0.12.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.
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)