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 +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: []
|