swift-tools 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/csharp2swift +48 -0
- data/bin/objc2swift +62 -0
- data/lib/core_ext.rb +40 -0
- data/lib/csharp_2_swift.rb +276 -0
- data/lib/objc_2_swift.rb +519 -0
- data/lib/swift_tools.rb +6 -0
- metadata +79 -0
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
|
data/lib/objc_2_swift.rb
ADDED
@@ -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
|
data/lib/swift_tools.rb
ADDED
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: []
|