Merge pull request #34 from xcbuddy/localizable-files
Add support for variant groups
This commit is contained in:
commit
130987d515
|
@ -68,7 +68,7 @@ class ProjectFileElements {
|
||||||
|
|
||||||
// Normal resources
|
// Normal resources
|
||||||
if let resourceBuildFile = buildFile as? ResourcesBuildFile {
|
if let resourceBuildFile = buildFile as? ResourcesBuildFile {
|
||||||
files.formUnion(resourceBuildFile.paths)
|
files.formUnion(resourceBuildFile.paths.map(normalize))
|
||||||
|
|
||||||
// Core Data model resoureces
|
// Core Data model resoureces
|
||||||
} else if let coreDataModelBuildFile = buildFile as? CoreDataModelBuildFile {
|
} else if let coreDataModelBuildFile = buildFile as? CoreDataModelBuildFile {
|
||||||
|
@ -130,7 +130,9 @@ class ProjectFileElements {
|
||||||
let closestRelativeAbsolutePath = sourceRootPath.appending(closestRelativeRelativePath)
|
let closestRelativeAbsolutePath = sourceRootPath.appending(closestRelativeRelativePath)
|
||||||
|
|
||||||
// Add the first relative element.
|
// Add the first relative element.
|
||||||
let firstElement = addElement(relativePath: closestRelativeRelativePath, from: sourceRootPath, toGroup: groups.project, objects: objects)
|
guard let firstElement = addElement(relativePath: closestRelativeRelativePath, from: sourceRootPath, toGroup: groups.project, objects: objects) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// If it matches the file that we are adding or it's not a group we can exit.
|
// If it matches the file that we are adding or it's not a group we can exit.
|
||||||
if (closestRelativeAbsolutePath == path) || !(firstElement.element is PBXGroup) {
|
if (closestRelativeAbsolutePath == path) || !(firstElement.element is PBXGroup) {
|
||||||
|
@ -141,12 +143,14 @@ class ProjectFileElements {
|
||||||
var lastGroup: PBXGroup! = firstElement.element as! PBXGroup
|
var lastGroup: PBXGroup! = firstElement.element as! PBXGroup
|
||||||
var lastPath: AbsolutePath = firstElement.path
|
var lastPath: AbsolutePath = firstElement.path
|
||||||
|
|
||||||
path.relative(to: lastPath).components.forEach { component in
|
for component in path.relative(to: lastPath).components {
|
||||||
if lastGroup == nil { return }
|
if lastGroup == nil { return }
|
||||||
let element = addElement(relativePath: RelativePath(component),
|
guard let element = addElement(relativePath: RelativePath(component),
|
||||||
from: lastPath,
|
from: lastPath,
|
||||||
toGroup: lastGroup!,
|
toGroup: lastGroup!,
|
||||||
objects: objects)
|
objects: objects) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
lastGroup = element.element as? PBXGroup
|
lastGroup = element.element as? PBXGroup
|
||||||
lastPath = element.path
|
lastPath = element.path
|
||||||
}
|
}
|
||||||
|
@ -164,7 +168,7 @@ class ProjectFileElements {
|
||||||
@discardableResult func addElement(relativePath: RelativePath,
|
@discardableResult func addElement(relativePath: RelativePath,
|
||||||
from: AbsolutePath,
|
from: AbsolutePath,
|
||||||
toGroup: PBXGroup,
|
toGroup: PBXGroup,
|
||||||
objects: PBXObjects) -> (element: PBXFileElement, path: AbsolutePath) {
|
objects: PBXObjects) -> (element: PBXFileElement, path: AbsolutePath)? {
|
||||||
let absolutePath = from.appending(relativePath)
|
let absolutePath = from.appending(relativePath)
|
||||||
if elements[absolutePath] != nil {
|
if elements[absolutePath] != nil {
|
||||||
return (element: elements[absolutePath]!, path: from.appending(relativePath))
|
return (element: elements[absolutePath]!, path: from.appending(relativePath))
|
||||||
|
@ -179,13 +183,13 @@ class ProjectFileElements {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the file element
|
// Add the file element
|
||||||
if isVariantGroup(path: absolutePath) {
|
if isLocalized(path: absolutePath) {
|
||||||
return addVariantGroupElement(from: from,
|
addVariantGroup(from: from,
|
||||||
folderAbsolutePath: absolutePath,
|
absolutePath: absolutePath,
|
||||||
folderRelativePath: relativePath,
|
relativePath: relativePath,
|
||||||
name: name,
|
|
||||||
toGroup: toGroup,
|
toGroup: toGroup,
|
||||||
objects: objects)
|
objects: objects)
|
||||||
|
return nil
|
||||||
} else if isVersionGroup(path: absolutePath) {
|
} else if isVersionGroup(path: absolutePath) {
|
||||||
return addVersionGroupElement(from: from,
|
return addVersionGroupElement(from: from,
|
||||||
folderAbsolutePath: absolutePath,
|
folderAbsolutePath: absolutePath,
|
||||||
|
@ -201,36 +205,54 @@ class ProjectFileElements {
|
||||||
toGroup: toGroup,
|
toGroup: toGroup,
|
||||||
objects: objects)
|
objects: objects)
|
||||||
} else {
|
} else {
|
||||||
return addFileElement(from: from,
|
addFileElement(from: from,
|
||||||
fileAbsolutePath: absolutePath,
|
fileAbsolutePath: absolutePath,
|
||||||
fileRelativePath: relativePath,
|
fileRelativePath: relativePath,
|
||||||
name: name,
|
name: name,
|
||||||
toGroup: toGroup,
|
toGroup: toGroup,
|
||||||
objects: objects)
|
objects: objects)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a variant group element.
|
/// Adds a localized element/s
|
||||||
///
|
///
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - from: absolute path of the group the group is being added to.
|
/// - from: absolute path of the group the group is being added to.
|
||||||
/// - folderAbsolutePath: folder absolute path.
|
/// - absolutePath: localized file absolute path.
|
||||||
/// - folderRelativePath: folder path relative to the group absolute path.
|
/// - relativePath: localized path relative to the group absolute path.
|
||||||
/// - name: element name.
|
|
||||||
/// - toGroup: group where the new group will be added.
|
/// - toGroup: group where the new group will be added.
|
||||||
/// - objects: Xcode project objects.
|
/// - objects: Xcode project objects.
|
||||||
/// - Returns: added group.
|
func addVariantGroup(from: AbsolutePath,
|
||||||
func addVariantGroupElement(from: AbsolutePath,
|
absolutePath: AbsolutePath,
|
||||||
folderAbsolutePath: AbsolutePath,
|
relativePath _: RelativePath,
|
||||||
folderRelativePath: RelativePath,
|
|
||||||
name: String?,
|
|
||||||
toGroup: PBXGroup,
|
toGroup: PBXGroup,
|
||||||
objects: PBXObjects) -> (element: PBXFileElement, path: AbsolutePath) {
|
objects: PBXObjects) {
|
||||||
let group = PBXVariantGroup(children: [], sourceTree: .group, name: name, path: folderRelativePath.asString)
|
// /path/to/*.lproj/*
|
||||||
let reference = objects.addObject(group)
|
absolutePath.glob("*").forEach { localizedFile in
|
||||||
toGroup.children.append(reference)
|
let localizedName = localizedFile.components.last!
|
||||||
elements[folderAbsolutePath] = group
|
|
||||||
return (element: group, path: from.appending(folderRelativePath))
|
// Variant group
|
||||||
|
let variantGroupPath = absolutePath.parentDirectory.appending(component: localizedName)
|
||||||
|
var variantGroup: PBXVariantGroup! = elements[variantGroupPath] as? PBXVariantGroup
|
||||||
|
if variantGroup == nil {
|
||||||
|
variantGroup = PBXVariantGroup(sourceTree: .group, name: localizedName)
|
||||||
|
let variantGroupReference = objects.addObject(variantGroup)
|
||||||
|
toGroup.children.append(variantGroupReference)
|
||||||
|
elements[variantGroupPath] = variantGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
// Localized element
|
||||||
|
let localizedFilePath = "\(absolutePath.components.last!)/\(localizedName)" // e.g: en.lproj/Main.storyboard
|
||||||
|
let lastKnownFileType = Xcode.filetype(extension: localizedName) // e.g. Main.storyboard
|
||||||
|
let name = absolutePath.components.last!.split(separator: ".").first! // e.g. en
|
||||||
|
let localizedFileReference = PBXFileReference(sourceTree: .group,
|
||||||
|
name: String(name),
|
||||||
|
lastKnownFileType: lastKnownFileType,
|
||||||
|
path: localizedFilePath)
|
||||||
|
let localizedFileReferenceReference = objects.addObject(localizedFileReference)
|
||||||
|
variantGroup.children.append(localizedFileReferenceReference)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a version group element.
|
/// Adds a version group element.
|
||||||
|
@ -293,19 +315,17 @@ class ProjectFileElements {
|
||||||
/// - name: element name.
|
/// - name: element name.
|
||||||
/// - toGroup: group where the file will be added.
|
/// - toGroup: group where the file will be added.
|
||||||
/// - objects: Xcode project objects.
|
/// - objects: Xcode project objects.
|
||||||
/// - Returns: added file.
|
func addFileElement(from _: AbsolutePath,
|
||||||
func addFileElement(from: AbsolutePath,
|
|
||||||
fileAbsolutePath: AbsolutePath,
|
fileAbsolutePath: AbsolutePath,
|
||||||
fileRelativePath: RelativePath,
|
fileRelativePath: RelativePath,
|
||||||
name: String?,
|
name: String?,
|
||||||
toGroup: PBXGroup,
|
toGroup: PBXGroup,
|
||||||
objects: PBXObjects) -> (element: PBXFileElement, path: AbsolutePath) {
|
objects: PBXObjects) {
|
||||||
let lastKnownFileType = Xcode.filetype(extension: fileAbsolutePath.extension!)
|
let lastKnownFileType = Xcode.filetype(extension: fileAbsolutePath.extension!)
|
||||||
let file = PBXFileReference(sourceTree: .group, name: name, lastKnownFileType: lastKnownFileType, path: fileRelativePath.asString)
|
let file = PBXFileReference(sourceTree: .group, name: name, lastKnownFileType: lastKnownFileType, path: fileRelativePath.asString)
|
||||||
let reference = objects.addObject(file)
|
let reference = objects.addObject(file)
|
||||||
toGroup.children.append(reference)
|
toGroup.children.append(reference)
|
||||||
elements[fileAbsolutePath] = file
|
elements[fileAbsolutePath] = file
|
||||||
return (element: file, path: from.appending(fileRelativePath))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the group at the given path if it's been added to the file elements object.
|
/// Returns the group at the given path if it's been added to the file elements object.
|
||||||
|
@ -324,6 +344,19 @@ class ProjectFileElements {
|
||||||
return elements[path] as? PBXFileReference
|
return elements[path] as? PBXFileReference
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if a path represents a localized resource *.lproj.
|
||||||
|
///
|
||||||
|
/// - Parameter path: path to be checked.
|
||||||
|
/// - Returns: true if the file is a localized file.
|
||||||
|
func isLocalized(path: AbsolutePath) -> Bool {
|
||||||
|
// swiftlint:disable:next force_try
|
||||||
|
let regex = try! NSRegularExpression(pattern: ".lproj$", options: [])
|
||||||
|
let pathString = path.asString
|
||||||
|
return regex.firstMatch(in: pathString,
|
||||||
|
options: [],
|
||||||
|
range: NSRange(location: 0, length: pathString.count)) != nil
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if the path is a version group.
|
/// Returns true if the path is a version group.
|
||||||
///
|
///
|
||||||
/// - Parameter path: path.
|
/// - Parameter path: path.
|
||||||
|
@ -332,20 +365,37 @@ class ProjectFileElements {
|
||||||
return path.extension == "xcdatamodeld"
|
return path.extension == "xcdatamodeld"
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if the group is a variant group.
|
|
||||||
///
|
|
||||||
/// - Parameter path: path.
|
|
||||||
/// - Returns: true if the group is a variant group.
|
|
||||||
func isVariantGroup(path: AbsolutePath) -> Bool {
|
|
||||||
return path.extension == "lproj"
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if the path should be a group.
|
/// Returns true if the path should be a group.
|
||||||
///
|
///
|
||||||
/// - Parameter path: path.
|
/// - Parameter path: path.
|
||||||
/// - Returns: true if the path should be represented as a group.
|
/// - Returns: true if the path should be represented as a group.
|
||||||
func isGroup(path: AbsolutePath) -> Bool {
|
func isGroup(path: AbsolutePath) -> Bool {
|
||||||
return !isVariantGroup(path: path) && !isVersionGroup(path: path) && path.extension == nil
|
return !isVersionGroup(path: path) && path.extension == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Normalizes a path. Some paths have no direct representation in Xcode,
|
||||||
|
/// like localizable files. This method normalizes those and returns a project
|
||||||
|
/// representable path.
|
||||||
|
///
|
||||||
|
/// - Example:
|
||||||
|
/// /test/es.lproj/Main.storyboard ~> /test/es.lproj
|
||||||
|
///
|
||||||
|
/// - Parameter path: path to be normalized.
|
||||||
|
/// - Returns: normalized path.
|
||||||
|
func normalize(_ path: AbsolutePath) -> AbsolutePath {
|
||||||
|
// swiftlint:disable:next force_try
|
||||||
|
let localizedregex = try! NSRegularExpression(pattern: "(.+\\.lproj)/.+",
|
||||||
|
options: [])
|
||||||
|
let pathString = path.asString
|
||||||
|
let range = NSRange(location: 0, length: pathString.count)
|
||||||
|
if let localizedMatch = localizedregex.firstMatch(in: pathString,
|
||||||
|
options: [],
|
||||||
|
range: range) {
|
||||||
|
let lprojPath = (pathString as NSString).substring(with: localizedMatch.range(at: 1))
|
||||||
|
return AbsolutePath(lprojPath)
|
||||||
|
} else {
|
||||||
|
return path
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the relative path of the closest relative element to the source root path.
|
/// Returns the relative path of the closest relative element to the source root path.
|
||||||
|
|
|
@ -99,23 +99,32 @@ final class ProjectFileElementsTests: XCTestCase {
|
||||||
XCTAssertEqual(file.sourceTree, .group)
|
XCTAssertEqual(file.sourceTree, .group)
|
||||||
}
|
}
|
||||||
|
|
||||||
func test_addVariantGroupElement() throws {
|
func test_addVariantGroup() throws {
|
||||||
let from = AbsolutePath("/project/")
|
let fileName = "localizable.strings"
|
||||||
let folderAbsolutePath = AbsolutePath("/project/en.lproj")
|
let dir = try TemporaryDirectory()
|
||||||
let folderRelativePath = RelativePath("./en.lproj")
|
let localizedDir = dir.path.appending(component: "en.lproj")
|
||||||
|
try localizedDir.mkpath()
|
||||||
|
try localizedDir.appending(component: fileName).write("test")
|
||||||
|
let from = dir.path.parentDirectory
|
||||||
|
let absolutePath = localizedDir
|
||||||
|
let relativePath = RelativePath("en.lproj")
|
||||||
let group = PBXGroup()
|
let group = PBXGroup()
|
||||||
let objects = PBXObjects(objects: [:])
|
let objects = PBXObjects(objects: [:])
|
||||||
objects.addObject(group)
|
objects.addObject(group)
|
||||||
_ = subject.addVariantGroupElement(from: from,
|
subject.addVariantGroup(from: from,
|
||||||
folderAbsolutePath: folderAbsolutePath,
|
absolutePath: absolutePath,
|
||||||
folderRelativePath: folderRelativePath,
|
relativePath: relativePath,
|
||||||
name: nil,
|
|
||||||
toGroup: group,
|
toGroup: group,
|
||||||
objects: objects)
|
objects: objects)
|
||||||
let variantGroup: PBXVariantGroup? = try group.children.first?.object()
|
let variantGroupPath = dir.path.appending(component: fileName)
|
||||||
XCTAssertEqual(variantGroup?.path, "en.lproj")
|
let variantGroup: PBXVariantGroup = subject.group(path: variantGroupPath) as! PBXVariantGroup
|
||||||
XCTAssertEqual(variantGroup?.sourceTree, .group)
|
XCTAssertEqual(variantGroup.name, fileName)
|
||||||
XCTAssertNil(variantGroup?.name)
|
XCTAssertEqual(variantGroup.sourceTree, .group)
|
||||||
|
|
||||||
|
let fileReference: PBXFileReference? = try variantGroup.children.first?.object()
|
||||||
|
XCTAssertEqual(fileReference?.name, "en")
|
||||||
|
XCTAssertEqual(fileReference?.sourceTree, .group)
|
||||||
|
XCTAssertEqual(fileReference?.path, "en.lproj/\(fileName)")
|
||||||
}
|
}
|
||||||
|
|
||||||
func test_addVersionGroupElement() throws {
|
func test_addVersionGroupElement() throws {
|
||||||
|
@ -172,21 +181,33 @@ final class ProjectFileElementsTests: XCTestCase {
|
||||||
XCTAssertEqual(subject.file(path: path), file)
|
XCTAssertEqual(subject.file(path: path), file)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func test_isLocalized() {
|
||||||
|
let path = AbsolutePath("/path/to/es.lproj")
|
||||||
|
XCTAssertTrue(subject.isLocalized(path: path))
|
||||||
|
}
|
||||||
|
|
||||||
func test_isVersionGroup() {
|
func test_isVersionGroup() {
|
||||||
let path = AbsolutePath("/path/to/model.xcdatamodeld")
|
let path = AbsolutePath("/path/to/model.xcdatamodeld")
|
||||||
XCTAssertTrue(subject.isVersionGroup(path: path))
|
XCTAssertTrue(subject.isVersionGroup(path: path))
|
||||||
}
|
}
|
||||||
|
|
||||||
func test_isVariantGroup() {
|
|
||||||
let path = AbsolutePath("/path/to/en.lproj")
|
|
||||||
XCTAssertTrue(subject.isVariantGroup(path: path))
|
|
||||||
}
|
|
||||||
|
|
||||||
func test_isGroup() {
|
func test_isGroup() {
|
||||||
let path = AbsolutePath("/path/to/folder")
|
let path = AbsolutePath("/path/to/folder")
|
||||||
XCTAssertTrue(subject.isGroup(path: path))
|
XCTAssertTrue(subject.isGroup(path: path))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func test_normalize_whenLocalized() {
|
||||||
|
let path = AbsolutePath("/test/es.lproj/Main.storyboard")
|
||||||
|
let normalized = subject.normalize(path)
|
||||||
|
XCTAssertEqual(normalized, AbsolutePath("/test/es.lproj"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_normalize() {
|
||||||
|
let path = AbsolutePath("/test/file.swift")
|
||||||
|
let normalized = subject.normalize(path)
|
||||||
|
XCTAssertEqual(normalized, path)
|
||||||
|
}
|
||||||
|
|
||||||
func test_closestRelativeElementPath() {
|
func test_closestRelativeElementPath() {
|
||||||
let got = subject.closestRelativeElementPath(path: AbsolutePath("/a/framework/framework.framework"),
|
let got = subject.closestRelativeElementPath(path: AbsolutePath("/a/framework/framework.framework"),
|
||||||
sourceRootPath: AbsolutePath("/a/b/c/project"))
|
sourceRootPath: AbsolutePath("/a/b/c/project"))
|
||||||
|
|
|
@ -9,6 +9,7 @@ Please, check out guidelines: https://keepachangelog.com/en/1.0.0/
|
||||||
* Files and groups generation https://github.com/xcbuddy/xcbuddy/pull/28 by @pepibumur.
|
* Files and groups generation https://github.com/xcbuddy/xcbuddy/pull/28 by @pepibumur.
|
||||||
* Sources build phase generation https://github.com/xcbuddy/xcbuddy/pull/30 by @pepibumur.
|
* Sources build phase generation https://github.com/xcbuddy/xcbuddy/pull/30 by @pepibumur.
|
||||||
* Add `create-issue` command https://github.com/xcbuddy/xcbuddy/pull/32 by @pepibumur.
|
* Add `create-issue` command https://github.com/xcbuddy/xcbuddy/pull/32 by @pepibumur.
|
||||||
|
* Add support for variant groups https://github.com/xcbuddy/xcbuddy/pull/34 by @pepibumur.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue