twigs-ios/twigs-cli/TwigsCli.swift

124 lines
3.6 KiB
Swift

//
// main.swift
// twigs-cli
//
// Created by William Brawner on 1/6/22.
// Copyright © 2022 William Brawner. All rights reserved.
//
import Foundation
import ArgumentParser
import TwigsCore
@main
enum TwigsCli {
static func main() async throws {
await Twigs.main()
}
}
protocol AsyncParsableCommand: ParsableCommand {
mutating func runAsync() async throws
}
extension ParsableCommand {
static func main() async {
do {
var command = try parseAsRoot(nil)
if var asyncCommand = command as? AsyncParsableCommand {
try await asyncCommand.runAsync()
} else {
try command.run()
}
} catch {
exit(withError: error)
}
}
}
struct Twigs: ParsableCommand {
static var configuration = CommandConfiguration(
commandName: "twigs-cli",
abstract: "A CLI for Twigs, a personal finance application focused on individual and family budgeting",
version: "1.0.0",
subcommands: [Twigs.Auth.self]
)
}
extension Twigs {
struct Auth: ParsableCommand {
static var configuration = CommandConfiguration(
abstract: "commands for authentication with a Twigs server",
subcommands: [Twigs.Auth.Login.self]
)
}
}
extension Twigs.Auth {
struct Login: AsyncParsableCommand {
static var configuration = CommandConfiguration(
abstract: "login with an existing account"
)
@Option(name: [.short, .long], help: "")
var token: String = ""
@Option(name: [.short, .long], parsing: SingleValueParsingStrategy.next, help: "the URL to the twigs server")
var url: String = ""
mutating func runAsync() async throws {
let config = Config(url: url, token: token)
// TODO: Check if token was provided, if not check config file
if let token = await config.token, !token.isEmpty {
print("using token for login")
// TODO: Save token to disk
return
}
print("Username:", terminator: " ")
if let input = readLine(), !input.isEmpty {
await config.setUsername(input)
} else {
throw TwigsErrors.input("ERROR: Username cannot be empty")
}
guard let passChars = getpass("Password: ") else {
throw TwigsErrors.input("ERROR: Unable to read password")
}
let password = String(cString: passChars)
if password.isEmpty {
throw TwigsErrors.input("ERROR: Password cannot be empty")
}
await config.setPassword(password)
if let username = await config.username, let password = await config.password {
print("Logging in as \(username) with password \(password)")
let apiService = TwigsApiService()
apiService.baseUrl = await config.url
try await apiService.login(username: username, password: password)
// TODO: Persist url and token in config file
}
}
}
}
actor Config {
var url: String? = nil
var username: String? = nil
var password: String? = nil
var token: String? = nil
init(url: String? = nil, token: String? = nil) {
self.url = url
self.token = token
}
func setUsername(_ username: String) {
self.username = username
}
func setPassword(_ password: String) {
self.password = password
}
}
enum TwigsErrors: Error {
case input(String)
}