Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: fulldecent/swift6-module-template
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 16.0
Choose a base ref
...
head repository: fulldecent/swift6-module-template
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: main
Choose a head ref
  • 1 commit
  • 3 files changed
  • 1 contributor

Commits on Jan 3, 2025

  1. Copy the full SHA
    d57afdf View commit details
Showing with 124 additions and 123 deletions.
  1. +11 −13 README.md
  2. +0 −110 configure.rb
  3. +113 −0 configure.swift
24 changes: 11 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -10,24 +10,24 @@ Use this template as a starting point for any Swift 6 module that you want other

Your new Swift module will immediately have working, compilable code, and implement these best practices:

- Ability to be used from Swift Package Manager
- Clean folder structure
- MIT license
- Testing as a standard
- Turnkey access to GitHub Actions testing
- Semantic versioning and a CHANGELOG
- Included example/demo app using SwiftUI
- Use a Xcode project to manage your code
- Ability to be used from Swift Package Manager
- Clean folder structure
- MIT license
- Testing as a standard
- Turnkey access to GitHub Actions testing
- Semantic versioning and a CHANGELOG
- Included example/demo app using SwiftUI
- Use a Xcode project to manage your code

## How to use this

Clone or [download a release](https://github.com/fulldecent/swift5-module-template/releases) and run the `./configure.rb` program. It will ask you some questions and generate a project.
Clone or [download a release](https://github.com/fulldecent/swift6-module-template/releases) and run the `./configure.swift` program. It will ask you some questions and generate a project.

You then add all the interesting features you want your module to have.

### Automating the configure script

To skip interactive prompts in the `./configure` script, use these environment variables:
To skip interactive prompts in the `./configure.swift` script, use these environment variables:

| Template variable | Environment variable |
| -------------------------------- | ---------------------------------------- |
@@ -36,11 +36,10 @@ To skip interactive prompts in the `./configure` script, use these environment v
| `com.AN.ORGANIZATION.IDENTIFIER` | `SMT_COM_AN_ORGANIZATION_IDENTIFIER` |
| `__AUTHOR NAME__` | `SMT_AUTHOR_NAME` |
| `__TODAYS_DATE__` | `SMT_TODAYS_DATE` |
| `__TODAYS_DATE__` (date format) | `SMT_DATE_FORMAT_STRING` Note: this will ask you for today’s date but it will use this format in the template. |
| `__TODAYS_YEAR__` | `SMT_TODAYS_YEAR` |
| `__GITHUB_USERNAME__` | `SMT_GITHUB_USERNAME` |

For example, you may use: `export SMT_ORGANIZATION_NAME='Awesome Org'` before running `./configure`.
For example, you may use: `export SMT_ORGANIZATION_NAME='Awesome Org'` before running `./configure.swift`.

## How it works

@@ -59,4 +58,3 @@ graph LR
## Contributing

See the file [Recipe.md](Recipe.md) for the complete steps (e.g. Open Xcode, make new project, click here, type that, …) of how we made the template.

110 changes: 0 additions & 110 deletions configure.rb

This file was deleted.

113 changes: 113 additions & 0 deletions configure.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#!/usr/bin/env swift

import Foundation

func promptForVariable(variable: String, defaultValue: String) -> String {
if variable == "__PROJECT_NAME__", CommandLine.arguments.count == 2 {
return CommandLine.arguments[1]
}
print("Please enter a value for \(variable) (default: \"\(defaultValue)\")")
if let answer = readLine(), !answer.isEmpty {
return answer
}
return defaultValue
}

func replaceVariablesInFiles(substitutions: [(from: String, to: String)]) {
let fileManager = FileManager.default
let enumerator = fileManager.enumerator(atPath: ".")

while let fileName = enumerator?.nextObject() as? String {
var isDirectory: ObjCBool = false
if fileManager.fileExists(atPath: fileName, isDirectory: &isDirectory), !isDirectory.boolValue {
do {
var text = try String(contentsOfFile: fileName, encoding: .utf8)
for substitution in substitutions {
text = text.replacingOccurrences(of: substitution.from, with: substitution.to)
}
try text.write(toFile: fileName, atomically: true, encoding: .utf8)
} catch {
// Skip this file if it isn’t valid UTF-8
}
}
}
}

func replaceVariablesInFileNames(substitutions: [(from: String, to: String)]) {
let fileManager = FileManager.default
let contents = try! fileManager.contentsOfDirectory(atPath: ".")

for fileName in contents {
if fileName == "." || fileName == ".." {
continue
}
var newFileName = fileName
for substitution in substitutions {
newFileName = newFileName.replacingOccurrences(of: substitution.from, with: substitution.to)
}
if newFileName != fileName {
try! fileManager.moveItem(atPath: fileName, toPath: newFileName)
}
var isDirectory: ObjCBool = false
if fileManager.fileExists(atPath: newFileName, isDirectory: &isDirectory), isDirectory.boolValue {
fileManager.changeCurrentDirectoryPath(newFileName)
replaceVariablesInFileNames(substitutions: substitutions)
fileManager.changeCurrentDirectoryPath("..")
}
}
}

struct Env {
static let ENV_VARIABLE_PREFIX = "SMT"

static func fetchSMT(templateVarName: String, defaultValue: String, prompt: (String, String) -> String) -> String {
let environmentVarName = nameFor(templateVarName: templateVarName)
if let value = ProcessInfo.processInfo.environment[environmentVarName] {
return value
}
return prompt(templateVarName, defaultValue)
}

static func nameFor(templateVarName: String) -> String {
return "\(ENV_VARIABLE_PREFIX)_\(sanitize(templateVarName: templateVarName))"
}

private static func sanitize(templateVarName: String) -> String {
return templateVarName
.uppercased()
.replacingOccurrences(of: "\\W", with: "_", options: .regularExpression)
.trimmingCharacters(in: CharacterSet(charactersIn: "_"))
}
}

let yearFormatter = DateFormatter()
yearFormatter.dateFormat = "yyyy"

var substitutionPairs: [(from: String, to: String)] = [
(from: "xxPROJECTxNAMExx", to: "MyProject"),
(from: "__ORGANIZATION NAME__", to: "Awesome Org"),
(from: "com.AN.ORGANIZATION.IDENTIFIER", to: "com.awesome"),
(from: "__AUTHOR NAME__", to: "Mr McAwesome"),
(from: "__TODAYS_DATE__", to: DateFormatter.localizedString(from: Date(), dateStyle: .medium, timeStyle: .none)),
(from: "__TODAYS_YEAR__", to: yearFormatter.string(from: Date())),
(from: "__GITHUB_USERNAME__", to: "awesome_octocat")
]

// Update the values through environment variables or prompts
substitutionPairs = substitutionPairs.map { pair in
(from: pair.from, to: Env.fetchSMT(templateVarName: pair.from, defaultValue: pair.to, prompt: promptForVariable))
}

let fileManager = FileManager.default
fileManager.changeCurrentDirectoryPath(fileManager.currentDirectoryPath)

// Create OUTPUT folder and copy your template folder
try! fileManager.createDirectory(atPath: "OUTPUT", withIntermediateDirectories: true, attributes: nil)
try! fileManager.copyItem(atPath: "xxPROJECTxNAMExx", toPath: "OUTPUT/xxPROJECTxNAMExx")

// Move into OUTPUT and do variable replacement
fileManager.changeCurrentDirectoryPath("OUTPUT")
replaceVariablesInFiles(substitutions: substitutionPairs)
replaceVariablesInFileNames(substitutions: substitutionPairs)

print("Done, your project is now ready to use in the OUTPUT/ folder")