diff --git a/Sources/RswiftCore/Generators/ResourceFileStructGenerator.swift b/Sources/RswiftCore/Generators/ResourceFileStructGenerator.swift index 8b1c6e7a..077d965c 100644 --- a/Sources/RswiftCore/Generators/ResourceFileStructGenerator.swift +++ b/Sources/RswiftCore/Generators/ResourceFileStructGenerator.swift @@ -36,52 +36,86 @@ struct ResourceFileStructGenerator: ExternalOnlyStructGenerator { typealiasses: [], properties: firstLocales.flatMap { propertiesFromResourceFiles(resourceFiles: $0.1, at: externalAccessLevel) }, functions: firstLocales.flatMap { functionsFromResourceFiles(resourceFiles: $0.1, at: externalAccessLevel) }, - structs: [], + structs: firstLocales.flatMap { directoryStructsFromResourceFiles(resourceFiles: $0.1, at: externalAccessLevel) }, classes: [], os: [] ) } - private func propertiesFromResourceFiles(resourceFiles: [ResourceFile], at externalAccessLevel: AccessLevel) -> [Let] { - - return resourceFiles - .map { - return Let( - comments: ["Resource file `\($0.fullname)`."], - accessModifier: externalAccessLevel, - isStatic: true, - name: SwiftIdentifier(name: $0.fullname), - typeDefinition: .inferred(Type.FileResource), - value: "Rswift.FileResource(bundle: R.hostingBundle, name: \"\($0.filename)\", pathExtension: \"\($0.pathExtension)\")" - ) + private func propertiesFromResourceFiles(resourceFiles: [ResourceFile], includeAllFileLet: Bool = false, at externalAccessLevel: AccessLevel) -> [Let] { + let filteredFiles = resourceFiles.filter { !$0.isDirectory } + let filteredFileLets = filteredFiles.map { propertyForResourceFile($0, at: externalAccessLevel) } + + guard includeAllFileLet && !filteredFiles.isEmpty else { + return filteredFileLets } + + let allFilesLet = Let(comments: ["An array of all fileResources contained in this namespaced folder (not including subfolders)"], + accessModifier: externalAccessLevel, + isStatic: true, + name: "allFiles", + typeDefinition: .specified(Type._Array.withGenericArgs([Type.FileResource])), + value: "[" + filteredFileLets.map { $0.name.description }.joined(separator: ", ") + "]") + return [allFilesLet] + filteredFileLets + } + + private func propertyForResourceFile(_ resourceFile: ResourceFile, at externalAccessLevel: AccessLevel, overrideLabel: String? = nil) -> Let { + Let( + comments: ["Resource file `\(resourceFile.fullname)`."], + accessModifier: externalAccessLevel, + isStatic: true, + name: SwiftIdentifier(name: overrideLabel ?? resourceFile.fullname), + typeDefinition: .inferred(Type.FileResource), + value: "Rswift.FileResource(bundle: R.hostingBundle, name: \"\(resourceFile.filenameWithNamespace)\", pathExtension: \"\(resourceFile.pathExtension)\")" + ) } private func functionsFromResourceFiles(resourceFiles: [ResourceFile], at externalAccessLevel: AccessLevel) -> [Function] { return resourceFiles - .flatMap { resourceFile -> [Function] in - let fullname = resourceFile.fullname - let filename = resourceFile.filename - let pathExtension = resourceFile.pathExtension - - return [ - Function( - availables: [], - comments: ["`bundle.url(forResource: \"\(filename)\", withExtension: \"\(pathExtension)\")`"], - accessModifier: externalAccessLevel, - isStatic: true, - name: SwiftIdentifier(name: fullname), - generics: nil, - parameters: [ - Function.Parameter(name: "_", type: Type._Void, defaultValue: "()") - ], - doesThrow: false, - returnType: Type._URL.asOptional(), - body: "let fileResource = R.file.\(SwiftIdentifier(name: fullname))\nreturn fileResource.bundle.url(forResource: fileResource)", - os: [] - ) - ] - } + .filter { !$0.isDirectory } + .flatMap { functionsFromResourceFile($0, at: externalAccessLevel) } + } + + private func functionsFromResourceFile(_ resourceFile: ResourceFile, at externalAccessLevel: AccessLevel, overrideLabel: String? = nil) -> [Function] { + let fullname = resourceFile.fullname + let filenameWithNamespace = resourceFile.filenameWithNamespace + let pathExtension = resourceFile.pathExtension + return [ + Function( + availables: [], + comments: ["`bundle.url(forResource: \"\(filenameWithNamespace)\", withExtension: \"\(pathExtension)\")`"], + accessModifier: externalAccessLevel, + isStatic: true, + name: SwiftIdentifier(name: "\(overrideLabel ?? fullname)Url"), + generics: nil, + parameters: [], + doesThrow: false, + returnType: Type._URL.asOptional(), + body: "Self.\(SwiftIdentifier(name: overrideLabel ?? fullname)).bundle.url(forResource: Self.\(SwiftIdentifier(name: overrideLabel ?? fullname)))", + os: [] + ) + ] } + + private func directoryStructsFromResourceFiles(resourceFiles: [ResourceFile], at externalAccessLevel: AccessLevel) -> [Struct] { + resourceFiles + .filter { $0.isDirectory } + .compactMap { resource in + let structName = SwiftIdentifier(name: resource.filename) + return Struct( + availables: [], + comments: ["This struct is generated, and contains static references to \(resource.subfiles.count) files."], + accessModifier: externalAccessLevel, + type: Type(module: .host, name: structName), + implements: [], + typealiasses: [], + properties: propertiesFromResourceFiles(resourceFiles: resource.subfiles, includeAllFileLet: true, at: externalAccessLevel) + [propertyForResourceFile(resource, at: externalAccessLevel, overrideLabel: "directory")], + functions: functionsFromResourceFiles(resourceFiles: resource.subfiles, at: externalAccessLevel) + functionsFromResourceFile(resource, at: externalAccessLevel, overrideLabel: "directory"), + structs: [], + classes: [], + os: [] + ) + } + } } diff --git a/Sources/RswiftCore/ResourceTypes/ResourceFile.swift b/Sources/RswiftCore/ResourceTypes/ResourceFile.swift index 1da89b71..151e084d 100644 --- a/Sources/RswiftCore/ResourceTypes/ResourceFile.swift +++ b/Sources/RswiftCore/ResourceTypes/ResourceFile.swift @@ -21,9 +21,12 @@ struct ResourceFile { let fullname: String let filename: String + let filenameWithNamespace: String let pathExtension: String + let isDirectory: Bool + let subfiles: [ResourceFile] - init(url: URL) throws { + init(url: URL, withNamespace namespace: String? = nil, fileManager: FileManager) throws { pathExtension = url.pathExtension if ResourceFile.unsupportedExtensions.contains(pathExtension) { throw ResourceParsingError.unsupportedExtension(givenExtension: pathExtension, supportedExtensions: ["*"]) @@ -33,8 +36,19 @@ struct ResourceFile { guard let filename = url.filename else { throw ResourceParsingError.parsingFailed("Couldn't extract filename from URL: \(url)") } - + let filenameWithNamespace = namespace.map { "\($0)/\(filename)" } ?? filename + self.fullname = fullname self.filename = filename + self.filenameWithNamespace = filenameWithNamespace + + if let subfileURLs = try? fileManager.contentsOfDirectory(at: url, includingPropertiesForKeys: nil, options: .skipsHiddenFiles) { + let subfileNamespace = namespace.map { "\($0)/\(filename)"} ?? filename + self.subfiles = subfileURLs.compactMap { try? ResourceFile(url: $0, withNamespace: subfileNamespace, fileManager: fileManager) } + self.isDirectory = true + } else { + self.subfiles = [] + self.isDirectory = false + } } } diff --git a/Sources/RswiftCore/ResourceTypes/Resources.swift b/Sources/RswiftCore/ResourceTypes/Resources.swift index 8f762c1f..e74dde03 100644 --- a/Sources/RswiftCore/ResourceTypes/Resources.swift +++ b/Sources/RswiftCore/ResourceTypes/Resources.swift @@ -51,7 +51,7 @@ struct Resources { } // All previous assets can also possibly be used as files - if let resourceFile = tryResourceParsing({ try ResourceFile(url: url) }) { + if let resourceFile = tryResourceParsing({ try ResourceFile(url: url, fileManager: fileManager) }) { resourceFiles.append(resourceFile) } }