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.8k
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
AIベース静的検査器の偽陽性率を抑える工夫3選
orgachem
PRO
8
3k
仕様漏れ実装漏れをなくすトレーサビリティAI基盤のご紹介
orgachem
PRO
16
11k
要求定義・仕様記述・設計・検証の手引き - 理論から学ぶ明確で統一された成果物定義
orgachem
PRO
40
24k
DeNA での思い出 / Memories at DeNA
orgachem
PRO
8
3.7k
状態遷移図を書こう / Sequence Chart vs State Diagram
orgachem
PRO
4
810
テストケースの名前はどうつけるべきか?
orgachem
PRO
2
980
欠陥を早期に発見するための Software Engineer in Test とその重要性 / What is Software Engineer in Test and How they works
orgachem
PRO
22
5.1k
住宅を WebXR で評価しよう / Evaluating My Home by WebXR
orgachem
PRO
0
260
HOME VR
orgachem
PRO
1
880
Other Decks in Programming
See All in Programming
市販E-Readerを乗っ取れ 〜Embedded Swiftで電子ペーパーガジェットを制御する〜
trickart
0
160
GKE で Pod の見方を変えたら、スケールアウト時の挙動を真に捉えられた話
stkk
0
110
Foundry Localでエージェント開発
seosoft
0
170
モバイル交通系ICへのチャージ実例から考える、クロスプラットフォーム開発におけるiOS実機テスト設計とCI運用
yusuga
1
290
ハーネス設計入門 〜プロンプト、コンテキストの次〜
kinopeee
55
37k
Deep dive into the select statement (GopherCon UK)
jespino
0
170
AI に Inclusive UI を書かせよう — Design Rules Skill で Compose UI を作り直す
theoriatec2024
1
470
新卒PdEのリアル
ryu1013
1
480
MIZARU@SPAJAM2026 第二回予選
1901drama
0
110
AIは賢い。でも実行環境は? CLIおじさんがAI時代に伝えたいこと ~ CLIおじさんがAI時代に伝えたいこと ~
curekoshimizu
1
230
思考垂れ流し開発 ~音声入力 × AIエージェント × 開発ハーネスによる試行錯誤~
npostring
0
1.1k
AIを上手に使っていこうとしたら越境せざるを得なくなった話 〜実践1年で見えた境界を越えなければならない理由と進め方〜 / Crossing borders with AI
tomoyakitaura
4
1.1k
Featured
See All Featured
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
234
17k
For a Future-Friendly Web
brad_frost
183
10k
How STYLIGHT went responsive
nonsquared
100
6.3k
How to train your dragon (web standard)
notwaldorf
97
6.8k
How to Grow Your eCommerce with AI & Automation
katarinadahlin
PRO
1
270
More Than Pixels: Becoming A User Experience Designer
marktimemedia
3
520
Mozcon NYC 2025: Stop Losing SEO Traffic
samtorres
1
520
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
12
1.3k
Winning Ecommerce Organic Search in an AI Era - #searchnstuff2025
aleyda
1
2.1k
AI in Enterprises - Java and Open Source to the Rescue
ivargrimstad
0
1.5k
The Limits of Empathy - UXLibs8
cassininazir
1
650
JAMstack: Web Apps at Ludicrous Speed - All Things Open 2022
reverentgeek
1
590
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&"%.&Λࢀরͯ͠΄͍͠ɻ ൃ ද Ͱ ׂ Ѫ