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

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

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

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

7a78f23eef1b0e883ef44c229a54f0bb?s=128

417.72KI

April 25, 2022
Tweet

More Decks by 417.72KI

Other Decks in Programming

Transcript

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

  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..." ] }
  3. 44() IUUQTRJJUBDPN@LJJUFNTCEECFFGDE

  4. 44()

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

    BTZODBXBJUΛ࢖͏͜ͱͰγϯϓϧʹॻ͖׵͑Δ͜ͱ͕Ͱ͖Δ͸ͣ
  6. .PEVMFT 44() 44()$PSF (JU)VC"1* TXJGUBSHVNFOUQBSTFS PDUPLJUTXJGU

  7. .PEVMFT targets: [ .executableTarget( name: "SSGH", dependencies: [ .product(name: "ArgumentParser",

    package: "swift-argument-parser"), "SSGHCore" ] ), .target( name: "SSGHCore", dependencies: ["GitHubAPI"] ), .target( name: "GitHubAPI", dependencies: ["OctoKit"] ),
  8. 1SFQBSBUJPO

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

  10. 1SFQBSBUJPO w PDUPLJUTXJGUʹDPOUSJCVUF͢Δ

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

  12. (JU)VC$MJFOU #FGPSF extension GitHubClientImpl { public func getUser(by userId: String)

    -> Result<User, GitHubAPIError> { var result: Result<OctoKit.User, Swift.Error>! 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) } } }
  13. (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)) } } } } } }
  14. $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:)) } }
  15. $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) } } } }
  16. 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 }
  17. 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 }
  18. 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
  19. 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) } } }
  20. 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) } } }
  21. BTZODBXBJU׬શʹཧղͨ͠😊

  22. ͱ͍͏ເΛݟͨΜͩ

  23. *TTVFTXJUI4XJGU$PODVSSFODZ w 9DPEFҎԼͰϏϧυ͞ΕͨόΠφϦΛݹ͍04Ͱ࣮ߦ͠Α͏ͱ͢Δͱ 
 ࿙Εͳ͘Ϋϥογϡ͢Δ w IUUQTEFWFMPQFSBQQMFDPNEPDVNFOUBUJPOYDPEFSFMFBTFOPUFTYDPEF@ SFMFBTFOPUFT w IUUQTGPSVNTTXJGUPSHUTXJGUDPODVSSFODZCBDLEFQMPZJTTVF

    w IUUQTGPSVNTTXJGUPSHUBTZODBXBJUDSBTIPOJPTXJUIYDPEF w J04ɺUW04ɺXBUDI04ʹ͔͠ݴٴ͞Ε͍ͯͳ͍͕NBD04΋ಉ༷ͱߟ͑ΒΕΔ w ࣄ্࣮SFRVJSFE9DPEF
  24. *TTVFTXJUI4XJGU$PODVSSFODZ w NBD04Ͱಈ͔͢ͱΫϥογϡ͢Δ IUUQTHJUIVCDPN,*44()QVMMDIFDLT DIFDL@SVO@JE

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

  26. &OBCMF4XJGU$PODVSSFODZ w (JU)VC"DUJPOT্ͷNBD04͸·ͩQSJWBUFCFUB ݱࡏ ͳͷͰ 
 BDDFTTSFRVFTUΛ౤͛Δ

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

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

  29. *NQSFTTJPO$PODMVTJPO w BTZODBXBJUͰॻ͘ͱγϯϓϧʹͳͬͨ ؾ͕͢Δ  w %JTQBUDI4FNBQIPSFΑΓ͸ԯഒϚγ w "DUPSͷ֓೦ΛJ04։ൃ΍NBD04։ൃ΄Ͳؾʹ͠ͳͯ͘ྑ͍ͷͰ 


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

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