Slide 1

Slide 1 text

ͦΕ$-*ϑϨʔϜϫʔΫ͕ ͳͯ͘΋Ͱ͖ΔΑ 'JOEZ5&$)#"50/ʮ(PQIFSͷͨΊͷ$-*πʔϧ։ൃʯ࠷৽ࣄ৘-5,VOJXBL

Slide 2

Slide 2 text

఻͍͑ͨ͜ͱ 2 w $-*πʔϧΛͭ͘Δͱ͖ϑϨʔϜϫʔΫΛ࢖Θͳ͍ બ୒ࢶʹ΋໨Λ޲͚Α͏ w গ͠ଥڠ͢Ε͹ɺඪ४ϥΠϒϥϦͱ୹͍ίʔυ͚ͩ ʢߦ͙Β͍ʣͰ͍͍ͨͯͷ͜ͱ͸࣮ݱͰ͖Δ w ϑϨʔϜϫʔΫΛ࢖Θͣ୹͍ίʔυͰ࣮ݱͰ͖Ε͹ ೥୯ҐͰͷϝϯςφϯεϑϦʔʹͰ͖Δ

Slide 3

Slide 3 text

աڈ࣮੷ w ࢲͷͭ͘ΔϑϨʔϜϫʔΫΛ࢖Θͳ͍$-*πʔϧ͸ ͢΂ͯ೥୯ҐͰϝϯςφϯεϑϦʔͰಈ࡞ w %FQFOEBCPUͳͲ͔Β੬ऑੑ౳ͷࢦఠΛड͚Δ ͜ͱ͸͔ͳΓ௝͍͠ 3 ͨͱ͑͹%F/"VOJUZNFUBDIFDL͸ػೳ௥ՃΛআ͚͹طଘػೳ͸೥ۙ͘ϝϯς͠ͳ͘ͱ΋ ੬ऑੑ΋ߋ৽΋ͳ͘ϢʔεέʔεΛຬ͍ͨͤͯΔ 3FOPWBUFͳͲΛ࢖͑͹Ξοϓσʔτ͸͋Δఔ౓লྗԽͰ͖Δ͕ɺࢲͷ৔߹͸লྗԽͲ͜Ζ͔ ·ͬͨ͘΍͍ͬͯͳ͍ͷͰ޻਺θϩ

Slide 4

Slide 4 text

༻ޠͷఆٛ 4 ·ͣ͸

Slide 5

Slide 5 text

5 ϑϨʔϜϫʔΫ ෦඼ͷن֨ΛܾΊΔ΋ͷɻϢʔβʔ͸ن֨ʹԊͬͨ෦඼Λ૊ΈೖΕΔ ͜ͱͰػೳΛ࣮ݱ͢Δɻͨͱ͑͹VSGBWFDMJ΍TQGDPCSBͳͲɻ ϥΠϒϥϦ ෦඼ͦͷ΋ͷɻϢʔβʔ͸෦඼Λ޷͖ʹ૊Έ߹ΘͤͯػೳΛ࣮ݱ͢Δɻ ྫ͑͹KFTTFWELHP fl BHTɻಛʹϥΠϒϥϦͷ͏ͪඪ४ϥΠϒϥϦͱ͸ ݴޠʹ૊ΈೖΕΒΕ͍ͯΔ΋ͷΛࢦ͢ɻྫ͑͹ fl BHɻ

Slide 6

Slide 6 text

6 ϑϨʔϜϫʔΫͱϥΠϒϥϦΛݟ෼͚Δʹ͸ɺ ͦͷίϯϙʔωϯτͷఏڙ͢Δܕ͕ࣗ෼ͷ࣮૷͢Δ ίϯϙʔωϯτʹͲͷఔ౓୅ସෆՄೳͳ΋ͷͱͯ͠ ొ৔͢Δ͔ΛଌΔͱΑ͍ɻ ଟ͚Ε͹ϑϨʔϜϫʔΫతɺগͳ͚Ε͹ϥΠϒϥϦతɻ ϑϨʔϜϫʔΫͱϥΠϒϥϦͷதؒతͳ΋ͷ΋͋Γ͑Δɻͨͱ͑͹3FBDU͸υϝΠϯ૚͔Β͸ ϥΠϒϥϦͷΑ͏ʹΈ͑ɺ7JFX૚͔Β͸ϑϨʔϜϫʔΫͷΑ͏ʹΈ͑Δ

Slide 7

Slide 7 text

ϝϯςϑϦʔʹ͢Δίπ 7 ͕͜͜ࠓճͷϙΠϯτ

Slide 8

Slide 8 text

ͳͯ͘΋͋·ΓࠔΒͳ͍ػೳΛଥڠͯ͠อकੑΛͱΔ 8 อकੑ ػೳͷଟ͞ ͭ͘Δπʔϧͷอकੑͱ ػೳͷଟ͞͸͓͓Αͦ ൓ൺྫ͢Δ

Slide 9

Slide 9 text

ͳͯ͘΋͋·ΓࠔΒͳ͍ػೳΛଥڠͯ͠อकੑΛͱΔ 9 อकੑ ػೳͷଟ͞ ໾ʹཱͨͳ͍ อक͕େม ͜͜Λ໨ࢦ͢

Slide 10

Slide 10 text

ͳͯ͘΋͋·ΓࠔΒͳ͍ػೳΛଥڠͯ͠อकੑΛͱΔ 10 อकੑ ػೳͷଟ͞ ͔͜͜Βग़ൃͯ͠ ͜͜Λ໨ࢦ͢

Slide 11

Slide 11 text

ͳͯ͘΋͋·ΓࠔΒͳ͍ػೳΛଥڠͯ͠อकੑΛͱΔ 11 อकੑ ػೳͷଟ͞ ϑϨʔϜϫʔΫΛ࢖͏ͱ͜͜ʹͳΓ͕ͪ

Slide 12

Slide 12 text

ඪ४ϥΠϒϥϦ͚ͩͷϨγϐ 12 ϝϯςϑϦʔΛ࣮ݱ͢ΔͨΊͷ

Slide 13

Slide 13 text

αϯϓϧίʔυ͸(JU)VCʹ͋Γ·͢ 13

Slide 14

Slide 14 text

ΤϯτϦϙΠϯτͷॻ͖ํ 14 Ϩγϐ

Slide 15

Slide 15 text

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$MPTFS΍JP8SJUF$MPTFSͰ΋Α͍ɻ ςετ࣌ʹ͸ద౰ͳِ෺ͱࠩ͠ସ͑Δ ຊ෺ͷඪ४ೖग़ྗΛ࡞Δؔ਺ $PNNBOEͷ࣮ߦؔ਺ $-*ϓϩάϥϜຊମͷܕɻςετΛ ͠΍͘͢͢ΔͨΊʹ࢖͏ ຊ ମ ࣮ ૷

Slide 16

Slide 16 text

16 $-*ϓϩάϥϜຊମ func MainCommand(args []string, inout *cli.InOut) int { fmt.Fprintln(inout.Stdout, "Hello, World!") return 0 } ຊ ମ ࣮ ૷

Slide 17

Slide 17 text

17 NBJOؔ਺͸͜Ε͚ͩ func main() { cli.Run(MainCommand) } ຊ ମ ࣮ ૷

Slide 18

Slide 18 text

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()) } } $-*ϓϩάϥϜͷςετɻ ͔ͳΓ͋ͬ͞Γॻ͚Δ ς ε τ

Slide 19

Slide 19 text

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Λ͚ͭͯฦ͢ྫɻ ς ε τ

Slide 20

Slide 20 text

ϑϥάͷॻ͖ํ 20 Ϩγϐ

Slide 21

Slide 21 text

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 } $-*ϓϩάϥϜͷΦϓγϣϯߏ଄ମ Φϓγϣϯղੳؔ਺ ຊ ମ ࣮ ૷

Slide 22

Slide 22 text

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, }, }, } Φϓγϣϯղੳؔ਺ͷςετ ೖྗͱͳΔҾ਺Λఆٛͯ͠ ظ଴͢ΔΦϓγϣϯߏ଄ମΛॻ͘ ς ε τ

Slide 23

Slide 23 text

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)) } } } ςʔϒϧۦಈͰςετ͢Δ ς ε τ

Slide 24

Slide 24 text

ϑϥάͷόϦσʔγϣϯͷॻ͖ํ 24 Ϩγϐ

Slide 25

Slide 25 text

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 } $-*ϓϩάϥϜͷΦϓγϣϯߏ଄ମ Φϓγϣϯղੳؔ਺Λ࣮૷͢Δ όϦσʔγϣϯ͢Δ ຊ ମ ࣮ ૷

Slide 26

Slide 26 text

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") } } } ςʔϒϧۦಈͰςετ͢Δ ඞਢͷҾ਺͕ͳ͚Ε͹ ΤϥʔʹͳΔ͜ͱΛظ଴͢Δ ς ε τ

Slide 27

Slide 27 text

ϔϧϓͷॻ͖ํ 27 Ϩγϐ

Slide 28

Slide 28 text

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 } ຊ ମ ࣮ ૷ Φϓγϣϯղੳؔ਺ ϔϧϓͷදࣔॲཧ

Slide 29

Slide 29 text

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()) } } ς ε τ ϔϧϓΛදࣔͤ͞Δ ظ଴ͱͷҰகΛ֬ೝ

Slide 30

Slide 30 text

αϒίϚϯυͷॻ͖ํ 30 Ϩγϐ

Slide 31

Slide 31 text

31 type SubCommand struct { Name string Description string Run Command } ຊ ମ ࣮ ૷ αϒίϚϯυͷ໊લ αϒίϚϯυߏ଄ମ αϒίϚϯυͷઆ໌ αϒίϚϯυͷॲཧ

Slide 32

Slide 32 text

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 } } ຊ ମ ࣮ ૷

Slide 33

Slide 33 text

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 }, } ς ε τ ِͷαϒίϚϯυΛ༻ҙ ِͷαϒίϚϯυΛ༻ҙ

Slide 34

Slide 34 text

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()) } } ς ε τ ͋ͱ͸͍ͭ΋௨Γςετ

Slide 35

Slide 35 text

఻͍͑ͨ͜ͱ 35 w $-*πʔϧΛͭ͘Δͱ͖ϑϨʔϜϫʔΫΛ࢖Θͳ͍ બ୒ࢶʹ΋໨Λ޲͚Α͏ w গ͠ଥڠ͢Ε͹ɺඪ४ϥΠϒϥϦͱ୹͍ίʔυ͚ͩ ʢߦ͙Β͍ʣͰ͍͍ͨͯͷ͜ͱ͸࣮ݱͰ͖Δ w ϑϨʔϜϫʔΫΛ࢖Θͣ୹͍ίʔυͰ࣮ݱͰ͖Ε͹ ೥୯ҐͰͷϝϯςφϯεϑϦʔʹͰ͖Δ

Slide 36

Slide 36 text

αʔυύʔςΟ੡ϥΠϒϥϦͷϨγϐ 36 Ͳ͏ͯ͠΋ଥڠͰ͖ͳ͍ͱ͖ͷͨΊͷ ൃ ද Ͱ ͸ ׂ Ѫ

Slide 37

Slide 37 text

37 ͜Ε͔Β঺հ͢ΔϨγϐ͸ɺඪ४ϥΠϒϥϦ͚ͩͩͱ ίʔυ͕௕͘ͳͬͯ͠·͏ͨΊंྠͷ࠶ൃ໌ͷײ͕ ग़͖ͯͯ͠·͏΋ͷʹͳ͍ͬͯΔɻ ൃ ද Ͱ ͸ ׂ Ѫ

Slide 38

Slide 38 text

γΣϧิ׬ͷॻ͖ํ 38 Ϩγϐ ൃ ද Ͱ ͸ ׂ Ѫ

Slide 39

Slide 39 text

39 ඪ४ϥΠϒϥϦ͚ͩͰ࣮ݱ͢Δͱߦ΄Ͳ͔͔Δ ʢαϯϓϧίʔυͷSFDJQFΛࢀরʣɻ ୅ସͱͯ͠࢖͑ΔϥΠϒϥϦʢOPUϑϨʔϜϫʔΫʣɿ w KFTTFWELHP fl BHT w TBHP fl BHDNQM w ʜ ࢖͍ํΛ஌Δʹ͸ͦΕͧΕͷ3&"%.&Λࢀরͯ͠΄͍͠ɻ ൃ ද Ͱ ͸ ׂ Ѫ

Slide 40

Slide 40 text

ಉ͡Φϓγϣϯͷෳ਺ճͷग़ݱ΁ͷରԠ 40 Ϩγϐ ൃ ද Ͱ ͸ ׂ Ѫ

Slide 41

Slide 41 text

41 ࢖༻Ͱ͖ΔϥΠϒϥϦʢOPUϑϨʔϜϫʔΫʣɿ w KFTTFWELHP fl BHT w BMFY fl JOUHPBSH w ʜ ࢖͍ํΛ஌Δʹ͸ͦΕͧΕͷ3&"%.&Λࢀরͯ͠΄͍͠ɻ ൃ ද Ͱ ͸ ׂ Ѫ