Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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..." ] }

Slide 3

Slide 3 text

44() IUUQTRJJUBDPN@LJJUFNTCEECFFGDE

Slide 4

Slide 4 text

44()

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

.PEVMFT targets: [ .executableTarget( name: "SSGH", dependencies: [ .product(name: "ArgumentParser", package: "swift-argument-parser"), "SSGHCore" ] ), .target( name: "SSGHCore", dependencies: ["GitHubAPI"] ), .target( name: "GitHubAPI", dependencies: ["OctoKit"] ),

Slide 8

Slide 8 text

1SFQBSBUJPO

Slide 9

Slide 9 text

1SFQBSBUJPO w TXJGUBSHVNFOUQBSTFSΛҎ্ʹ্͛Δ

Slide 10

Slide 10 text

1SFQBSBUJPO w PDUPLJUTXJGUʹDPOUSJCVUF͢Δ

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

(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) } } }

Slide 13

Slide 13 text

(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)) } } } } } }

Slide 14

Slide 14 text

$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:)) } }

Slide 15

Slide 15 text

$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) } } } }

Slide 16

Slide 16 text

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 }

Slide 17

Slide 17 text

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 }

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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) } } }

Slide 20

Slide 20 text

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) } } }

Slide 21

Slide 21 text

BTZODBXBJU׬શʹཧղͨ͠😊

Slide 22

Slide 22 text

ͱ͍͏ເΛݟͨΜͩ

Slide 23

Slide 23 text

*TTVFTXJUI4XJGU$PODVSSFODZ w 9DPEFҎԼͰϏϧυ͞ΕͨόΠφϦΛݹ͍04Ͱ࣮ߦ͠Α͏ͱ͢Δͱ 
 ࿙Εͳ͘Ϋϥογϡ͢Δ w IUUQTEFWFMPQFSBQQMFDPNEPDVNFOUBUJPOYDPEFSFMFBTFOPUFTYDPEF@ SFMFBTFOPUFT w IUUQTGPSVNTTXJGUPSHUTXJGUDPODVSSFODZCBDLEFQMPZJTTVF w IUUQTGPSVNTTXJGUPSHUBTZODBXBJUDSBTIPOJPTXJUIYDPEF w J04ɺUW04ɺXBUDI04ʹ͔͠ݴٴ͞Ε͍ͯͳ͍͕NBD04΋ಉ༷ͱߟ͑ΒΕΔ w ࣄ্࣮SFRVJSFE9DPEF

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

5FTUQBTTFT😊 IUUQTHJUIVCDPN,*44()BDUJPOTSVOT

Slide 28

Slide 28 text

5FTUQBTTFT😊 IUUQTHJUIVCDPN,*44()BDUJPOTSVOT

Slide 29

Slide 29 text

*NQSFTTJPO$PODMVTJPO w BTZODBXBJUͰॻ͘ͱγϯϓϧʹͳͬͨ ؾ͕͢Δ w %JTQBUDI4FNBQIPSFΑΓ͸ԯഒϚγ w "DUPSͷ֓೦ΛJ04։ൃ΍NBD04։ൃ΄Ͳؾʹ͠ͳͯ͘ྑ͍ͷͰ 
 4XJGU$PODVSSFODZͷೖ໳ʹ͸ྑͦ͞͏ w .POUFSFZ͡Όͳ͍ͱಈ࡞֬ೝͰ͖ͳ͍ͷ͕೉఺

Slide 30

Slide 30 text

࠷ޙʹ

Slide 31

Slide 31 text

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