mirror of
https://github.com/nicklockwood/SwiftFormat.git
synced 2026-06-16 10:34:34 +00:00
128 lines
4.3 KiB
Swift
Executable File
128 lines
4.3 KiB
Swift
Executable File
// Copyright © 2017 Schibsted. All rights reserved.
|
|
|
|
import Foundation
|
|
|
|
func rename(_ old: String, to new: String, in files: [String]) -> [FormatError] {
|
|
var errors = [Error]()
|
|
for path in files {
|
|
let url = expandPath(path)
|
|
errors += enumerateFiles(withInputURL: url) { inputURL, outputURL in
|
|
do {
|
|
if let xml = try parseLayoutXML(inputURL) {
|
|
let output = try format(rename(old, to: new, in: xml))
|
|
try output.write(to: outputURL, atomically: true, encoding: .utf8)
|
|
}
|
|
return {}
|
|
} catch {
|
|
return { throw error }
|
|
}
|
|
}
|
|
}
|
|
return errors.map(FormatError.init)
|
|
}
|
|
|
|
func rename(_ old: String, to new: String, in xml: String) throws -> String {
|
|
guard let data = xml.data(using: .utf8, allowLossyConversion: true) else {
|
|
throw FormatError.parsing("Invalid xml string")
|
|
}
|
|
let xml = try XMLParser.parse(data: data)
|
|
return try format(rename(old, to: new, in: xml))
|
|
}
|
|
|
|
private let stringExpressions: Set<String> = [
|
|
"id", "udid", "uuid", "guid",
|
|
"title", "text", "label", "name", "identifier",
|
|
"key", "font", "image", "icon", "path", "url",
|
|
"touchDown",
|
|
"touchDownRepeat",
|
|
"touchDragInside",
|
|
"touchDragOutside",
|
|
"touchDragEnter",
|
|
"touchDragExit",
|
|
"touchUpInside",
|
|
"touchUpOutside",
|
|
"touchCancel",
|
|
"valueChanged",
|
|
"primaryActionTriggered",
|
|
"editingDidBegin",
|
|
"editingChanged",
|
|
"editingDidEnd",
|
|
"editingDidEndOnExit",
|
|
"allTouchEvents",
|
|
"allEditingEvents",
|
|
"allEvents",
|
|
]
|
|
|
|
func rename(_ old: String, to new: String, in xml: [XMLNode]) -> [XMLNode] {
|
|
return xml.map {
|
|
switch $0 {
|
|
case .comment:
|
|
return $0
|
|
case let .text(text):
|
|
guard let parts = try? parseStringExpression(text) else {
|
|
return $0
|
|
}
|
|
return .text(rename(old, to: new, in: parts) ?? text)
|
|
case .node(var name, var attributes, let children):
|
|
for (key, value) in attributes {
|
|
var isString = false
|
|
if value.contains("{"), value.contains("}") {
|
|
isString = true // May not actually be a string, but we can parse it as one
|
|
} else if attributeIsString(key, inNode: $0) ?? stringExpressions.contains(key) {
|
|
isString = true
|
|
} else {
|
|
isString = [
|
|
"ID", "Id", "Url", "URL", "Path",
|
|
"Title", "Text", "String", "Label",
|
|
"Name", "Identifier", "Key",
|
|
"Font", "Image", "Icon",
|
|
].contains { key.hasSuffix($0) }
|
|
}
|
|
if !isString, let expression = try? parseExpression(value) {
|
|
if let renamed = rename(old, to: new, in: expression) {
|
|
attributes[key] = renamed
|
|
}
|
|
} else if let parts = try? parseStringExpression(value),
|
|
let result = rename(old, to: new, in: parts)
|
|
{
|
|
attributes[key] = result
|
|
}
|
|
}
|
|
if name == old, name.lowercased() != name {
|
|
// Only rename non-HTML elements
|
|
name = new
|
|
}
|
|
return .node(
|
|
name: name,
|
|
attributes: attributes,
|
|
children: rename(old, to: new, in: children)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func rename(_ old: String, to new: String, in parts: [ParsedExpressionPart]) -> String? {
|
|
var changed = false
|
|
let parts: [String] = parts.map {
|
|
switch $0 {
|
|
case .string,
|
|
.comment:
|
|
return $0.description
|
|
case let .expression(expression):
|
|
if let renamed = rename(old, to: new, in: expression) {
|
|
changed = true
|
|
return "{\(renamed)}"
|
|
}
|
|
return $0.description
|
|
}
|
|
}
|
|
return changed ? parts.joined() : nil
|
|
}
|
|
|
|
private func rename(_ old: String, to new: String, in expression: ParsedLayoutExpression) -> String? {
|
|
guard expression.symbols.contains(.variable(old)) else {
|
|
return nil
|
|
}
|
|
return expression.description.replacingOccurrences(of: "\\b\(old)\\b", with: new, options: .regularExpression)
|
|
}
|