easy-swig 1.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 (45) hide show
  1. checksums.yaml +15 -0
  2. data/MIT_LICENSE +19 -0
  3. data/README.md +86 -0
  4. data/lib/apinodes/api_attribute.rb +6 -0
  5. data/lib/apinodes/api_class.rb +207 -0
  6. data/lib/apinodes/api_enum.rb +10 -0
  7. data/lib/apinodes/api_function.rb +13 -0
  8. data/lib/apinodes/api_group.rb +13 -0
  9. data/lib/apinodes/api_method.rb +12 -0
  10. data/lib/apinodes/api_namespace.rb +104 -0
  11. data/lib/apinodes/api_node.rb +109 -0
  12. data/lib/apinodes/api_variable.rb +12 -0
  13. data/lib/apinodes/inc_file.rb +118 -0
  14. data/lib/config.rb +56 -0
  15. data/lib/csharp/csharp_features.rb +100 -0
  16. data/lib/csharp/generators/csharp_class_generator.rb +14 -0
  17. data/lib/csharp/generators/csharp_generator.rb +17 -0
  18. data/lib/csharp/generators/csharp_namespace_generator.rb +33 -0
  19. data/lib/easy-swig-cli.rb +143 -0
  20. data/lib/easy-swig.rb +71 -0
  21. data/lib/features.rb +39 -0
  22. data/lib/generators/class_generator.rb +209 -0
  23. data/lib/generators/generator.rb +124 -0
  24. data/lib/generators/generator_util.rb +135 -0
  25. data/lib/generators/hfile_generator.rb +56 -0
  26. data/lib/generators/namespace_generator.rb +58 -0
  27. data/lib/generators/properties.rb +13 -0
  28. data/lib/readers/csv_parser.rb +127 -0
  29. data/lib/tasks/doxygen_task.rb +28 -0
  30. data/lib/tasks/generate_task.rb +85 -0
  31. data/lib/tasks/hfiles_manager.rb +86 -0
  32. data/lib/tasks/swig_task.rb +69 -0
  33. data/lib/util/logger.rb +36 -0
  34. data/lib/util/print.rb +72 -0
  35. data/lib/util/query.rb +136 -0
  36. data/lib/util/utilities.rb +101 -0
  37. data/spec/accessors/accessors_spec.rb +45 -0
  38. data/spec/anonymousEnums/anonymous_enums_spec.rb +49 -0
  39. data/spec/friends/friends_spec.rb +45 -0
  40. data/spec/innerclasses/innerclasses_spec.rb +48 -0
  41. data/spec/namespacePrefixes/namespace_prefixes_spec.rb +39 -0
  42. data/spec/squish/squish_spec.rb +40 -0
  43. data/spec/subdirectories/subdirectories_spec.rb +41 -0
  44. data/spec/templates/templates_spec.rb +34 -0
  45. metadata +107 -0
@@ -0,0 +1,28 @@
1
+ module EasySwig
2
+
3
+ class DoxygenTask
4
+ include EasySwig
5
+
6
+ def initialize config
7
+ @config = config
8
+ if Dir.exists?(@config.doxy_dir)
9
+ FileUtils.rm_r @config.doxy_dir
10
+ end
11
+ FileUtils.mkdir_p @config.doxy_dir
12
+
13
+ @log = EasySwig::Logger.doxy_log(@config.doxy_dir)
14
+ @log.info { "Created Doxygen directory in #{@config.doxy_dir}" }
15
+ end
16
+
17
+ def generate
18
+ @log.info { "Creating Doxygen documentation in directory: #{@config.doxy_dir} ..." }
19
+ headers_dirs = [@config.headers_dir]
20
+ output = Doxyparser::gen_xml_docs(headers_dirs, @config.doxy_dir, true, @config.includes_dir, @config.html, @config.stl_support)
21
+ @log.info { 'Doxygen documentation created at: '+ @config.doxy_dir}
22
+ end
23
+
24
+ def dispose
25
+ @log.close
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,85 @@
1
+ module EasySwig
2
+
3
+ class GenerateTask
4
+ include Util
5
+ include Print
6
+
7
+ def initialize(config)
8
+ @config = config
9
+ @gen_dir = @config.generate_dir+'/'+@config.lang
10
+ @generators = {}
11
+
12
+ FileUtils.rm_r @gen_dir if Dir.exists?(@gen_dir)
13
+ FileUtils.mkdir_p @gen_dir
14
+ FileUtils.cp "#{home_dir}/resources/#{@config.lang}/standard_header.i", "#{@gen_dir}/standard_header.i"
15
+ FileUtils.cp @config.custom_file, "#{@gen_dir}/custom_config.i" if @config.custom_file
16
+ FileUtils.cp "#{home_dir}/resources/swig-patches/attribute.i", "#{@gen_dir}/attribute.i"
17
+
18
+ @log=EasySwig::Logger.gen_log(@gen_dir)
19
+ @log.info { "Created Generator directory for #{@native_name} in #{@node_gen_dir}" }
20
+ end
21
+
22
+ def generate
23
+ module_file = <<-IFILE.escape_heredoc
24
+ %module #{@config.module_name}
25
+
26
+ %include "standard_header.i"
27
+ %include "#{@config.custom_file}"
28
+ %include "attribute.i"
29
+
30
+ IFILE
31
+
32
+ @log.info { "Parsing CSV file #{@csv_file} ..." }
33
+ csv_parser = EasySwig::Readers::CsvParser.new(@config.csv_file, @config.lang)
34
+
35
+ @log.info { "Parsed CSV file. Number of entries: #{csv_parser.table.size}" }
36
+
37
+ @log.info { "Generating Namespaces from CSV file #{@csv_file}..." }
38
+ api_namespaces = csv_parser.namespaces_from_csv
39
+ @log.info { "Namespaces generated in memory: \n\t\tNamespaces: " + (api_namespaces.map{|n| n.basename}.join(', ')) }
40
+ @log.debug { "Namespace contents:\n\t\t\t\t" + print_api_namespaces(api_namespaces) }
41
+
42
+ api_namespaces.each do |api_namespace|
43
+
44
+ @log.info { "Parsing namespace from C++ files..." }
45
+ namespace = Doxyparser::parse_namespace(api_namespace.basename, @config.doxy_dir+'/xml')
46
+ @log.info { "C++ Namespace parsed" }
47
+
48
+ @log.info { "Associating namespace tree (classes, structs, enums...) with the corresponding Doxygen tree..." }
49
+ api_namespace.assoc_with_node(namespace)
50
+ @log.info { "Namespace Tree associated" }
51
+ @log.debug { print_api_namespace(api_namespace) }
52
+
53
+ # If there are no classes in this namespace things get a bit tricky
54
+ conf_doxy_dir = @config.doxy_dir
55
+ if api_namespace.api_classes.empty?
56
+ api_namespace.define_singleton_method(:file) {
57
+ Doxyparser::parse_file(api_namespace.basename + '.h', conf_doxy_dir+"/xml")
58
+ }
59
+ end
60
+
61
+ @log.info { "Associating and sorting header files for all sub-nodes of: #{api_namespace.node_type} #{api_namespace.name}..." }
62
+ hfiles = EasySwig::HFilesManager.assoc_headers(api_namespace)
63
+ @log.info { "Header files associated" }
64
+ hfiles = EasySwig::HFilesManager.sort_headers(hfiles)
65
+ @log.info { "Header files sorted" }
66
+ @log.debug { print_incl_files(hfiles) }
67
+
68
+ native_name = api_namespace.basename # Name of module (C++ namespace)
69
+ target_name = api_namespace.target_name # Name of target namespace
70
+
71
+ @generators[api_namespace] = Generator.create_instance(hfiles, api_namespace, @config, @log)
72
+
73
+ @log.info { "Generating SWIG configuration files..." }
74
+ gen_node_name = @generators[api_namespace].generate
75
+ @log.info { "SWIG configuration files generated" }
76
+ module_file << %Q{%include "#{gen_node_name}.i"\n}
77
+ end
78
+ write_file(%Q{#{@gen_dir}/#{@config.module_name}.i}, module_file)
79
+ end
80
+
81
+ def dispose
82
+ @log.close
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,86 @@
1
+ module EasySwig
2
+ module HFilesManager
3
+
4
+ class << self
5
+
6
+ def assoc_headers(api_node)
7
+
8
+ api_nodes = api_node.api_nodes
9
+ hfile_hash = Hash.new
10
+
11
+ api_nodes.each { |api_n|
12
+ hfile = api_n.file
13
+
14
+ next if hfile.nil?
15
+
16
+ filename = hfile.name
17
+
18
+ if hfile_hash[filename] == nil
19
+ params = {'node_type' => 'file', 'features' => api_n.features }
20
+ hfile_hash[filename] = IncFile.new(params).assoc_with_node(hfile)
21
+ hfile_hash[filename].assoc_inner_node(api_node)
22
+ end
23
+ api_n.header_file = hfile_hash[filename]
24
+ hfile_hash[filename].assoc_inner_node(api_n)
25
+ }
26
+ hfile_hash.each_value { |f|
27
+ f.ignore_inner_nodes
28
+ puts f.name
29
+ }
30
+ hfiles = hfile_hash.values
31
+ hfiles
32
+ end
33
+
34
+ def sort_headers(files)
35
+ sorted = []
36
+ includes = {}
37
+ while true
38
+
39
+ break if files.empty?
40
+
41
+ files_aux = files.map { |f| f.name }
42
+ files = files.sort { |fx,fy| compare(fx, fy, files_aux) }
43
+ includes = {}
44
+ while true
45
+ f = files.shift
46
+ break if f.nil?
47
+
48
+ incs = f.files_included.map { |x| x.name }
49
+ t1 = files.select { |h| incs.include?(h.name) }
50
+ t2 = sorted.select { |h| incs.include?(h.name) }
51
+ t2.select! { |h| compare(h, f, files_aux) > 0}
52
+ includes[f] = t1
53
+ includes[f] << t2.flatten
54
+ t1.each { |v| files.delete(v) }
55
+ t2.each { |v| sorted.delete(v) }
56
+ files -= includes[f]
57
+ end
58
+ sorted.push(*includes.keys)
59
+ files = includes.values.flatten
60
+ end
61
+ sorted.reverse
62
+ end
63
+
64
+ def compare(fx, fy, files_set)
65
+ num_incl_by_fx = num_included_by(fx, files_set)
66
+ num_incl_by_fy = num_included_by(fy, files_set)
67
+ num_incl_fx = num_includes(fx, files_set)
68
+ num_incl_fy = num_includes(fy, files_set)
69
+ if num_incl_by_fx == 0 && num_incl_by_fy == 0
70
+ return num_incl_fy <=> num_incl_fx
71
+ end
72
+ return -1 if num_incl_by_fx == 0
73
+ return 1 if num_incl_by_fy == 0
74
+ return (num_incl_fy - num_incl_by_fy) <=> (num_incl_fx - num_incl_by_fx)
75
+ end
76
+
77
+ def num_includes(file, files_set)
78
+ file.files_included.map { |f| f.name }.select { |f| files_set.include?(f) }.size
79
+ end
80
+
81
+ def num_included_by(file, files_set)
82
+ file.files_including.map { |f| f.name }.select{ |f| files_set.include?(f) }.size
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,69 @@
1
+ module EasySwig
2
+ class SwigTask
3
+
4
+ def initialize(config)
5
+ @config = config
6
+ @target = @config.target_file
7
+ @header_dir = @config.headers_dir
8
+ @out_dir = @config.output_dir
9
+ @lang = @config.lang
10
+ @inc_dirs = @config.includes_dir
11
+
12
+ if Dir.exists? @out_dir
13
+ FileUtils.rm_r @out_dir
14
+ end
15
+ FileUtils.mkdir_p(@out_dir)
16
+
17
+ @log=EasySwig::Logger.swig_log @out_dir
18
+ @log.info { "Created SWIG output directory in #{@out_dir}" }
19
+ end
20
+
21
+ def run_swig
22
+
23
+ swig_opts=%Q{-c++ -#{@lang} -v -Wall -debug-classes} #
24
+ saved_dir = Dir.pwd
25
+ output_dir = @out_dir.clone
26
+ ifiles = []
27
+ current_dir = nil
28
+ if File.directory? @target
29
+ ifiles = Dir.entries(@target).select { |entry| File.extname(entry)=='.i' }
30
+ output_dir << "/#{File.basename(@target)}"
31
+ current_dir = @target
32
+ else
33
+ ifiles << File.basename(@target)
34
+ current_dir = ::File.dirname(@target)
35
+ end
36
+
37
+ ifiles.each { |f|
38
+ @log.info "\nExecuting SWIG command for file: " + f
39
+ target_name = File.basename(f, '.i')
40
+ odir = output_dir + "/#{target_name}"
41
+ FileUtils.mkdir_p(odir)
42
+ Dir.chdir current_dir
43
+ incs = ""
44
+ Dir.foreach(@header_dir) { |e|
45
+ subdir = File.absolute_path(e, @header_dir)
46
+ if File.directory?(subdir)
47
+ incs << "-I#{subdir} "
48
+ end
49
+ }
50
+ @inc_dirs.each { |idir|
51
+ incs << "-I#{idir} "
52
+ }
53
+ command=%Q{swig #{swig_opts} -namespace #{target_name} -outdir #{odir} #{incs} #{f} > swig_output 2>&1}
54
+ @log.info command
55
+ output=IO.popen(command)
56
+ @log.debug { 'SWIG output:' }
57
+ @log.debug { output.readlines.join }
58
+ output.close
59
+ FileUtils.mv("#{File.basename(f, '.i')}_wrap.cxx", output_dir)
60
+ }
61
+ Dir.chdir saved_dir
62
+ end
63
+
64
+ def dispose
65
+ @log.close
66
+ end
67
+
68
+ end
69
+ end
@@ -0,0 +1,36 @@
1
+ module EasySwig
2
+
3
+ class Logger
4
+ class << self
5
+
6
+ def doxy_log dir
7
+ file = File.open("#{dir}/doxygen.log", File::WRONLY | File::APPEND | File::CREAT)
8
+ @doxy_log ||= ::Logger.new(file)
9
+ end
10
+
11
+ def log msg
12
+ @gen_log.info msg
13
+ end
14
+
15
+ def gen_log dir=nil
16
+ @gen_log ||= ::Logger.new(File.open("#{dir}/generate.log", File::WRONLY | File::APPEND | File::CREAT))
17
+ end
18
+
19
+ def node_log dir=nil
20
+ @gen_log
21
+ end
22
+
23
+ def csv_log dir
24
+ @csv_log ||= ::Logger.new(File.open("#{dir}/generate.log", File::WRONLY | File::APPEND | File::CREAT))
25
+ end
26
+
27
+ def swig_log dir
28
+ @swig_log ||= ::Logger.new(File.open("#{dir}/swig.log", File::WRONLY | File::APPEND | File::CREAT))
29
+ end
30
+
31
+ def close_logs
32
+ end
33
+ end
34
+
35
+ end
36
+ end
data/lib/util/print.rb ADDED
@@ -0,0 +1,72 @@
1
+ module EasySwig
2
+ module Print
3
+
4
+ def print_incl_files incl_files
5
+ ret = ''
6
+ ret << "\nGenerated Include Files: "
7
+ incl_files.each { |f|
8
+ ret << "\nincl_file:" + f.basename
9
+ f.api_classes.each { |c|
10
+ ret << "\t"+"Api Class:" + c.basename
11
+ c.api_methods.each { |m|
12
+ ret << "\t"+"\t"+"Api Method:" + m.basename
13
+ }
14
+ c.ignored_methods.each { |m|
15
+ ret << "\t"+"\t"+"Ignored Method:" + m.basename
16
+ }
17
+ }
18
+ f.ignored_classes.each { |c|
19
+ ret << "\t"+"Ignored Class:" + c.basename
20
+ # c.methods.each { |m|
21
+ # log "\t"+"\t"+"Ignored Method:" + m.name
22
+ #}
23
+ }
24
+ }
25
+ ret
26
+ end
27
+
28
+ def print_api_namespaces api_namespaces
29
+ ret = ''
30
+ api_namespaces.each { |api_ns|
31
+ ret << print_api_namespace(api_ns)
32
+ }
33
+ ret
34
+ end
35
+
36
+ def print_api_namespace api_ns
37
+ ret = '\n'
38
+ ret << "\tNamespace: "+ api_ns+"\n"
39
+ api_ns.api_functions.each { |f|
40
+ ret << "\t\tFunction: "+f.basename+" Assoc: "+f.wrapped_node.to_s + '\n'
41
+ }
42
+ api_ns.api_variables.each { |f|
43
+ ret << "\t\tVariable: "+f.basename+" Assoc: "+f.wrapped_node.to_s + '\n'
44
+ }
45
+ api_ns.api_enums.each { |f|
46
+ ret << "\t\tEnum: "+f.basename+" Assoc: "+f.wrapped_node.to_s + '\n'
47
+ }
48
+ api_ns.api_classes.each { |c|
49
+ ret << print_api_class(c)
50
+ }
51
+ ret
52
+ end
53
+
54
+ def print_api_class c
55
+ ret = ''
56
+ ret << "\t\tClass: " + c + "\n"
57
+ c.api_methods.each { |m|
58
+ ret << "\t\t\tMethod: "+m.basename+" Assoc: "+m.wrapped_node.to_s + '\n'
59
+ }
60
+ c.api_attributes.each { |m|
61
+ ret << "\t\t\tAttribute : "+m.basename+" Assoc: "+m.wrapped_node.to_s + '\n'
62
+ }
63
+ c.api_enums.each { |m|
64
+ ret << "\t\t\tEnum: "+m.basename+" Assoc: "+m.wrapped_node.to_s + '\n'
65
+ }
66
+ c.api_innerclasses.each { |m|
67
+ ret << "\t\t\tInnerClass: "+m.basename+" Assoc: "+m.wrapped_node.to_s + '\n'
68
+ }
69
+ ret
70
+ end
71
+ end
72
+ end
data/lib/util/query.rb ADDED
@@ -0,0 +1,136 @@
1
+ module EasySwig
2
+ module Query
3
+
4
+ def get_reference_type(type, klass)
5
+ if is_primitive?(type.name)
6
+ return :ptr
7
+ elsif type.name.end_with?('&')
8
+ return :ref
9
+ elsif type.name == 'std::string'
10
+ return :str
11
+ else
12
+ looked_up = klass.lookup_node(type)
13
+ return nil if looked_up.nil?
14
+ if type.name.end_with?('*')
15
+ if is_primitive?(looked_up.name)
16
+ return :prim_ptr
17
+ end
18
+ return :ptr
19
+ end
20
+ if looked_up.class == Doxyparser::Enum
21
+ return :ptr
22
+ else
23
+ return :val
24
+ end
25
+ end
26
+ end
27
+
28
+ def lookup_typename(type, klass) # Type should not have * & const
29
+ escaped_typename = escape_template(type.escaped_name)
30
+ return type.escaped_name if escaped_typename.include?('::') || is_primitive?(escaped_typename)
31
+ return type.escaped_name.gsub(escaped_typename, 'std::' + escaped_typename) if is_std?(escaped_typename)
32
+ lookedup = klass.lookup_node(type)
33
+ return nil if lookedup.nil?
34
+ lookedup.name
35
+ end
36
+
37
+ def nested_typenames_for(typename)
38
+ Doxyparser::Type.nested_typenames(typename)
39
+ end
40
+
41
+ def no_public_constructors?(klass)
42
+ klass.constructors(:public).empty? && !klass.constructors(:all).empty?
43
+ end
44
+
45
+ def no_public_destructors?(klass)
46
+ klass.destructors(:public).empty? && !klass.destructors(:all).empty?
47
+ end
48
+
49
+ def name_for_template(template_name)
50
+ nested_typenames_for(template_name).map{ |typename|
51
+ del_prefix_class(typename.split(' ').join('')).capitalize
52
+ }.join('')
53
+ end
54
+
55
+ def name_for_operator(typename, num_args)
56
+ ret = case typename
57
+ when 'operator+'
58
+ return num_args == 2 ? '__add__' : '__pos__'
59
+ when 'operator-'
60
+ return num_args == 2 ? '__sub__' : '__neg__'
61
+ when 'operator*'
62
+ return '__mul__'
63
+ when 'operator/'
64
+ return '__div__'
65
+ when 'operator%'
66
+ return '__mod__'
67
+ when 'operator<<'
68
+ return '__lshift__'
69
+ when 'operator>>'
70
+ return '__rshift__'
71
+ when 'operator&'
72
+ return '__and__'
73
+ when 'operator||'
74
+ return '__or__'
75
+ when 'operator^'
76
+ return '__xor__'
77
+ when 'operator~'
78
+ return '__invert__'
79
+ when 'operator<'
80
+ return '__lt__'
81
+ when 'operator<='
82
+ return '__le__'
83
+ when 'operator>'
84
+ return '__gt__'
85
+ when 'operator>='
86
+ return '__ge__'
87
+ when 'operator=='
88
+ return '__eq__'
89
+ when 'operator()'
90
+ return '__call__'
91
+ else
92
+ return nil
93
+ end
94
+ end
95
+
96
+ def name_for_friend(typename, num_args = 0)
97
+ return '_friend_' + typename if num_args == 0
98
+ result = name_for_operator(typename, num_args)
99
+ return result.nil? ? '_friend_' + typename : result
100
+ end
101
+
102
+ def is_template_param?(typename, klass)
103
+ class_template_params = klass.template_params.map{|tp| tp.declname}
104
+ return if nested_typenames_for(typename).any?{ |t|
105
+ class_template_params.include?(t)
106
+ }
107
+ end
108
+
109
+ def is_template?(typename)
110
+ Doxyparser::Type.template?(typename)
111
+ end
112
+
113
+ def is_operator?(name)
114
+ name =~ /operator\W+/
115
+ end
116
+
117
+ def method_has_blacklisted_types?(method)
118
+ return true if type_is_blacklisted?(method)
119
+ return true if method.params.any?{ |p| type_is_blacklisted?(p) }
120
+ return false
121
+ end
122
+
123
+ def type_is_blacklisted?(p)
124
+ return p.type.nested_typenames.any?{ |t| typename_is_blacklisted?(t)}
125
+ end
126
+
127
+ def typename_is_blacklisted?(typename)
128
+ return typename == 'multimap' || typename == 'std::multimap'
129
+ end
130
+
131
+ def anonymous_enum?(enum)
132
+ enum.basename =~ /_Enum\d*$/
133
+ end
134
+
135
+ end
136
+ end