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
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
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
AIベース静的検査器の偽陽性率を抑える工夫3選
orgachem
PRO
8
3.1k
仕様漏れ実装漏れをなくすトレーサビリティAI基盤のご紹介
orgachem
PRO
16
12k
要求定義・仕様記述・設計・検証の手引き - 理論から学ぶ明確で統一された成果物定義
orgachem
PRO
40
24k
DeNA での思い出 / Memories at DeNA
orgachem
PRO
8
3.8k
状態遷移図を書こう / Sequence Chart vs State Diagram
orgachem
PRO
4
820
テストケースの名前はどうつけるべきか?
orgachem
PRO
2
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
HOME VR
orgachem
PRO
1
880
Other Decks in Programming
See All in Programming
自サークルのIdPからログアウトできるようになるまで
taiseiue
0
110
KiroのSpecで「五目並べ」を作ってみる
satoshi256kbyte
1
350
技術的負債の返済は、AI時代の複利で効く投資 — 経営としての意思決定とその遂行
curekoshimizu
1
2.2k
JAWS-UG 東京支部が始める、JAWS-UG支部コラボ / JAWS-UG lunchtime LT Collaboration
y0hgi
0
190
Workers Cache を知る
syumai
0
340
[2026-09-26]空論ジェネリックプロセス~テスト資産とAIで紡ぐ、再現可能なパフォーマンスチューニングの話~
tosite
0
260
FreeBSDでZabbixを動かす
kenkino
0
360
GitHubハンズオン講座 — 実務レベルのチーム開発のフローを身につけよう
junhat6
0
120
SalesForceを内製化!? ~ HR事業を支える顧客管理基盤のインフラを大公開 ~
oku053
0
140
Augmenting AI with the Power of Jakarta EE
ivargrimstad
0
470
GemmaをJevのように使ってみる / Use Gemma like Jev
kishida
5
710
見えないものを探る要求要件定義に必要な基本的思考 / invisible-requirement-thinking
minodriven
15
7.6k
Featured
See All Featured
How to Talk to Developers About Accessibility
jct
2
560
The Organizational Zoo: Understanding Human Behavior Agility Through Metaphoric Constructive Conversations (based on the works of Arthur Shelley, Ph.D)
kimpetersen
PRO
0
490
How to build an LLM SEO readiness audit: a practical framework
nmsamuel
2
930
Hiding What from Whom? A Critical Review of the History of Programming languages for Music
tomoyanonymous
3
1.3k
No one is an island. Learnings from fostering a developers community.
thoeni
21
3.8k
Noah Learner - AI + Me: how we built a GSC Bulk Export data pipeline
techseoconnect
PRO
0
450
Statistics for Hackers
jakevdp
799
230k
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
28
3.7k
Bridging the Design Gap: How Collaborative Modelling removes blockers to flow between stakeholders and teams @FastFlow conf
baasie
0
720
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
PRO
203
76k
ラッコキーワード サービス紹介資料
rakko
1
5.2M
Kristin Tynski - Automating Marketing Tasks With AI
techseoconnect
PRO
0
540
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&"%.&Λࢀরͯ͠΄͍͠ɻ ൃ ද Ͱ ׂ Ѫ