swift-tools 3.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c28f4aeceef967541bfc5e16a19d2bcde80b73bf
4
+ data.tar.gz: f33bd65d76081c16bb726a49d48e1392a6a32229
5
+ SHA512:
6
+ metadata.gz: 155ed59fd5357ec9c6fe1354c0e9f238a5026066666b5208d97115a4315446b2f70493a760f2816aa35558c5e6fe02a93f7b116c4d0492fb6ff5a7b6f7d1efa0
7
+ data.tar.gz: 359367669c2c4bbd84373f6fd10e74f82b682ca4f6797d6d77cca12caa799ac01f76b9e1576edc40119b166df2c4cafefd5486260a55eb096fd8c7bd942b31fd
data/bin/csharp2swift ADDED
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'bundler/setup'
5
+ require 'methadone'
6
+ require 'swift_tools'
7
+
8
+ module SwiftTools
9
+ class Tool
10
+ include Methadone::Main
11
+ include Methadone::CLILogging
12
+ include Methadone::ExitNow
13
+
14
+ main do |cs_filename|
15
+ unless File.exist?(cs_filename)
16
+ exit_now! 127, "File #{cs_filename} does not exist"
17
+ end
18
+
19
+ cs_file = File.new(cs_filename, "r")
20
+ swift_filename = options[:swift_filename]
21
+
22
+ if swift_filename.nil?
23
+ swift_file = STDOUT
24
+ else
25
+ swift_file = File.new(swift_filename, 'w+')
26
+ end
27
+
28
+ tool = Csharp2Swift.new
29
+ tool.execute(cs_file, swift_file, options)
30
+
31
+ info "\"#{cs_filename}\" -> \"#{swift_filename}\""
32
+
33
+ tool.renamed_vars.each {|k,v| puts k + ' -> ' + v}
34
+ tool.renamed_methods.each {|k,v| puts k + '() -> ' + v + '()'}
35
+ end
36
+
37
+ version SwiftTools::VERSION
38
+ description %Q(This tool does a rough conversion of C# source code to Swift. The goal of the tool
39
+ is to do most of the easy stuff that simply requires a lot of typing effort, and allow you
40
+ to concentrate on the more difficult aspects of the conversion, such as library and
41
+ framework usage.
42
+ )
43
+ arg :cs_filename, :required
44
+ on("-o", "--output-file SWIFT_FILENAME", "The Swift output file.")
45
+
46
+ go!
47
+ end
48
+ end
data/bin/objc2swift ADDED
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ require 'methadone'
6
+ require 'objc_2_swift'
7
+ require 'swift_tools'
8
+
9
+ module SwiftTools
10
+ class Tool
11
+ include Methadone::Main
12
+ include Methadone::CLILogging
13
+
14
+ main do |h_filename, m_filename|
15
+ unless File.extname(h_filename) == '.h'
16
+ exit_now! 127, "Header file must end in .h"
17
+ end
18
+
19
+ unless File.exist?(h_filename)
20
+ exit_now! 127, "File '#{h_filename}' does not exist"
21
+ end
22
+
23
+ unless m_filename.nil?
24
+ unless File.extname(m_filename) == '.m'
25
+ exit_now! 127, "Implementation file must end in .m"
26
+ end
27
+
28
+ unless File.exists?(m_filename)
29
+ exit_now! 127, "File '#{m_filename}' does not exist"
30
+ end
31
+ end
32
+
33
+ h_file = File.new(h_filename, "r")
34
+ m_file = m_filename.nil? ? nil : File.new(m_filename, "r")
35
+ swift_filename = options[:o]
36
+
37
+ if swift_filename.nil?
38
+ swift_file = STDOUT
39
+ swift_filename = "STDOUT"
40
+ else
41
+ swift_file = File.new(swift_filename, 'w+')
42
+ end
43
+
44
+ tool = ObjC2Swift.new
45
+ tool.execute(h_file, m_file, swift_file, options)
46
+
47
+ info "\"#{h_filename}\"" + (m_filename.nil? ? '' : " + \"#{m_filename}\"") + " -> \"#{swift_filename}\""
48
+ end
49
+
50
+ version SwiftTools::VERSION
51
+ description %Q(This tool does a rough conversion of Objective-C source code to Swift. The goal of the tool
52
+ is to do most of the stuff that simply requires a lot of typing effort, and allow you
53
+ to concentrate on the more difficult aspects of the conversion, such as library and
54
+ framework usage, use of let vs. var, etc.
55
+ )
56
+ arg :h_file, :required
57
+ arg :m_file
58
+ on("-o", "--output-file SWIFT_FILENAME", "The Swift output file.")
59
+
60
+ go!
61
+ end
62
+ end
data/lib/core_ext.rb ADDED
@@ -0,0 +1,40 @@
1
+ class String
2
+ def camelcase(*separators)
3
+ case separators.first
4
+ when Symbol, TrueClass, FalseClass, NilClass
5
+ first_letter = separators.shift
6
+ end
7
+
8
+ separators = ['_'] if separators.empty?
9
+
10
+ str = self.dup
11
+
12
+ separators.each do |s|
13
+ str = str.gsub(/(?:#{s}+)([a-z])/){ $1.upcase }
14
+ end
15
+
16
+ case first_letter
17
+ when :upper, true
18
+ str = str.gsub(/(\A|\s)([a-z])/){ $1 + $2.upcase }
19
+ when :lower, false
20
+ str = str.gsub(/(\A|\s)([A-Z])/){ $1 + $2.downcase }
21
+ end
22
+
23
+ str
24
+ end
25
+
26
+ def upper_camelcase(*separators)
27
+ camelcase(:upper, *separators)
28
+ end
29
+
30
+ def lower_camelcase(*separators)
31
+ camelcase(:lower, *separators)
32
+ end
33
+
34
+ end
35
+
36
+ class Range
37
+ def self.make(a, exclude_end)
38
+ return Range.new(a[0], a[1], exclude_end)
39
+ end
40
+ end
@@ -0,0 +1,276 @@
1
+ require_relative './core_ext.rb'
2
+
3
+ module SwiftTools
4
+ class Csharp2Swift
5
+
6
+ attr_reader :renamed_vars
7
+ attr_reader :renamed_methods
8
+
9
+ def initialize
10
+ @renamed_methods = {}
11
+ @renamed_vars = {}
12
+ end
13
+
14
+ def execute(cs_file, swift_file, options)
15
+ content = cs_file.read()
16
+
17
+ # Things that clean up the code and make other regex's easier
18
+ remove_eol_semicolons(content)
19
+ join_open_brace_to_last_line(content)
20
+ remove_region(content)
21
+ remove_endregion(content)
22
+ remove_namespace_using(content)
23
+ convert_this_to_self(content)
24
+ convert_int_type(content)
25
+ convert_string_type(content)
26
+ convert_bool_type(content)
27
+ convert_float_type(content)
28
+ convert_double_type(content)
29
+ convert_list_list_type(content)
30
+ convert_list_array_type(content)
31
+ convert_list_type(content)
32
+ convert_debug_assert(content)
33
+ remove_new(content)
34
+ insert_import(content)
35
+
36
+ # Slightly more complicated stuff
37
+ remove_namespace(content)
38
+ convert_property(content)
39
+ remove_get_set(content)
40
+ convert_const_field(content)
41
+ convert_field(content)
42
+ constructors_to_inits(content)
43
+ convert_method_decl_to_func_decl(content)
44
+ convert_locals(content)
45
+ convert_if(content)
46
+ convert_next_line_else(content)
47
+ convert_simple_range_for_loop(content)
48
+
49
+ # Global search/replace
50
+ @renamed_vars.each { |v, nv|
51
+ content.gsub!(Regexp.new("\\." + v + "\\b"), '.' + nv)
52
+ }
53
+ @renamed_methods.each { |m, nm|
54
+ content.gsub!(Regexp.new('\\b' + m + '\\('), nm + '(')
55
+ }
56
+
57
+ swift_file.write(content)
58
+ end
59
+
60
+ def remove_eol_semicolons(content)
61
+ content.gsub!(/; *$/m, '')
62
+ end
63
+
64
+ def join_open_brace_to_last_line(content)
65
+ re = / *\{$/m
66
+ m = re.match(content)
67
+ s = ' {'
68
+
69
+ while m != nil do
70
+ offset = m.offset(0)
71
+ start = offset[0]
72
+ content.slice!(offset[0]..offset[1])
73
+ content.insert(start - 1, s)
74
+ m = re.match(content, start - 1 + s.length)
75
+ end
76
+ end
77
+
78
+ def convert_this_to_self(content)
79
+ content.gsub!(/this\./, 'self.')
80
+ end
81
+
82
+ def remove_region(content)
83
+ content.gsub!(/ *#region.*\n/, '')
84
+ end
85
+
86
+ def remove_endregion(content)
87
+ content.gsub!(/ *#endregion.*\n/, '')
88
+ end
89
+
90
+ def remove_namespace_using(content)
91
+ content.gsub!(/ *using (?!\().*\n/, '')
92
+ end
93
+
94
+ def convert_int_type(content)
95
+ content.gsub!(/\bint\b/, 'Int')
96
+ end
97
+
98
+ def convert_string_type(content)
99
+ content.gsub!(/\bstring\b/, 'Int')
100
+ end
101
+
102
+ def convert_bool_type(content)
103
+ content.gsub!(/\bbool\b/, 'Bool')
104
+ end
105
+
106
+ def convert_float_type(content)
107
+ content.gsub!(/\bfloat\b/, 'Float')
108
+ end
109
+
110
+ def convert_double_type(content)
111
+ content.gsub!(/\bdouble\b/, 'Double')
112
+ end
113
+
114
+ def convert_list_type(content)
115
+ content.gsub!(/(?:List|IList)<(\w+)>/, '[\\1]')
116
+ end
117
+
118
+ def convert_list_list_type(content)
119
+ content.gsub!(/(?:List|IList)<(?:List|IList)<(\w+)>>/, '[[\\1]]')
120
+ end
121
+
122
+ def convert_list_array_type(content)
123
+ content.gsub!(/(?:List|IList)<(\w+)>\[\]/, '[[\\1]]')
124
+ end
125
+
126
+ def convert_debug_assert(content)
127
+ content.gsub!(/Debug\.Assert\(/, 'assert(')
128
+ end
129
+
130
+ def remove_new(content)
131
+ content.gsub!(/new /, '')
132
+ end
133
+
134
+ def insert_import(content)
135
+ content.insert(0, "import Foundation\n")
136
+ end
137
+
138
+ def remove_namespace(content)
139
+ re = / *namespace +.+ *\{$/
140
+ m = re.match(content)
141
+
142
+ if m == nil
143
+ return
144
+ end
145
+
146
+ i = m.end(0)
147
+ n = 1
148
+ bol = (content[i] == "\n")
149
+ while i < content.length do
150
+ c = content[i]
151
+
152
+ if bol and c == " " and i + 3 < content.length
153
+ content.slice!(i..(i + 3))
154
+ c = content[i]
155
+ end
156
+
157
+ case c
158
+ when "{"
159
+ n += 1
160
+ when "}"
161
+ n -= 1
162
+ if n == 0
163
+ content.slice!(i) # Take out the end curly
164
+ content.slice!(m.begin(0)..m.end(0)) # Take out the original namespace
165
+ break
166
+ end
167
+ when "\n"
168
+ bol = true
169
+ else
170
+ bol = false
171
+ end
172
+ i += 1
173
+ end
174
+ end
175
+
176
+ def convert_const_field(content)
177
+ content.gsub!(/(^ *)(?:public|private|internal) +const +(.+?) +(.+?)( *= *.*?|)$/) { |m|
178
+ v = $3
179
+ nv = v.lower_camelcase
180
+ @renamed_vars[v] = nv
181
+ $1 + 'let ' + nv + ': ' + $2 + $4
182
+ }
183
+ end
184
+
185
+ def convert_field(content)
186
+ content.gsub!(/(^ *)(?:public|private|internal) +(\w+) +(\w+)( *= *.*?|)$/) { |m|
187
+ $1 + 'private var ' + $3 + ': ' + $2 + $4
188
+ }
189
+ end
190
+
191
+ def convert_property(content)
192
+ content.gsub!(/(^ *)(?:public|private|internal) +(?!class)([A-Za-z0-9_\[\]<>]+) +(\w+)(?: *\{)/) { |m|
193
+ v = $3
194
+ nv = v.lower_camelcase
195
+ @renamed_vars[v] = nv
196
+ $1 + 'var ' + nv + ': ' + $2 + ' {'
197
+ }
198
+ end
199
+
200
+ def remove_get_set(content)
201
+ content.gsub!(/{ *get; *set; *}$/, '')
202
+ end
203
+
204
+ def constructors_to_inits(content)
205
+ re = /(?:(?:public|internal|private) +|)class +(\w+)/
206
+ m = re.match(content)
207
+ while m != nil do
208
+ content.gsub!(Regexp.new('(?:(?:public|internal) +|)' + m.captures[0] + " *\\("), 'init(')
209
+ m = re.match(content, m.end(0))
210
+ end
211
+
212
+ content.gsub!(/init\((.*)\)/) { |m|
213
+ 'init(' + swap_args($1) + ')'
214
+ }
215
+ end
216
+
217
+ def convert_method_decl_to_func_decl(content)
218
+ # TODO: Override should be captured and re-inserted
219
+ content.gsub!(/(?:(?:public|internal|private) +)(?:override +|)(.+) +(.*)\((.*)\) *\{/) { |m|
220
+ f = $2
221
+ nf = f.lower_camelcase
222
+ @renamed_methods[f] = nf
223
+ if $1 == "void"
224
+ 'func ' + nf + '(' + swap_args($3) + ') {'
225
+ else
226
+ 'func ' + nf + '(' + swap_args($3) + ') -> ' + $1 + ' {'
227
+ end
228
+ }
229
+ end
230
+
231
+ def convert_locals(content)
232
+ content.gsub!(/^( *)(?!return|import)([A-Za-z0-9_\[\]<>]+) +(\w+)(?:( *= *.+)|)$/, '\\1let \\3\\4')
233
+ end
234
+
235
+ def convert_if(content)
236
+ content.gsub!(/if *\((.*)\) +\{/, 'if \\1 {')
237
+ content.gsub!(/if *\((.*?)\)\n( +)(.*?)\n/m) { |m|
238
+ s = $2.length > 4 ? $2[0...-4] : s
239
+ 'if ' + $1 + " {\n" + $2 + $3 + "\n" + s + "}\n"
240
+ }
241
+ end
242
+
243
+ def convert_next_line_else(content)
244
+ content.gsub!(/\}\n +else \{/m, '} else {')
245
+ end
246
+
247
+ def convert_simple_range_for_loop(content)
248
+ content.gsub!(/for \(.+ +(\w+) = (.+); \1 < (.*); \1\+\+\)/, 'for \\1 in \\2..<\\3')
249
+ content.gsub!(/for \(.+ +(\w+) = (.+); \1 >= (.*); \1\-\-\)/, 'for \\1 in (\\3...\\2).reverse()')
250
+ end
251
+
252
+ def swap_args(arg_string)
253
+ args = arg_string.split(/, */)
254
+ args.collect! { |arg|
255
+ a = arg.split(' ')
256
+ a[1] + ': ' + a[0]
257
+ }
258
+ args.join(', ')
259
+ end
260
+
261
+ def read_file(filename)
262
+ content = nil
263
+ File.open(filename, 'rb') { |f| content = f.read() }
264
+ content
265
+ end
266
+
267
+ def write_file(filename, content)
268
+ File.open(filename, 'w') { |f| f.write(content) }
269
+ end
270
+
271
+ def error(msg)
272
+ STDERR.puts "error: #{msg}".red
273
+ end
274
+
275
+ end
276
+ end
@@ -0,0 +1,519 @@
1
+ module SwiftTools
2
+ class ObjC2Swift
3
+
4
+ INDENT = ' '
5
+
6
+ def initialize
7
+ end
8
+
9
+ def execute(h_file, m_file, swift_file, options)
10
+ hdr_content = h_file.read()
11
+ h_filename = File.basename(h_file.to_path)
12
+
13
+ imports = capture_imports(h_filename, hdr_content)
14
+ statics = capture_statics(hdr_content)
15
+ interfaces = capture_interfaces(hdr_content, in_hdr = true)
16
+ copyright = capture_copyright(hdr_content)
17
+
18
+ unless m_file.nil?
19
+ impl_content = m_file.read()
20
+ implementations = capture_implementation(impl_content)
21
+ impl_imports = capture_imports(h_filename, impl_content)
22
+ impl_interfaces = capture_interfaces(impl_content, in_hdr = false)
23
+ end
24
+
25
+ # Add copyright
26
+ unless copyright.nil?
27
+ swift_file.write("//\n#{copyright}\n//\n\n")
28
+ end
29
+
30
+ # Combine all the imports
31
+ swift_file.write(imports[:at_imports])
32
+ swift_file.write(impl_imports[:at_imports]) unless impl_imports.nil?
33
+ swift_file.write(imports[:hash_imports])
34
+ swift_file.write(impl_imports[:hash_imports]) unless impl_imports.nil?
35
+ swift_file.write("\n")
36
+
37
+ # Write statics
38
+ swift_file.write(statics)
39
+ swift_file.write("\n")
40
+
41
+ # Combine interfaces
42
+ unless impl_interfaces.nil?
43
+ impl_interfaces.each { |name, data|
44
+ hdr_has_interface = interfaces.include?(name)
45
+ interfaces[name][:properties].merge!(data[:properties]) if hdr_has_interface
46
+ interfaces[name][:methods].merge!(data[:methods]) { |key, v1, v2|
47
+ {
48
+ :scope => v2[:scope],
49
+ :return_type => v2[:return_type],
50
+ :visibility => v1[:visibility]
51
+ }
52
+ } if hdr_has_interface
53
+ }
54
+ end
55
+
56
+ # Combine implementations
57
+ unless implementations.nil?
58
+ implementations.each { |name, data|
59
+ have_interface = interfaces.include?(name)
60
+ interfaces[name][:methods].merge!(data[:methods]) { |key, v1, v2|
61
+ {
62
+ :scope => v1[:scope] || v2[:scope],
63
+ :return_type => v1[:return_type],
64
+ :visibility => v1[:visibility],
65
+ :body => v2[:body]
66
+ }
67
+ } if have_interface
68
+ }
69
+ end
70
+
71
+ # Output classes or extensions from interfaces
72
+ interfaces.each {|interface_name, interface_data|
73
+ swift_file.write("@objc class #{interface_name}")
74
+ if interface_data[:base] != nil
75
+ swift_file.write(": #{interface_data[:base]}")
76
+ end
77
+ if interface_data[:protocols] != nil
78
+ swift_file.write(", #{interface_data[:protocols]}")
79
+ end
80
+ swift_file.write("{\n")
81
+
82
+ # Create Swift singletons
83
+ if interface_data[:methods].include?('sharedInstance')
84
+ interface_data[:methods].delete('sharedInstance')
85
+ interface_data[:properties]['sharedInstance'] = {
86
+ :const => true,
87
+ :visibility => :public,
88
+ :scope => :static,
89
+ :initializer => "#{interface_name}()"
90
+ }
91
+ interface_data[:inits].push({
92
+ body: '',
93
+ :visibility => :private
94
+ })
95
+ end
96
+
97
+ # Add getter props
98
+ new_properties = {}
99
+ interface_data[:properties].each {|prop_name, prop_data|
100
+ if prop_data[:getter]
101
+ new_properties[prop_data[:getter]] = {
102
+ :visibility => :public,
103
+ :type => 'Bool',
104
+ :scope => prop_data[:scope],
105
+ :body => "return #{prop_name}"
106
+ }
107
+
108
+ # Remove :readonly if set and make private
109
+ prop_data.delete(:readonly)
110
+ prop_data[:visibility] = :private
111
+ end
112
+ }
113
+ interface_data[:properties].merge!(new_properties)
114
+
115
+ # line2 = INDENT + "public var #{prop_data[:getter]}: #{prop_data[:type]} { return #{prop_name} }\n"
116
+ # if prop_data[:getter]
117
+ # line1 = INDENT + "private "
118
+
119
+ # Write properties
120
+ interface_data[:properties].each {|prop_name, prop_data|
121
+ line = INDENT
122
+ if prop_data[:readonly]
123
+ line += "private(set) public "
124
+ elsif prop_data[:visibility] == :private
125
+ line += 'private '
126
+ end
127
+
128
+ if prop_data[:scope] == :static
129
+ line += "static "
130
+ end
131
+
132
+ if prop_data[:weak]
133
+ line += 'weak '
134
+ end
135
+
136
+ if prop_data[:const]
137
+ line += 'let '
138
+ else
139
+ line += 'var '
140
+ end
141
+
142
+ line += "#{prop_name}"
143
+
144
+ if prop_data[:type]
145
+ line += ": #{prop_data[:type]}"
146
+ end
147
+
148
+ if prop_data[:initializer]
149
+ line += " = #{prop_data[:initializer]}"
150
+ end
151
+
152
+ if prop_data[:body]
153
+ line += " {\n" + INDENT + INDENT + prop_data[:body] + "\n" + INDENT + "}"
154
+ end
155
+
156
+ line += "\n"
157
+
158
+ swift_file.write(line)
159
+ }
160
+
161
+ # Write inits
162
+ interface_data[:inits].each {|init_data|
163
+ line = "\n" + INDENT
164
+
165
+ if init_data[:visibility] == :private
166
+ line += 'private '
167
+ end
168
+
169
+ line += "init() {"
170
+
171
+ if init_data[:body].length > 0
172
+ line += "\n" + body + "\n"
173
+ end
174
+
175
+ line += "}\n"
176
+ swift_file.write(line)
177
+ }
178
+
179
+ # Write methods
180
+ interface_data[:methods].each {|method_name, method_data|
181
+ line = "\n" + INDENT
182
+
183
+ if method_data[:visibility] == :private
184
+ line += 'private '
185
+ end
186
+
187
+ line += "func #{method_name}()"
188
+ if method_data[:return_type] and method_data[:return_type] != 'Void'
189
+ line += " -> #{method_data[:return_type]}"
190
+ end
191
+
192
+ line += ' {'
193
+
194
+ if method_data[:body].strip.length == 0
195
+ line += "}\n"
196
+ else
197
+ body = method_data[:body]
198
+ convert_var_decls(body)
199
+ remove_eol_semicolons(body)
200
+ convert_to_dot_syntax(body)
201
+ convert_underscore_props(body, interface_data[:properties])
202
+ convert_block_to_closure(body)
203
+ fix_if_statements(body)
204
+ fix_switch_statements(body)
205
+ fix_yes_no(body)
206
+ fix_null(body)
207
+ fix_at_string(body)
208
+ fix_broken_else(body)
209
+ line += body + INDENT + "}\n"
210
+ end
211
+
212
+ swift_file.write(line)
213
+ }
214
+
215
+ swift_file.write("}\n")
216
+ }
217
+ end
218
+
219
+ def capture_copyright(content)
220
+ copyright = nil
221
+ content.match(/^\s*\/\/.*Copyright.*$/) {|m|
222
+ copyright = m[0]
223
+ }
224
+ copyright
225
+ end
226
+
227
+ def capture_imports(h_filename, content)
228
+ r1 = ''
229
+ r2 = ''
230
+ content.scan(/[@#]import\s*"?[\.a-zA-Z0-9_]+"?\s*;?\s*\n?/m).each {|m|
231
+ s = m.strip
232
+ if s.start_with?("#")
233
+ if s.index(h_filename).nil?
234
+ r2 += "// TODO: Add '#{s}' to bridging header\n"
235
+ end
236
+ else
237
+ r1 += remove_eol_semicolons(s) + "\n"
238
+ end
239
+ }
240
+ {:at_imports => r1, :hash_imports => r2}
241
+ end
242
+
243
+ def capture_interfaces(content, in_hdr)
244
+ interfaces = {}
245
+ content.scan(/^\s*@interface\s*([a-zA-Z0-9_]+)(?:\s*:\s*([a-zA-Z0-9_]+)\s*)?(?:\s*\(([a-zA-Z0-9_, ]*)\))?(?:\s*<(.+)>)?((?:.|\n)*?)@end *\n$/m).each {|m|
246
+ body = m[4]
247
+ properties = extract_properties(body, in_hdr)
248
+ methods = extract_methods(body, in_hdr)
249
+
250
+ interfaces[m[0]] = {
251
+ :base => m[1],
252
+ :categories => m[2] == '' ? nil : m[2],
253
+ :protocols => m[3],
254
+ :properties => properties,
255
+ :methods => methods,
256
+ :inits => []
257
+ }
258
+ }
259
+ interfaces
260
+ end
261
+
262
+ def extract_properties(content, in_hdr)
263
+ properties = {}
264
+ content.scan(/^\s*@property\s*\(([a-zA-Z0-9_=, ]*)\)\s*([a-zA-Z0-9_\*]+)\s*([a-zA-Z0-9_\*]+)\s*/m) {|m|
265
+ name = $3
266
+ type = map_type(remove_ptr($2))
267
+ options = Hash[$1.split(',').map(&:strip).collect {|s|
268
+ a = s.split('=')
269
+ if a.length > 1
270
+ [a[0].to_sym, a[1]]
271
+ else
272
+ [a[0].to_sym, true]
273
+ end
274
+ }]
275
+ properties[remove_ptr(name)] = {
276
+ :type => type,
277
+ :visibility => in_hdr ? :public : :private,
278
+ :scope => :instance
279
+ }.merge(options)
280
+ }
281
+ properties
282
+ end
283
+
284
+ def extract_methods(content, in_hdr)
285
+ methods = {}
286
+ content.to_enum(:scan, /^ *(\+|-)? *\(([a-zA-Z0-9_]+)\) *([a-zA-Z0-9_]+)(?:[ \n]*\{)?/m).map { Regexp.last_match }.each {|m|
287
+ if in_hdr
288
+ body = ''
289
+ else
290
+ body_start_offset = m.offset(0)[1]
291
+ body_end_offset = find_close_char_offset(content, body_start_offset, '{', '}') - 1
292
+ body = indent_lines(content[body_start_offset..body_end_offset])
293
+ end
294
+ methods[m[3]] = {
295
+ :scope => (m[1] == '+' ? :static : :instance),
296
+ :return_type => map_type(remove_ptr(m[2])),
297
+ :visibility => in_hdr ? :public : :private,
298
+ :body => body
299
+ }
300
+ }
301
+ methods
302
+ end
303
+
304
+ def capture_implementation(content)
305
+ implementations = {}
306
+ content.scan(/^\s*@implementation *([a-zA-Z0-9_]+)((?:.|\n)*?)@end *\n$/m).each {|m|
307
+ body = m[1]
308
+ methods = extract_methods(body, in_hdr = false)
309
+
310
+ implementations[m[0]] = {
311
+ :methods => methods,
312
+ :inits => []
313
+ }
314
+ }
315
+ implementations
316
+ end
317
+
318
+ def capture_statics(content)
319
+ statics = ''
320
+ content.scan(/static *(.*?);/m) {|m|
321
+ decl = m[0]
322
+ decl.gsub!('const', '')
323
+ decl.gsub!('*', '')
324
+ decl.gsub!('@"', '"')
325
+ decl.scan(/([a-zA-Z0-9_\*]+) +([a-zA-Z0-9_\*]+)(?: *= *(.*))?/m) {|mm|
326
+ statics += "@objc let #{mm[1]}: #{map_type(mm[0])}"
327
+ if mm[2]
328
+ statics += " = #{mm[2]}"
329
+ end
330
+ statics += "\n"
331
+ }
332
+ }
333
+ statics
334
+ end
335
+
336
+ def convert_to_dot_syntax(content)
337
+ # Repeatedly find a [] syntax method call that does not have a nested [] call
338
+ # and convert it until done.
339
+ while m = content.match(/\[([^\[\]]+?)\]/) do
340
+ # Parse m[0], convert to . syntax and substitute into 'content'
341
+ body = m[1].strip
342
+ params = []
343
+
344
+ # Get the object name
345
+ obj_match = body.match(/^([a-zA-Z0-9_\.\(\)]+)/)
346
+ obj_name = obj_match[1]
347
+
348
+ # Search for label with empty parameter list
349
+ label_match = body.match(/(?:\n| )([a-zA-Z0-9_]+)/, obj_match.offset(0)[1])
350
+
351
+ if label_match and label_match.offset(0)[1] >= body.length
352
+ # There are no parameters
353
+ params.push({:name => label_match[1], :value => nil})
354
+ else
355
+ # There are labelled parameters, start parsing them by searching for the labels
356
+ param_label_re = /(?:\n| )+([a-zA-Z0-9_]+) *:/
357
+ label_match = body.match(param_label_re, obj_match.offset(0)[1])
358
+
359
+ begin
360
+ arg = { :name => label_match[1]}
361
+
362
+ if block_match = body.match(/ *(?:\(.*?\))?(?: |\n)\{/, label_match.offset(0)[1])
363
+ end_of_param = find_close_char_offset(body, block_match.offset(0)[1], '{', '}')
364
+ else
365
+ # Find the next arg label or the end of the string
366
+ next_label_match = body.match(param_label_re, label_match.offset(0)[1])
367
+ if next_label_match.nil?
368
+ end_of_param = body.length
369
+ else
370
+ end_of_param = next_label_match.offset(0)[0]
371
+ end
372
+ end
373
+ arg[:value] = body[label_match.offset(0)[1]...end_of_param]
374
+ params.push(arg)
375
+ label_match = next_label_match
376
+ end until label_match.nil?
377
+ end
378
+
379
+ new_call = "#{obj_name}."
380
+ params.each_index {|i|
381
+ if i == 0
382
+ new_call += params[i][:name] + '(' + (params[i][:value] || '')
383
+ else
384
+ new_call += ", #{params[i][:name]}: #{params[i][:value]}"
385
+ end
386
+ }
387
+ new_call += ')'
388
+
389
+ # Replace the orginal method call
390
+ content[Range.make(m.offset(0), true)] = new_call
391
+ end
392
+ end
393
+
394
+ def convert_var_decls(content)
395
+ # Note, this must be done _before_ removing EOL semicolons
396
+ content.gsub!(/^( *)([a-zA-Z0-9_\*]+) +([a-zA-Z0-9_\*]+)(?: *= *(.*?))? *;/m) {|m|
397
+ decl = "#{$1}var #{$3}: #{map_type(remove_ptr($2))}"
398
+ if $4
399
+ decl += " = #{$4}"
400
+ end
401
+ decl
402
+ }
403
+ end
404
+
405
+ def convert_underscore_props(content, properties)
406
+ properties.each {|prop_name, prop_data|
407
+ content.gsub!(Regexp.new('\b_' + prop_name + '\b'), "self.#{prop_name}")
408
+ }
409
+ end
410
+
411
+ def convert_block_to_closure(content)
412
+ content.gsub!(/\^(?:\(([a-zA-Z0-9_,\* ]+)\))?(?: |\n)*\{/m) {|m|
413
+ if $1
414
+ '{ ' + convert_param_list($1) + ' in '
415
+ else
416
+ '{\n'
417
+ end
418
+ }
419
+ end
420
+
421
+ def convert_param_list(content)
422
+ params = ''
423
+ content.split(/ *, */).each {|param|
424
+ pair = param.split(' ')
425
+ if params.length > 0
426
+ params += ', '
427
+ end
428
+ params += "#{remove_ptr(pair[1])}: #{map_type(remove_ptr(pair[0]))}"
429
+ }
430
+ '(' + params + ')'
431
+ end
432
+
433
+ def fix_if_statements(content)
434
+ content.gsub!(/if *\((.+?)\)(?: |\n)*{/m) {|m|
435
+ "if #{$1} {"
436
+ }
437
+ end
438
+
439
+ def fix_switch_statements(content)
440
+ content.gsub!(/switch *\((.+?)\)(?: |\n)*{/m) {|m|
441
+ "switch #{$1} {"
442
+ }
443
+ content.gsub!(/^ *break *\n/m) {|m|
444
+ ''
445
+ }
446
+ end
447
+
448
+ def fix_yes_no(content)
449
+ content.gsub!(/\b(YES|NO)\b/) {|m|
450
+ m == 'YES' ? 'true' : 'false'
451
+ }
452
+ end
453
+
454
+ def fix_null(content)
455
+ content.gsub!(/\bNULL\b/, 'nil')
456
+ end
457
+
458
+ def fix_broken_else(content)
459
+ content.gsub!(/\}(?: |\n)*else(?: |\n)*\{/, '} else {')
460
+ end
461
+
462
+ def fix_at_string(content)
463
+ content.gsub!(/@"/, '"')
464
+ end
465
+
466
+ def remove_eol_semicolons(content)
467
+ content.gsub!(/; *$/m, '')
468
+ end
469
+
470
+ def find_close_char_offset(content, offset, open_char, close_char)
471
+ count = 1
472
+ loop do
473
+ if offset >= content.length
474
+ raise "Ran out of content looking for #{close_char}"
475
+ elsif content[offset] == close_char
476
+ count -= 1
477
+ if count == 0
478
+ break
479
+ end
480
+ elsif content[offset] == open_char
481
+ count += 1
482
+ end
483
+ offset += 1
484
+ end
485
+ offset
486
+ end
487
+
488
+ def indent_lines(content)
489
+ s = ''
490
+ content.each_line {|line|
491
+ s += INDENT + line
492
+ }
493
+ s
494
+ end
495
+
496
+ def remove_ptr(content)
497
+ content.gsub('*', '')
498
+ end
499
+
500
+ def map_type(content)
501
+ case content
502
+ when 'BOOL'
503
+ 'Bool'
504
+ when 'int'
505
+ 'Int'
506
+ when 'uint'
507
+ 'UInt'
508
+ when 'long'
509
+ 'Long'
510
+ when 'void'
511
+ 'Void'
512
+ when /(.*)\*/
513
+ $1
514
+ else
515
+ content
516
+ end
517
+ end
518
+ end
519
+ end
@@ -0,0 +1,6 @@
1
+ require 'csharp_2_swift'
2
+ require 'objc_2_swift'
3
+
4
+ module SwiftTools
5
+ VERSION = "3.0.0"
6
+ end
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: swift-tools
3
+ version: !ruby/object:Gem::Version
4
+ version: 3.0.0
5
+ platform: ruby
6
+ authors:
7
+ - John Lyon-smith
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-08-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: methadone
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.9'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.9'
27
+ - !ruby/object:Gem::Dependency
28
+ name: code-tools
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '5.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '5.0'
41
+ description: ''
42
+ email: john@jamoki.com
43
+ executables:
44
+ - csharp2swift
45
+ - objc2swift
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - bin/csharp2swift
50
+ - bin/objc2swift
51
+ - lib/core_ext.rb
52
+ - lib/csharp_2_swift.rb
53
+ - lib/objc_2_swift.rb
54
+ - lib/swift_tools.rb
55
+ homepage: http://rubygems.org/gems/swift-tools
56
+ licenses:
57
+ - MIT
58
+ metadata: {}
59
+ post_install_message:
60
+ rdoc_options: []
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '2.2'
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ requirements: []
74
+ rubyforge_project:
75
+ rubygems_version: 2.4.5
76
+ signing_key:
77
+ specification_version: 4
78
+ summary: Tools for managing Swift source code
79
+ test_files: []