From 570d9a01c669104aed66c8e1ab75ce07298da98a Mon Sep 17 00:00:00 2001 From: Ferenc Viasz-Kadi Date: Sat, 19 Oct 2024 12:35:02 +0200 Subject: [PATCH] Init command (#41) - Introduced a new `init` command that sets up the minimal example to help users get started. --- Package.resolved | 2 +- Sources/toucan-cli/Commands/Init.swift | 133 +++++++++++++++++++++++++ Sources/toucan-cli/Entrypoint.swift | 1 + 3 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 Sources/toucan-cli/Commands/Init.swift diff --git a/Package.resolved b/Package.resolved index b7fdcf8d..451db985 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "979e58bf8de276e032be54bbee652a49a1e83b7f0df0fd166dcf73870bb4c0bc", + "originHash" : "0d221128da17c85390a02b1bd85d463806d537b671cd23cadad8713557a21f98", "pins" : [ { "identity" : "async-http-client", diff --git a/Sources/toucan-cli/Commands/Init.swift b/Sources/toucan-cli/Commands/Init.swift new file mode 100644 index 00000000..39477b4c --- /dev/null +++ b/Sources/toucan-cli/Commands/Init.swift @@ -0,0 +1,133 @@ +import Foundation +import ArgumentParser +import ToucanSDK +import Logging +import FileManagerKit +import ShellKit + +extension Entrypoint { + + struct Init: AsyncParsableCommand { + + @Argument(help: "The name of the site directory (default: site).") + var siteDirectory: String = "site" + + @Option(name: .shortAndLong, help: "The log level to use.") + var logLevel: Logger.Level = .info + + func run() async throws { + var logger = Logger(label: "toucan") + logger.logLevel = logLevel + + let siteExists = fileManager.directoryExists(at: siteDirUrl) + + guard !siteExists else { + logger.error("Folder already exists: \(siteDirUrl)") + return + } + + do { + let source = Download( + sourceUrl: exampleSourceUrl, + targetDirUrl: siteDirUrl, + fileManager: fileManager + ) + let theme = Download( + sourceUrl: exampleThemeUrl, + targetDirUrl: themesDefaultDirUrl, + fileManager: fileManager + ) + + logger.info("Preparing source files.") + try await source.resolve() + + logger.info("Preparing theme files.") + try await theme.resolve() + + logger.info("'\(siteDirectory)' was prepared successfully.") + } + catch { + logger.error("\(String(describing: error))") + } + } + } +} + +extension Entrypoint.Init { + + var fileManager: FileManager { .default } + + var currentDirUrl: URL { + URL(fileURLWithPath: fileManager.currentDirectoryPath) + } + + var siteDirUrl: URL { + currentDirUrl.appendingPathComponent(siteDirectory) + } + + var exampleSourceUrl: URL { + .init( + string: + "https://github.com/toucansites/minimal-example/archive/refs/heads/main.zip" + )! + } + + var exampleThemeUrl: URL { + .init( + string: + "https://github.com/toucansites/minimal-theme/archive/refs/heads/main.zip" + )! + } + + var themesDefaultDirUrl: URL { + siteDirUrl.appendingPathComponent("src/themes/default") + } +} + +extension Entrypoint.Init { + + struct Download { + + let id = UUID().uuidString + let sourceUrl: URL + let targetDirUrl: URL + let fileManager: FileManager + + private var url: URL { + fileManager.temporaryDirectory.appendingPathComponent(id) + } + + private var zipUrl: URL { + url.appendingPathExtension("zip") + } + + func resolve() async throws { + let shell = Shell() + + /// Downloading the ZIP file into a temporary directory. + try shell.run( + #"curl -L -o \#(zipUrl.path) \#(sourceUrl.absoluteString)"# + ) + + /// Unzipping the file to a temporary directory. + try shell.run(#"unzip \#(zipUrl.path) -d \#(url.path)"#) + + /// Emptying the target directory. Git submodules can cause issues. + try? fileManager.removeItem(at: targetDirUrl) + + /// Finding the root directory URL. + let items = fileManager.listDirectory(at: url) + guard let rootDirName = items.first else { + throw URLError(.cannotParseResponse) + } + let rootDirUrl = url.appendingPathComponent(rootDirName) + + /// Moving files to the target directory. + try fileManager.moveItem(at: rootDirUrl, to: targetDirUrl) + + /// Cleaning up unnecessary files. + try? fileManager.delete(at: zipUrl) + try? fileManager.delete(at: url) + } + } +} diff --git a/Sources/toucan-cli/Entrypoint.swift b/Sources/toucan-cli/Entrypoint.swift index 1deabe01..7ab2c372 100644 --- a/Sources/toucan-cli/Entrypoint.swift +++ b/Sources/toucan-cli/Entrypoint.swift @@ -19,6 +19,7 @@ struct Entrypoint: AsyncParsableCommand { Generate.self, Serve.self, Watch.self, + Init.self, ] ) }