Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
それ CLI フレームワークがなくてもできるよ / Building CLI Tools Wi...
Search
Kuniwak
PRO
July 25, 2025
Programming
4.9k
18
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
それ CLI フレームワークがなくてもできるよ / Building CLI Tools Without Frameworks
『GopherのためのCLIツール開発』最新事情 LT
https://findy.connpass.com/event/362163/
Kuniwak
PRO
July 25, 2025
More Decks by Kuniwak
See All by Kuniwak
Codex を使った障害対応机上演習の実際
orgachem
PRO
0
390
AIベース静的検査器の偽陽性率を抑える工夫3選
orgachem
PRO
8
3.2k
仕様漏れ実装漏れをなくすトレーサビリティAI基盤のご紹介
orgachem
PRO
16
12k
要求定義・仕様記述・設計・検証の手引き - 理論から学ぶ明確で統一された成果物定義
orgachem
PRO
40
25k
DeNA での思い出 / Memories at DeNA
orgachem
PRO
8
3.8k
状態遷移図を書こう / Sequence Chart vs State Diagram
orgachem
PRO
4
830
テストケースの名前はどうつけるべきか?
orgachem
PRO
2
1.1k
欠陥を早期に発見するための Software Engineer in Test とその重要性 / What is Software Engineer in Test and How they works
orgachem
PRO
22
5.2k
住宅を WebXR で評価しよう / Evaluating My Home by WebXR
orgachem
PRO
0
260
Other Decks in Programming
See All in Programming
선언형 햅틱 라이브러리 진동을 소개합니다
l2hyunwoo
1
250
[JA] Domain Modeling with Vanilla Rails: 「操作」から再考するRailsの設計論
yasaichi
4
1.7k
クラウドゲーミングのパフォーマンス最適化に挑む / Cloud Gaming Performance Tuning
vivion
0
310
エージェントを使い続けるためのメタワーク / Meta-work for keeping using the agent
rkaga
6
2.1k
作れる、でも消せるか?──安全に「枯らして消す」機能削除術 / Drain, Then Delete
chobishiba
3
1.3k
SODA - FACT BOOK(EN)
sodainc
1
200
當我們開始不讀 Code - AI Agent 時代,Code Review 與 Clean Code 還剩下什麼?
jame2408
1
350
Apple Intelligenceとオンデバイス時代の到来
yahir0
0
190
AI-Powered Domain-Driven Design
ziobrando
0
370
Kotlinize Is Not Just Converting to Kotlin
chigichan24
1
210
React Native에 현대적인 빌드 도구 더하기: Rollipop 구현기
leegeunhyeok
0
180
Vapor Mode でアクセシビリティは 壊れないか検証した話
fuqda
1
300
Featured
See All Featured
Learning to Love Humans: Emotional Interface Design
aarron
275
41k
The Pragmatic Product Professional
lauravandoore
37
7.5k
Faster Mobile Websites
deanohume
310
32k
AI: The stuff that nobody shows you
jnunemaker
PRO
10
1.2k
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
38
3.1k
How To Stay Up To Date on Web Technology
chriscoyier
790
250k
Navigating Team Friction
lara
192
16k
16th Malabo Montpellier Forum Presentation
akademiya2063
PRO
0
440
Everyday Curiosity
cassininazir
0
390
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
52
6.2k
Connecting the Dots Between Site Speed, User Experience & Your Business [WebExpo 2025]
tammyeverts
11
1.1k
Helping Users Find Their Own Way: Creating Modern Search Experiences
danielanewman
31
3.4k
Transcript
ͦΕ$-*ϑϨʔϜϫʔΫ͕ ͳͯ͘Ͱ͖ΔΑ 'JOEZ5&$)#"50/ʮ(PQIFSͷͨΊͷ$-*πʔϧ։ൃʯ࠷৽ࣄ-5,VOJXBL
͍͑ͨ͜ͱ 2 w $-*πʔϧΛͭ͘Δͱ͖ϑϨʔϜϫʔΫΛΘͳ͍ બࢶʹΛ͚Α͏ w গ͠ଥڠ͢Εɺඪ४ϥΠϒϥϦͱ͍ίʔυ͚ͩ ʢߦ͙Β͍ʣͰ͍͍ͨͯͷ͜ͱ࣮ݱͰ͖Δ w ϑϨʔϜϫʔΫΛΘ͍ͣίʔυͰ࣮ݱͰ͖Ε
୯ҐͰͷϝϯςφϯεϑϦʔʹͰ͖Δ
աڈ࣮ w ࢲͷͭ͘ΔϑϨʔϜϫʔΫΛΘͳ͍$-*πʔϧ ͯ͢୯ҐͰϝϯςφϯεϑϦʔͰಈ࡞ w %FQFOEBCPUͳͲ͔Β੬ऑੑͷࢦఠΛड͚Δ ͜ͱ͔ͳΓ͍͠ 3 ͨͱ͑%F/"VOJUZNFUBDIFDLػೳՃΛআ͚طଘػೳۙ͘ϝϯς͠ͳ͘ͱ ੬ऑੑߋ৽ͳ͘ϢʔεέʔεΛຬ͍ͨͤͯΔ
3FOPWBUFͳͲΛ͑Ξοϓσʔτ͋ΔఔলྗԽͰ͖Δ͕ɺࢲͷ߹লྗԽͲ͜Ζ͔ ·͍ͬͨͬͯ͘ͳ͍ͷͰθϩ
༻ޠͷఆٛ 4 ·ͣ
5 ϑϨʔϜϫʔΫ ෦ͷن֨ΛܾΊΔͷɻϢʔβʔن֨ʹԊͬͨ෦ΛΈೖΕΔ ͜ͱͰػೳΛ࣮ݱ͢Δɻͨͱ͑VSGBWFDMJTQGDPCSBͳͲɻ ϥΠϒϥϦ ෦ͦͷͷɻϢʔβʔ෦Λ͖ʹΈ߹ΘͤͯػೳΛ࣮ݱ͢Δɻ ྫ͑KFTTFWELHP fl BHTɻಛʹϥΠϒϥϦͷ͏ͪඪ४ϥΠϒϥϦͱ ݴޠʹΈೖΕΒΕ͍ͯΔͷΛࢦ͢ɻྫ͑
fl BHɻ
6 ϑϨʔϜϫʔΫͱϥΠϒϥϦΛݟ͚Δʹɺ ͦͷίϯϙʔωϯτͷఏڙ͢Δܕ͕ࣗͷ࣮͢Δ ίϯϙʔωϯτʹͲͷఔସෆՄೳͳͷͱͯ͠ ొ͢Δ͔ΛଌΔͱΑ͍ɻ ଟ͚ΕϑϨʔϜϫʔΫతɺগͳ͚ΕϥΠϒϥϦతɻ ϑϨʔϜϫʔΫͱϥΠϒϥϦͷதؒతͳͷ͋Γ͑Δɻͨͱ͑3FBDUυϝΠϯ͔Β ϥΠϒϥϦͷΑ͏ʹΈ͑ɺ7JFX͔ΒϑϨʔϜϫʔΫͷΑ͏ʹΈ͑Δ
ϝϯςϑϦʔʹ͢Δίπ 7 ͕͜͜ࠓճͷϙΠϯτ
ͳͯ͋͘·ΓࠔΒͳ͍ػೳΛଥڠͯ͠อकੑΛͱΔ 8 อकੑ ػೳͷଟ͞ ͭ͘Δπʔϧͷอकੑͱ ػೳͷଟ͓͓͞Αͦ ൺྫ͢Δ
ͳͯ͋͘·ΓࠔΒͳ͍ػೳΛଥڠͯ͠อकੑΛͱΔ 9 อकੑ ػೳͷଟ͞ ʹཱͨͳ͍ อक͕େม ͜͜Λࢦ͢
ͳͯ͋͘·ΓࠔΒͳ͍ػೳΛଥڠͯ͠อकੑΛͱΔ 10 อकੑ ػೳͷଟ͞ ͔͜͜Βग़ൃͯ͠ ͜͜Λࢦ͢
ͳͯ͋͘·ΓࠔΒͳ͍ػೳΛଥڠͯ͠อकੑΛͱΔ 11 อकੑ ػೳͷଟ͞ ϑϨʔϜϫʔΫΛ͏ͱ͜͜ʹͳΓ͕ͪ
ඪ४ϥΠϒϥϦ͚ͩͷϨγϐ 12 ϝϯςϑϦʔΛ࣮ݱ͢ΔͨΊͷ
αϯϓϧίʔυ(JU)VCʹ͋Γ·͢ 13
ΤϯτϦϙΠϯτͷॻ͖ํ 14 Ϩγϐ
15 type InOut struct { Stdin io.Reader Stdout io.Writer Stderr
io.Writer } func NewInOut() *InOut { return &InOut{ Stdin: os.Stdin, Stdout: os.Stdout, Stderr: os.Stderr, } } type Command func(args []string, inout *InOut) int func Run(c Command) { args := os.Args[1:] exitStatus := c(args, NewInOut()) os.Exit(exitStatus) } ඪ४ೖग़ྗΛ·ͱΊͨߏମɻ JP3FBE$MPTFSJP8SJUF$MPTFSͰΑ͍ɻ ςετ࣌ʹదͳِͱࠩ͠ସ͑Δ ຊͷඪ४ೖग़ྗΛ࡞Δؔ $PNNBOEͷ࣮ߦؔ $-*ϓϩάϥϜຊମͷܕɻςετΛ ͘͢͢͠ΔͨΊʹ͏ ຊ ମ ࣮
16 $-*ϓϩάϥϜຊମ func MainCommand(args []string, inout *cli.InOut) int { fmt.Fprintln(inout.Stdout,
"Hello, World!") return 0 } ຊ ମ ࣮
17 NBJOؔ͜Ε͚ͩ func main() { cli.Run(MainCommand) } ຊ ମ ࣮
func TestMainCommand(t *testing.T) { stdin := strings.NewReader("") stdout := &bytes.Buffer{}
stderr := &bytes.Buffer{} exitStatus := MainCommand([]string{}, &cli.InOut{ Stdin: stdin, Stdout: stdout, Stderr: stderr, }) if exitStatus != 0 { t.Errorf("unexpected exit status: %d", exitStatus) } expected := "Hello, World!" if stdout.String() != expected { t.Errorf("want %q, got %q", expected, stdout.String()) } } $-*ϓϩάϥϜͷςετɻ ͔ͳΓ͋ͬ͞Γॻ͚Δ ς ε τ
19 func TestMainCommand(t *testing.T) { stdin := strings.NewReader("foo\nbar\n") stdout :=
&bytes.Buffer{} stderr := &bytes.Buffer{} exitStatus := MainCommand([]string{}, &cli.InOut{ Stdin: stdin, Stdout: stdout, Stderr: stderr, }) if exitStatus != 0 { t.Errorf("unexpected exit status: %d", exitStatus) } expected := "Hello, foo!\nHello, bar!\n" if stdout.String() != expected { t.Errorf("want %q, got %q", expected, stdout.String()) } } .$1αʔόʔͳͲରతͳ $-*ϓϩάϥϜͰͳ͘ ςετΛॻ͚Δɻ ͜Εඪ४ೖྗʹೖྗ͞Εͨ ໊લʹ)FMMPΛ͚ͭͯฦ͢ྫɻ ς ε τ
ϑϥάͷॻ͖ํ 20 Ϩγϐ
21 type Options struct { Foo string Bar string Help
bool } func ParseOptions(args []string, inout *cli.InOut) (*Options, error) { flags := flag.NewFlagSet("recipe2", flag.ContinueOnError) flags.SetOutput(inout.Stderr) options := &Options{} flags.StringVar(&options.Foo, "foo", "", "Foo") flags.StringVar(&options.Bar, "bar", "", "Bar") if err := flags.Parse(args); err != nil { if errors.Is(err, flag.ErrHelp) { options.Help = true return options, nil } return nil, err } return options, nil } $-*ϓϩάϥϜͷΦϓγϣϯߏମ Φϓγϣϯղੳؔ ຊ ମ ࣮
func TestParseOptions(t *testing.T) { testCases := []struct { Input []string
Expected *Options }{ { Input: []string{"-foo", "foo", "-bar", "bar"}, Expected: &Options{ Foo: "foo", Bar: "bar", }, }, { Input: []string{"-h"}, Expected: &Options{ Help: true, }, }, } Φϓγϣϯղੳؔͷςετ ೖྗͱͳΔҾΛఆٛͯ͠ ظ͢ΔΦϓγϣϯߏମΛॻ͘ ς ε τ
23 for _, testCase := range testCases { stdout :=
&bytes.Buffer{} stderr := &bytes.Buffer{} opts, err := ParseOptions(testCase.Input, &cli.InOut{ Stdin: strings.NewReader(""), Stdout: stdout, Stderr: stderr, }) if err != nil { t.Fatalf("failed to parse options: %v", err) } if !reflect.DeepEqual(opts, testCase.Expected) { t.Error(cmp.Diff(opts, testCase.Expected)) } } } ςʔϒϧۦಈͰςετ͢Δ ς ε τ
ϑϥάͷόϦσʔγϣϯͷॻ͖ํ 24 Ϩγϐ
25 type Options struct { SomethingRequired string Help bool }
func ParseOptions(args []string, inout *cli.InOut) (*Options, error) { flags := flag.NewFlagSet("recipe3", flag.ContinueOnError) flags.SetOutput(inout.Stderr) options := &Options{} flags.StringVar(&options.SomethingRequired, "something-required", "", "Something required") if err := flags.Parse(args); err != nil { if errors.Is(err, flag.ErrHelp) { options.Help = true return options, nil } return nil, err } if options.SomethingRequired == "" { return nil, errors.New("something-required is required") } return options, nil } $-*ϓϩάϥϜͷΦϓγϣϯߏମ ΦϓγϣϯղੳؔΛ࣮͢Δ όϦσʔγϣϯ͢Δ ຊ ମ ࣮
26 func TestParseOptions_Error(t *testing.T) { testCases := []struct { Input
[]string }{ { Input: []string{}, }, } for _, testCase := range testCases { stdout := &bytes.Buffer{} stderr := &bytes.Buffer{} _, err := ParseOptions(testCase.Input, &cli.InOut{ Stdin: strings.NewReader(""), Stdout: stdout, Stderr: stderr, }) if err == nil { t.Fatalf("expected error, got nil") } } } ςʔϒϧۦಈͰςετ͢Δ ඞਢͷҾ͕ͳ͚Ε ΤϥʔʹͳΔ͜ͱΛظ͢Δ ς ε τ
ϔϧϓͷॻ͖ํ 27 Ϩγϐ
28 func ParseOptions(args []string, inout *cli.InOut) (*Options, error) { flags
:= flag.NewFlagSet("recipe4", flag.ContinueOnError) flags.SetOutput(inout.Stderr) options := &Options{} flags.StringVar(&options.Foo, "foo", "", "foo") flags.StringVar(&options.Bar, "bar", "", "bar") flags.Usage = func() { inout.Stderr.Write([]byte("Usage: recipe4 [options]\n")) inout.Stderr.Write([]byte("OPTIONS\n")) flags.PrintDefaults() } if err := flags.Parse(args); err != nil { if errors.Is(err, flag.ErrHelp) { options.Help = true return options, nil } return nil, err } return options, nil } ຊ ମ ࣮ Φϓγϣϯղੳؔ ϔϧϓͷදࣔॲཧ
29 func TestFlagUsage(t *testing.T) { stdin := strings.NewReader("") stdout :=
&bytes.Buffer{} stderr := &bytes.Buffer{} _, err := ParseOptions([]string{"-h"}, &cli.InOut{ Stdin: stdin, Stdout: stdout, Stderr: stderr, }) if err != nil { t.Fatalf("failed to parse options: %v", err) } expected := `Usage: recipe4 [options] OPTIONS -bar string bar -foo string foo ` if stderr.String() != expected { t.Errorf("want %q, got %q", expected, stderr.String()) } } ς ε τ ϔϧϓΛදࣔͤ͞Δ ظͱͷҰகΛ֬ೝ
αϒίϚϯυͷॻ͖ํ 30 Ϩγϐ
31 type SubCommand struct { Name string Description string Run
Command } ຊ ମ ࣮ αϒίϚϯυͷ໊લ αϒίϚϯυߏମ αϒίϚϯυͷઆ໌ αϒίϚϯυͷॲཧ
32 Φϓγϣϯͷղੳ αϒίϚϯυͷ͋ΔίϚϯυΛએݴ αϒίϚϯυͷىಈ ϔϧϓͷ४උ func NewCommand(name string, cmds []SubCommand)
Command { return func(args []string, inout *InOut) int { flags := flag.NewFlagSet(name, flag.ContinueOnError) flags.SetOutput(inout.Stderr) flags.Usage = func() { fmt.Fprintf(inout.Stderr, "Usage: %s [command]\n\n", name) fmt.Fprintf(inout.Stderr, "COMMANDS\n") for _, cmd := range cmds { fmt.Fprintf(inout.Stderr, " %s\n \t%s\n", cmd.Name, cmd.Description) } } if err := flags.Parse(args); err != nil { if err == flag.ErrHelp { return 0 } return 1 } if flags.NArg() == 0 { fmt.Fprintf(inout.Stderr, "error: no command provided\n") flags.Usage() return 1 } for _, cmd := range cmds { if cmd.Name == flags.Arg(0) { return cmd.Run(flags.Args()[1:], inout) } } fmt.Fprintf(inout.Stderr, "error: unknown command: %s\n", flags.Arg(0)) flags.Usage() return 1 } } ຊ ମ ࣮
33 func TestNewCommand(t *testing.T) { s1 := SubCommand{ Name: "one",
Description: "1st subcommand", Run: func(args []string, inout *ProcInout) int { fmt.Fprintln(inout.Stdout, "1") return 0 }, } s2 := SubCommand{ Name: "two", Description: "2nd subcommand", Run: func(args []string, inout *ProcInout) int { fmt.Fprintln(inout.Stdout, "2") return 0 }, } ς ε τ ِͷαϒίϚϯυΛ༻ҙ ِͷαϒίϚϯυΛ༻ҙ
34 stdin := strings.NewReader("") stdout := &bytes.Buffer{} stderr := &bytes.Buffer{}
cmd := NewCommand("test", []SubCommand{s1, s2}) exitStatus := cmd([]string{"two"}, &ProcInout{ Stdin: stdin, Stdout: stdout, Stderr: stderr, }) if exitStatus != 0 { t.Errorf("expected exit status 0, got %d", exitStatus) } if stdout.String() != "2\n" { t.Errorf("expected output '2', got %q", stdout.String()) } } ς ε τ ͋ͱ͍ͭ௨Γςετ
͍͑ͨ͜ͱ 35 w $-*πʔϧΛͭ͘Δͱ͖ϑϨʔϜϫʔΫΛΘͳ͍ બࢶʹΛ͚Α͏ w গ͠ଥڠ͢Εɺඪ४ϥΠϒϥϦͱ͍ίʔυ͚ͩ ʢߦ͙Β͍ʣͰ͍͍ͨͯͷ͜ͱ࣮ݱͰ͖Δ w ϑϨʔϜϫʔΫΛΘ͍ͣίʔυͰ࣮ݱͰ͖Ε
୯ҐͰͷϝϯςφϯεϑϦʔʹͰ͖Δ
αʔυύʔςΟϥΠϒϥϦͷϨγϐ 36 Ͳ͏ͯ͠ଥڠͰ͖ͳ͍ͱ͖ͷͨΊͷ ൃ ද Ͱ ׂ Ѫ
37 ͜Ε͔Βհ͢ΔϨγϐɺඪ४ϥΠϒϥϦ͚ͩͩͱ ίʔυ͕͘ͳͬͯ͠·͏ͨΊंྠͷ࠶ൃ໌ͷײ͕ ग़͖ͯͯ͠·͏ͷʹͳ͍ͬͯΔɻ ൃ ද Ͱ ׂ Ѫ
γΣϧิͷॻ͖ํ 38 Ϩγϐ ൃ ද Ͱ ׂ Ѫ
39 ඪ४ϥΠϒϥϦ͚ͩͰ࣮ݱ͢Δͱߦ΄Ͳ͔͔Δ ʢαϯϓϧίʔυͷSFDJQFΛࢀরʣɻ ସͱͯ͑͠ΔϥΠϒϥϦʢOPUϑϨʔϜϫʔΫʣɿ w KFTTFWELHP fl BHT w TBHP
fl BHDNQM w ʜ ͍ํΛΔʹͦΕͧΕͷ3&"%.&Λࢀরͯ͠΄͍͠ɻ ൃ ද Ͱ ׂ Ѫ
ಉ͡Φϓγϣϯͷෳճͷग़ݱͷରԠ 40 Ϩγϐ ൃ ද Ͱ ׂ Ѫ
41 ༻Ͱ͖ΔϥΠϒϥϦʢOPUϑϨʔϜϫʔΫʣɿ w KFTTFWELHP fl BHT w BMFY fl JOUHPBSH
w ʜ ͍ํΛΔʹͦΕͧΕͷ3&"%.&Λࢀরͯ͠΄͍͠ɻ ൃ ද Ͱ ׂ Ѫ