swift-tools 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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: []