Upgrade to Pro — share decks privately, control downloads, hide ads and more …

CLIツールにSwift Concurrencyを適用させようとしている話

CLIツールにSwift Concurrencyを適用させようとしている話

2022/04/25@Swift愛好会 vol.67

417.72KI

April 25, 2022
Tweet

More Decks by 417.72KI

Other Decks in Programming

Transcript

  1. !@LJ
    $-*πʔϧʹ4XJGU$PODVSSFODZ
    Λద༻ͤͨ͞Α͏ͱ͍ͯ͠Δ࿩
    .PO

    4XJGUѪ޷ձWPM

    View full-size slide

  2. 1SFTFOUFS
    struct Me {


    let name = "Takuhiro Muta"


    let aka = "417.72KI"


    let company = "***"


    let twitter = "417_72ki"


    let qiita = "417_72ki"


    let gitHub = "417-72KI"


    let products = [


    "MockUserDefaults",


    "MultipartFormDataParser",


    "BuildConfig.swift",


    "SSGH"


    ]


    let contributing = [


    "Danger-Swift",


    "octokit.swift",


    "etc..."


    ]


    }

    View full-size slide

  3. 44()
    IUUQTRJJUBDPN@LJJUFNTCEECFFGDE

    View full-size slide

  4. .PUJWBUJPO
    w BTZODBXBJUʹؔ͢Δهࣄ͕J04ؔ࿈ͷ΋ͷ͹͔ΓͰ$-*πʔϧʹݴٴͨ͠ه
    ࣄΛݟ͔͚ͳ͔ͬͨ
    w 4XJGU͔Β-JOVYͰ΋4XJGU$PODVSSFODZ͕࢖͑ΔΑ͏ʹͳͬͨ
    w Ҏલ͸%JTQBUDI4FNBQIPSFΛ࢖ͬͨॲཧͷϒϩοΩϯά͕ඞཁ
    ˞%JTQBUDI.BJOΛ࢖͏ํ๏͕͋ΔΒ͍͚͠Ͳ΍Γํ෼͔ΒΜͷͰׂѪ
    w BTZODBXBJUΛ࢖͏͜ͱͰγϯϓϧʹॻ͖׵͑Δ͜ͱ͕Ͱ͖Δ͸ͣ

    View full-size slide

  5. .PEVMFT
    44()
    44()$PSF
    (JU)VC"1*
    TXJGUBSHVNFOUQBSTFS
    PDUPLJUTXJGU

    View full-size slide

  6. .PEVMFT
    targets: [


    .executableTarget(


    name: "SSGH",


    dependencies: [


    .product(name: "ArgumentParser", package: "swift-argument-parser"),


    "SSGHCore"


    ]


    ),


    .target(


    name: "SSGHCore",


    dependencies: ["GitHubAPI"]


    ),


    .target(


    name: "GitHubAPI",


    dependencies: ["OctoKit"]


    ),


    View full-size slide

  7. 1SFQBSBUJPO
    w TXJGUBSHVNFOUQBSTFSΛҎ্ʹ্͛Δ

    View full-size slide

  8. 1SFQBSBUJPO
    w PDUPLJUTXJGUʹDPOUSJCVUF͢Δ

    View full-size slide

  9. 1SFQBSBUJPO
    w 3FRVFTU,JU PDUPLJUTXJGUͷCBTF
    ʹDPOUSJCVUF͢Δ

    View full-size slide

  10. (JU)VC$MJFOU
    #FGPSF
    extension GitHubClientImpl {


    public func getUser(by userId: String) -> Result {


    var result: Result!


    let semaphore = DispatchSemaphore(value: 0)


    octoKit.user(session, name: userId) {


    result = $0


    semaphore.signal()


    }


    semaphore.wait()


    return result.map(User.init)


    .mapError {


    if ($0 as NSError).code == 404 {


    return .userNotFound(userId)


    }


    return .other($0)


    }


    }


    }

    View full-size slide

  11. (JU)VC$MJFOU
    "GUFS
    extension GitHubClientImpl {


    public func getUser(by userId: String) async throws -> User {


    try await withCheckedThrowingContinuation { continuation in


    octoKit.user(session, name: userId) {


    switch $0 {


    case let .success(result):


    continuation.resume(returning: User(result))


    case let .failure(error):


    if (error as NSError).code == 404 {


    continuation.resume(throwing: GitHubAPIError.userNotFound(userId))


    } else {


    continuation.resume(throwing: GitHubAPIError.other(error))


    }


    }


    }


    }


    }


    }

    View full-size slide

  12. $PSF
    #FGPSF
    func execute(mode: Mode) throws {


    switch mode {


    case let .specifiedTargets(targets):


    try targets.compactMap {


    switch gitHubClient.getUser(by: $0) {


    case let .success(user):


    return user


    case let .failure(error):


    dumpWarn(error)


    return nil


    }


    }


    .forEach(star(to:))


    }


    }

    View full-size slide

  13. $PSF
    "GUFS
    func execute(mode: Mode) async throws {


    switch mode {


    case let .specifiedTargets(targets):


    for target in targets {


    do {


    let user = try await gitHubClient.getUser(by: target)


    try await star(to: user)


    } catch {


    dumpWarn(error)


    }


    }


    }


    }


    View full-size slide

  14. 1BSTBCMF$PNNBOE
    #FGPSF
    @main


    struct SSGH: ParsableCommand {


    @Argument(help: "GitHub user name to give stars.")


    var targets: [String]


    @Option(name: [.customLong("github-token"), .customShort("t")],


    help: "GitHub Token to give stars. If not set, use `SSGH_TOKEN` in environment.")


    var gitHubToken: String?


    @Flag(name: [.customLong("dry-run"), .short], help: "dry-run mode. Only fetch lists to give stars.")


    var dryRunMode = false


    }


    View full-size slide

  15. 1BSTBCMF$PNNBOE
    "GUFS d

    @main


    struct SSGH: AsyncParsableCommand {


    @Argument(help: "GitHub user name to give stars.")


    var targets: [String]


    @Option(name: [.customLong("github-token"), .customShort("t")],


    help: "GitHub Token to give stars. If not set, use `SSGH_TOKEN` in environment.")


    var gitHubToken: String?


    @Flag(name: [.customLong("dry-run"), .short], help: "dry-run mode. Only fetch lists to give stars.")


    var dryRunMode = false


    }


    View full-size slide

  16. 1BSTBCMF$PNNBOE
    "GUFS d

    @main


    enum Main: AsyncMainProtocol {


    typealias Command = SSGH


    }


    struct SSGH: AsyncParsableCommand {


    @Argument(help: "GitHub user name to give stars.")


    var targets: [String]


    @Option(name: [.customLong("github-token"), .customShort("t")],


    help: "GitHub Token to give stars. If not set, use `SSGH_TOKEN` in environment.")


    var gitHubToken: String?


    @Flag(name: [.customLong("dry-run"), .short], help: "dry-run mode. Only fetch lists to give stars.")


    var dryRunMode = false


    }


    IUUQTHJUIVCDPNBQQMFTXJGUBSHVNFOUQBSTFSQVMM

    View full-size slide

  17. 1BSTBCMF$PNNBOESVO

    #FGPSF
    extension SSGH {


    func run() throws {


    let gitHubToken = try gitHubToken ?? (try Environment.getValue(forKey: .gitHubToken))


    let core = SSGHCore(


    gitHubToken: gitHubToken,


    dryRunMode: dryRunMode


    )


    do {


    try core.execute(mode: . specifiedTargets(targets))


    } catch {


    dumpError(error)


    Self.exit(withError: error)


    }


    }


    }


    View full-size slide

  18. 1BSTBCMF$PNNBOESVO

    "GUFS d

    extension SSGH {


    func run() async throws {


    let gitHubToken = try gitHubToken ?? (try Environment.getValue(forKey: .gitHubToken))


    let core = SSGHCore(


    gitHubToken: gitHubToken,


    dryRunMode: dryRunMode


    )


    do {


    try await core.execute(mode: .specifiedTargets(targets))


    } catch {


    dumpError(error)


    Self.exit(withError: error)


    }


    }


    }


    View full-size slide

  19. BTZODBXBJU׬શʹཧղͨ͠😊

    View full-size slide

  20. ͱ͍͏ເΛݟͨΜͩ

    View full-size slide

  21. *TTVFTXJUI4XJGU$PODVSSFODZ
    w 9DPEFҎԼͰϏϧυ͞ΕͨόΠφϦΛݹ͍04Ͱ࣮ߦ͠Α͏ͱ͢Δͱ

    ࿙Εͳ͘Ϋϥογϡ͢Δ
    w IUUQTEFWFMPQFSBQQMFDPNEPDVNFOUBUJPOYDPEFSFMFBTFOPUFTYDPEF@
    SFMFBTFOPUFT
    w IUUQTGPSVNTTXJGUPSHUTXJGUDPODVSSFODZCBDLEFQMPZJTTVF
    w IUUQTGPSVNTTXJGUPSHUBTZODBXBJUDSBTIPOJPTXJUIYDPEF
    w J04ɺUW04ɺXBUDI04ʹ͔͠ݴٴ͞Ε͍ͯͳ͍͕NBD04΋ಉ༷ͱߟ͑ΒΕΔ
    w ࣄ্࣮SFRVJSFE9DPEF

    View full-size slide

  22. *TTVFTXJUI4XJGU$PODVSSFODZ
    w NBD04Ͱಈ͔͢ͱΫϥογϡ͢Δ
    IUUQTHJUIVCDPN,*44()QVMMDIFDLT DIFDL@SVO@JE

    View full-size slide

  23. &OBCMF4XJGU$PODVSSFODZ
    w 9DPEFͰಈ͔ͨ͢ΊʹXPSL
    fl
    PXΛߋ৽͢Δ

    View full-size slide

  24. &OBCMF4XJGU$PODVSSFODZ
    w (JU)VC"DUJPOT্ͷNBD04͸·ͩQSJWBUFCFUB ݱࡏ
    ͳͷͰ

    BDDFTTSFRVFTUΛ౤͛Δ

    View full-size slide

  25. 5FTUQBTTFT😊
    IUUQTHJUIVCDPN,*44()BDUJPOTSVOT

    View full-size slide

  26. 5FTUQBTTFT😊
    IUUQTHJUIVCDPN,*44()BDUJPOTSVOT

    View full-size slide

  27. *NQSFTTJPO$PODMVTJPO
    w BTZODBXBJUͰॻ͘ͱγϯϓϧʹͳͬͨ ؾ͕͢Δ

    w %JTQBUDI4FNBQIPSFΑΓ͸ԯഒϚγ
    w "DUPSͷ֓೦ΛJ04։ൃ΍NBD04։ൃ΄Ͳؾʹ͠ͳͯ͘ྑ͍ͷͰ

    4XJGU$PODVSSFODZͷೖ໳ʹ͸ྑͦ͞͏
    w .POUFSFZ͡Όͳ͍ͱಈ࡞֬ೝͰ͖ͳ͍ͷ͕೉఺

    View full-size slide

  28. ελʔ͍ͩ͘͞
    IUUQTHJUIVCDPN,*44()

    View full-size slide