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.
- checksums.yaml +7 -0
- data/AUTHORS +1 -0
- data/CHANGES.md +7 -0
- data/CREDITS.md +2 -0
- data/README.md +201 -0
- data/UNLICENSE +24 -0
- data/VERSION +1 -0
- data/bin/ffidb +387 -0
- data/etc/mappings/dart.yaml +35 -0
- data/etc/mappings/java.yaml +36 -0
- data/etc/mappings/lisp.yaml +35 -0
- data/etc/mappings/python.yaml +35 -0
- data/etc/mappings/ruby.yaml +35 -0
- data/etc/templates/c.erb +46 -0
- data/etc/templates/cpp.erb +45 -0
- data/etc/templates/dart.erb +64 -0
- data/etc/templates/go.erb +50 -0
- data/etc/templates/java.erb +56 -0
- data/etc/templates/lisp.erb +49 -0
- data/etc/templates/python.erb +59 -0
- data/etc/templates/ruby.erb +48 -0
- data/lib/ffidb.rb +34 -0
- data/lib/ffidb/enum.rb +37 -0
- data/lib/ffidb/errors.rb +64 -0
- data/lib/ffidb/exporter.rb +141 -0
- data/lib/ffidb/exporters.rb +28 -0
- data/lib/ffidb/exporters/c.rb +52 -0
- data/lib/ffidb/exporters/cpp.rb +13 -0
- data/lib/ffidb/exporters/csharp.rb +6 -0
- data/lib/ffidb/exporters/csv.rb +24 -0
- data/lib/ffidb/exporters/dart.rb +60 -0
- data/lib/ffidb/exporters/go.rb +16 -0
- data/lib/ffidb/exporters/haskell.rb +3 -0
- data/lib/ffidb/exporters/java.rb +39 -0
- data/lib/ffidb/exporters/json.rb +38 -0
- data/lib/ffidb/exporters/julia.rb +3 -0
- data/lib/ffidb/exporters/lisp.rb +41 -0
- data/lib/ffidb/exporters/luajit.rb +3 -0
- data/lib/ffidb/exporters/nim.rb +4 -0
- data/lib/ffidb/exporters/nodejs.rb +4 -0
- data/lib/ffidb/exporters/ocaml.rb +4 -0
- data/lib/ffidb/exporters/php.rb +4 -0
- data/lib/ffidb/exporters/python.rb +35 -0
- data/lib/ffidb/exporters/racket.rb +3 -0
- data/lib/ffidb/exporters/ruby.rb +33 -0
- data/lib/ffidb/exporters/rust.rb +5 -0
- data/lib/ffidb/exporters/yaml.rb +31 -0
- data/lib/ffidb/exporters/zig.rb +3 -0
- data/lib/ffidb/function.rb +70 -0
- data/lib/ffidb/glob.rb +28 -0
- data/lib/ffidb/header.rb +19 -0
- data/lib/ffidb/header_parser.rb +339 -0
- data/lib/ffidb/library.rb +120 -0
- data/lib/ffidb/library_parser.rb +132 -0
- data/lib/ffidb/location.rb +17 -0
- data/lib/ffidb/parameter.rb +35 -0
- data/lib/ffidb/registry.rb +87 -0
- data/lib/ffidb/release.rb +14 -0
- data/lib/ffidb/struct.rb +41 -0
- data/lib/ffidb/symbol_table.rb +90 -0
- data/lib/ffidb/symbolic.rb +67 -0
- data/lib/ffidb/sysexits.rb +21 -0
- data/lib/ffidb/type.rb +214 -0
- data/lib/ffidb/typedef.rb +38 -0
- data/lib/ffidb/union.rb +37 -0
- data/lib/ffidb/version.rb +21 -0
- metadata +197 -0
@@ -0,0 +1,49 @@
|
|
1
|
+
<% if header? %>
|
2
|
+
; <%= FFIDB.header %>
|
3
|
+
|
4
|
+
<% end %>
|
5
|
+
(asdf:load-system :cffi)
|
6
|
+
<% for library in @libraries %>
|
7
|
+
<% if library && @functions[library] %>
|
8
|
+
|
9
|
+
(cffi:define-foreign-library <%= library&.name || :lib %>
|
10
|
+
(t (:default "<%= dlopen_paths_for(library).first %>")))
|
11
|
+
|
12
|
+
(cffi:use-foreign-library <%= library&.name || :lib %>)
|
13
|
+
<% end %>
|
14
|
+
<% for enum in @enums[library] || [] %>
|
15
|
+
|
16
|
+
<% if enum.comment %>
|
17
|
+
;; <%= enum.comment %>
|
18
|
+
<% end %>
|
19
|
+
(cffi:defcenum <%= enum.name %>
|
20
|
+
<% for name, value in enum.values || {} %>
|
21
|
+
(:<%= name %> <%= value %>)
|
22
|
+
<% end %>
|
23
|
+
)
|
24
|
+
<% end %>
|
25
|
+
<% for struct in @structs[library] || [] %>
|
26
|
+
|
27
|
+
<% if struct.comment %>
|
28
|
+
;; <%= struct.comment %>
|
29
|
+
<% end %>
|
30
|
+
<% if struct.opaque? %>
|
31
|
+
(cffi:defctype <%= struct.name %> :pointer)
|
32
|
+
<% else %>
|
33
|
+
(cffi:defcstruct <%= struct.name %>
|
34
|
+
<% for name, type in struct.fields || {} %>
|
35
|
+
(<%= name %> <%= struct_type(type).map(&:inspect).join(' ') %>)
|
36
|
+
<% end %>
|
37
|
+
)
|
38
|
+
<% end %>
|
39
|
+
<% end %>
|
40
|
+
<% for function in @functions[library] || [] %>
|
41
|
+
|
42
|
+
<% if function.comment %>
|
43
|
+
;; <%= function.comment %>
|
44
|
+
<% end %>
|
45
|
+
(cffi:defcfun "<%= function.name %>" <%= param_type(function.type).inspect %> <%=
|
46
|
+
function.parameters.each_value.map { |p| "(#{p.name} #{param_type(p.type).inspect})" }.join(' ')
|
47
|
+
%>)
|
48
|
+
<% end %>
|
49
|
+
<% end %>
|
@@ -0,0 +1,59 @@
|
|
1
|
+
<% if header? %>
|
2
|
+
# <%= FFIDB.header %>
|
3
|
+
|
4
|
+
<% end %>
|
5
|
+
import ctypes, ctypes.util
|
6
|
+
<% for library in @libraries %>
|
7
|
+
<% if library && @functions[library] %>
|
8
|
+
|
9
|
+
<%= library&.name || :lib %> = ctypes.CDLL(
|
10
|
+
<% self.dlopen_paths_for(library).each_with_index do |library_path, i| %>
|
11
|
+
<%= i.nonzero? ? 'or ' : '' %>ctypes.util.find_library("<%= library_path %>")
|
12
|
+
<% end %>
|
13
|
+
<% if !options[:library_path] && library&.dlopen %>
|
14
|
+
or "<%= library.dlopen.first %>"
|
15
|
+
<% end %>
|
16
|
+
)
|
17
|
+
<% end %>
|
18
|
+
<% for enum in @enums[library] || [] %>
|
19
|
+
|
20
|
+
<% if enum.comment %>
|
21
|
+
# <%= enum.comment %>
|
22
|
+
<% end %>
|
23
|
+
<%= enum.name %> = ctypes.c_int
|
24
|
+
<% for name, value in enum.values || {} %>
|
25
|
+
<%= name %> = <%= value %>
|
26
|
+
<% end %>
|
27
|
+
<% end %>
|
28
|
+
<% for struct in @structs[library] || [] %>
|
29
|
+
|
30
|
+
<% if struct.comment %>
|
31
|
+
# <%= struct.comment %>
|
32
|
+
<% end %>
|
33
|
+
<% if struct.opaque? %>
|
34
|
+
<%= struct.name %> = ctypes.POINTER(ctypes.c_void)
|
35
|
+
<% else %>
|
36
|
+
class <%= struct.name %>(Structure):
|
37
|
+
<% if struct.fields.nil? || struct.fields.empty? %>
|
38
|
+
_fields_ = []
|
39
|
+
<% else %>
|
40
|
+
_fields_ = [
|
41
|
+
<% for name, type in struct.fields || {} %>
|
42
|
+
(<%= name.to_s.inspect %>, <%= struct_type(type) %>), # <%= type %>
|
43
|
+
<% end %>
|
44
|
+
]
|
45
|
+
<% end %>
|
46
|
+
<% end %>
|
47
|
+
<% end %>
|
48
|
+
<% for function in @functions[library] || [] %>
|
49
|
+
|
50
|
+
<% if function.comment %>
|
51
|
+
# <%= function.comment %>
|
52
|
+
<% end %>
|
53
|
+
<%= function.name %> = <%= library&.name || :lib %>.<%= function.name %>
|
54
|
+
<%= function.name %>.restype = <%= param_type(function.type) %>
|
55
|
+
<%= function.name %>.argtypes = [<%=
|
56
|
+
function.parameters.each_value.map { |p| param_type(p.type) }.join(', ')
|
57
|
+
%>]
|
58
|
+
<% end %>
|
59
|
+
<% end %>
|
@@ -0,0 +1,48 @@
|
|
1
|
+
<% if header? %>
|
2
|
+
# <%= FFIDB.header %>
|
3
|
+
|
4
|
+
<% end %>
|
5
|
+
require 'ffi'
|
6
|
+
<% for library in @libraries %>
|
7
|
+
|
8
|
+
module <%= options[:module] || library&.name&.capitalize || :FFI %>
|
9
|
+
<% if library && @functions[library] %>
|
10
|
+
extend FFI::Library
|
11
|
+
ffi_lib [<%= dlopen_paths_for(library).map(&:inspect).join(', ') %>]
|
12
|
+
<% end %>
|
13
|
+
<% for enum in @enums[library] || [] %>
|
14
|
+
|
15
|
+
<% if enum.comment %>
|
16
|
+
# <%= enum.comment %>
|
17
|
+
<% end %>
|
18
|
+
<%= enum.name %> = :int
|
19
|
+
<% for name, value in enum.values || {} %>
|
20
|
+
<%= name %> = <%= value %>
|
21
|
+
<% end %>
|
22
|
+
<% end %>
|
23
|
+
<% for struct in @structs[library] || [] %>
|
24
|
+
|
25
|
+
<% if struct.comment %>
|
26
|
+
# <%= struct.comment %>
|
27
|
+
<% end %>
|
28
|
+
<% if struct.opaque? %>
|
29
|
+
<%= struct.name %> = FFI::Pointer
|
30
|
+
<% else %>
|
31
|
+
class <%= struct.name %> < FFI::Struct
|
32
|
+
<% for (name, type), i in (struct.fields || {}).each_with_index %>
|
33
|
+
<%= i.zero? ? 'layout' : ' '*6 %> :<%= name %>, <%= struct_type(type).inspect %><%= (i == (struct.fields || {}).size-1) ? '' : ',' %> # <%= type %>
|
34
|
+
<% end %>
|
35
|
+
end
|
36
|
+
<% end %>
|
37
|
+
<% end %>
|
38
|
+
<% for function in @functions[library] || [] %>
|
39
|
+
|
40
|
+
<% if function.comment %>
|
41
|
+
# <%= function.comment %>
|
42
|
+
<% end %>
|
43
|
+
attach_function :<%= function.name %>, [<%=
|
44
|
+
function.parameters.each_value.map { |p| param_type(p.type).inspect }.join(', ')
|
45
|
+
%>], :<%= param_type(function.type) %>
|
46
|
+
<% end %>
|
47
|
+
end # <%= options[:module] || library&.name&.capitalize || :FFI %>
|
48
|
+
<% end %>
|
data/lib/ffidb.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# This is free and unencumbered software released into the public domain.
|
2
|
+
|
3
|
+
require_relative 'ffidb/enum'
|
4
|
+
require_relative 'ffidb/errors'
|
5
|
+
require_relative 'ffidb/exporter'
|
6
|
+
require_relative 'ffidb/exporters'
|
7
|
+
require_relative 'ffidb/function'
|
8
|
+
require_relative 'ffidb/glob'
|
9
|
+
require_relative 'ffidb/header'
|
10
|
+
require_relative 'ffidb/header_parser'
|
11
|
+
require_relative 'ffidb/library'
|
12
|
+
require_relative 'ffidb/library_parser'
|
13
|
+
require_relative 'ffidb/location'
|
14
|
+
require_relative 'ffidb/parameter'
|
15
|
+
require_relative 'ffidb/registry'
|
16
|
+
require_relative 'ffidb/release'
|
17
|
+
require_relative 'ffidb/struct'
|
18
|
+
require_relative 'ffidb/symbolic'
|
19
|
+
require_relative 'ffidb/symbol_table'
|
20
|
+
require_relative 'ffidb/sysexits'
|
21
|
+
require_relative 'ffidb/type'
|
22
|
+
require_relative 'ffidb/typedef'
|
23
|
+
require_relative 'ffidb/union'
|
24
|
+
require_relative 'ffidb/version'
|
25
|
+
|
26
|
+
module FFIDB
|
27
|
+
HEADER = "This is free and unencumbered software released into the public domain.".freeze
|
28
|
+
|
29
|
+
##
|
30
|
+
# @return [String]
|
31
|
+
def self.header
|
32
|
+
HEADER
|
33
|
+
end
|
34
|
+
end # FFIDB
|
data/lib/ffidb/enum.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# This is free and unencumbered software released into the public domain.
|
2
|
+
|
3
|
+
require_relative 'symbolic'
|
4
|
+
|
5
|
+
module FFIDB
|
6
|
+
class Enum < ::Struct.new(:name, :values, :comment)
|
7
|
+
include Symbolic
|
8
|
+
|
9
|
+
##
|
10
|
+
# @param [Symbol, #to_sym] name
|
11
|
+
# @param [Map<String, Integer>] values
|
12
|
+
# @param [String, #to_s] comment
|
13
|
+
def initialize(name, values = {}, comment = nil)
|
14
|
+
super(name.to_sym, values || {}, comment&.to_s)
|
15
|
+
end
|
16
|
+
|
17
|
+
##
|
18
|
+
# @return [Boolean]
|
19
|
+
def enum?() return true end
|
20
|
+
|
21
|
+
##
|
22
|
+
# @return [String]
|
23
|
+
def to_s
|
24
|
+
"enum #{self.name}"
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# @return [Hash<Symbol, Type>]
|
29
|
+
def to_h
|
30
|
+
{
|
31
|
+
name: self.name.to_s,
|
32
|
+
comment: self.comment,
|
33
|
+
values: self.values,
|
34
|
+
}.delete_if { |k, v| v.nil? }
|
35
|
+
end
|
36
|
+
end # Enum
|
37
|
+
end # FFIDB
|
data/lib/ffidb/errors.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# This is free and unencumbered software released into the public domain.
|
2
|
+
|
3
|
+
require_relative 'sysexits'
|
4
|
+
|
5
|
+
module FFIDB
|
6
|
+
##
|
7
|
+
# Base class for FFIDB errors.
|
8
|
+
class Error < StandardError
|
9
|
+
EXIT_CODE = Sysexits::EX_SOFTWARE
|
10
|
+
|
11
|
+
##
|
12
|
+
# @return [Integer]
|
13
|
+
def exit_code
|
14
|
+
self.class.const_get(:EXIT_CODE)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
##
|
19
|
+
# An error indicating that a problem with the registry.
|
20
|
+
class RegistryError < Error
|
21
|
+
EXIT_CODE = Sysexits::EX_CONFIG
|
22
|
+
end
|
23
|
+
|
24
|
+
##
|
25
|
+
# An error indicating that opening a registry directory requires a newer
|
26
|
+
# version of FFIDB.rb than the current one.
|
27
|
+
class RegistryVersionMismatch < RegistryError; end
|
28
|
+
|
29
|
+
##
|
30
|
+
# An error indicating that an FFI symbol was not found.
|
31
|
+
class SymbolNotFound < Error
|
32
|
+
EXIT_CODE = Sysexits::EX_NOINPUT
|
33
|
+
|
34
|
+
def initialize(symbol_kind, symbol_name)
|
35
|
+
super("#{symbol_kind.to_s.capitalize} not found: #{symbol_name}")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# An error indicating that an FFI function was not found.
|
41
|
+
class FunctionNotFound < SymbolNotFound
|
42
|
+
def initialize(function_name)
|
43
|
+
super(:function, function_name)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# A warning raised during header file parsing.
|
49
|
+
class ParseWarning < Error
|
50
|
+
EXIT_CODE = Sysexits::EX_DATAERR
|
51
|
+
end
|
52
|
+
|
53
|
+
##
|
54
|
+
# An error raised during header file parsing.
|
55
|
+
class ParseError < Error
|
56
|
+
EXIT_CODE = Sysexits::EX_DATAERR
|
57
|
+
end
|
58
|
+
|
59
|
+
##
|
60
|
+
# A fatal error raised during header file parsing.
|
61
|
+
class ParsePanic < Error
|
62
|
+
EXIT_CODE = Sysexits::EX_DATAERR
|
63
|
+
end
|
64
|
+
end # FFIDB
|
@@ -0,0 +1,141 @@
|
|
1
|
+
# This is free and unencumbered software released into the public domain.
|
2
|
+
|
3
|
+
#require 'erb'
|
4
|
+
require 'tilt' # https://rubygems.org/gems/tilt
|
5
|
+
require 'yaml'
|
6
|
+
|
7
|
+
module FFIDB
|
8
|
+
class Exporter
|
9
|
+
def self.for(format) # TODO
|
10
|
+
require_relative 'exporters'
|
11
|
+
case format&.to_sym
|
12
|
+
when :c, :c99, :c11, :c18 then Exporters::C
|
13
|
+
when :'c++', :'c++11', :'c++14', :'c++17', :'c++20', :cpp, :cxx then Exporters::Cpp
|
14
|
+
when :csv then Exporters::CSV
|
15
|
+
when :dart, :flutter then Exporters::Dart
|
16
|
+
when :go, :cgo then Exporters::Go
|
17
|
+
when :java, :jna then Exporters::Java
|
18
|
+
when :json then Exporters::JSON
|
19
|
+
when :lisp, :'common-lisp' then Exporters::Lisp
|
20
|
+
when :python, :py then Exporters::Python
|
21
|
+
when :ruby, :rb then Exporters::Ruby
|
22
|
+
# TODO: csharp, haskell, julia, luajit, nim, nodejs, ocaml, php, racket, rust, zig
|
23
|
+
when :yaml then Exporters::YAML
|
24
|
+
else raise "unknown output format: #{format}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
attr_reader :options
|
29
|
+
|
30
|
+
def initialize(stream = $stdout, **kwargs)
|
31
|
+
@stream = stream
|
32
|
+
@options = kwargs.transform_keys(&:to_sym).freeze
|
33
|
+
end
|
34
|
+
|
35
|
+
def debug?
|
36
|
+
self.options[:debug]
|
37
|
+
end
|
38
|
+
|
39
|
+
def verbose?
|
40
|
+
self.options[:verbose] || self.debug?
|
41
|
+
end
|
42
|
+
|
43
|
+
def header?
|
44
|
+
self.options[:header]
|
45
|
+
end
|
46
|
+
|
47
|
+
def emit(&block)
|
48
|
+
begin
|
49
|
+
self.begin
|
50
|
+
yield self
|
51
|
+
self.finish
|
52
|
+
ensure
|
53
|
+
self.close
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def dlopen_paths_for(library)
|
58
|
+
if library_path = self.options[:library_path]
|
59
|
+
library.objects.map { |lib| library_path.delete_suffix('/') << "/" << lib }
|
60
|
+
else
|
61
|
+
library.objects + library.dlopen
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def begin() end
|
66
|
+
|
67
|
+
def begin_library(library)
|
68
|
+
@library = library
|
69
|
+
@libraries ||= []
|
70
|
+
@libraries << library
|
71
|
+
@typedefs ||= {}
|
72
|
+
@enums ||= {}
|
73
|
+
@structs ||= {}
|
74
|
+
@unions ||= {}
|
75
|
+
@functions ||= {}
|
76
|
+
end
|
77
|
+
|
78
|
+
def export_header(header)
|
79
|
+
header.typedefs.sort.each { |typedef| self.export_typedef(typedef) }
|
80
|
+
header.enums.sort.each { |enum| self.export_enum(enum) }
|
81
|
+
header.structs.sort.each { |struct| self.export_struct(struct) }
|
82
|
+
header.unions.sort.each { |union| self.export_union(union) }
|
83
|
+
header.functions.sort.each { |function| self.export_function(function) }
|
84
|
+
end
|
85
|
+
|
86
|
+
def export_symbol(symbol, disabled: nil)
|
87
|
+
self.__send__("export_#{symbol.kind}", symbol, disabled: disabled)
|
88
|
+
end
|
89
|
+
|
90
|
+
def export_typedef(typedef, disabled: nil)
|
91
|
+
(@typedefs[@library] ||= []) << typedef
|
92
|
+
end
|
93
|
+
|
94
|
+
def export_enum(enum, disabled: nil)
|
95
|
+
(@enums[@library] ||= []) << enum
|
96
|
+
end
|
97
|
+
|
98
|
+
def export_struct(struct, disabled: nil)
|
99
|
+
(@structs[@library] ||= []) << struct
|
100
|
+
end
|
101
|
+
|
102
|
+
def export_union(union, disabled: nil)
|
103
|
+
(@unions[@library] ||= []) << union
|
104
|
+
end
|
105
|
+
|
106
|
+
def export_function(function, disabled: nil)
|
107
|
+
(@functions[@library] ||= []) << function
|
108
|
+
end
|
109
|
+
|
110
|
+
def finish_library
|
111
|
+
@library = nil
|
112
|
+
end
|
113
|
+
|
114
|
+
def finish() end
|
115
|
+
|
116
|
+
def close() end
|
117
|
+
|
118
|
+
protected
|
119
|
+
|
120
|
+
def puts(*args)
|
121
|
+
@stream.puts *args
|
122
|
+
end
|
123
|
+
|
124
|
+
def print(*args)
|
125
|
+
@stream.print *args
|
126
|
+
end
|
127
|
+
|
128
|
+
def render_template(template_name)
|
129
|
+
#ERB.new(self.load_template(template_name)).result(binding)
|
130
|
+
Tilt.new(self.path_to_template(template_name)).render(self)
|
131
|
+
end
|
132
|
+
|
133
|
+
def load_template(template_name)
|
134
|
+
File.read(self.path_to_template(template_name))
|
135
|
+
end
|
136
|
+
|
137
|
+
def path_to_template(template_name)
|
138
|
+
File.expand_path("../../etc/templates/#{template_name}", __dir__)
|
139
|
+
end
|
140
|
+
end # Exporter
|
141
|
+
end # FFIDB
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# This is free and unencumbered software released into the public domain.
|
2
|
+
|
3
|
+
module FFIDB
|
4
|
+
module Exporters; end
|
5
|
+
end
|
6
|
+
|
7
|
+
require_relative 'exporters/c'
|
8
|
+
require_relative 'exporters/cpp'
|
9
|
+
require_relative 'exporters/csharp'
|
10
|
+
require_relative 'exporters/csv'
|
11
|
+
require_relative 'exporters/dart'
|
12
|
+
require_relative 'exporters/go'
|
13
|
+
require_relative 'exporters/haskell'
|
14
|
+
require_relative 'exporters/java'
|
15
|
+
require_relative 'exporters/julia'
|
16
|
+
require_relative 'exporters/json'
|
17
|
+
require_relative 'exporters/lisp'
|
18
|
+
require_relative 'exporters/luajit'
|
19
|
+
require_relative 'exporters/nim'
|
20
|
+
require_relative 'exporters/nodejs'
|
21
|
+
require_relative 'exporters/ocaml'
|
22
|
+
require_relative 'exporters/php'
|
23
|
+
require_relative 'exporters/python'
|
24
|
+
require_relative 'exporters/racket'
|
25
|
+
require_relative 'exporters/ruby'
|
26
|
+
require_relative 'exporters/rust'
|
27
|
+
require_relative 'exporters/yaml'
|
28
|
+
require_relative 'exporters/zig'
|