diff --git a/docs/cmd/ach.md b/docs/cmd/ach.md index 05eb84e..34f7b0b 100644 --- a/docs/cmd/ach.md +++ b/docs/cmd/ach.md @@ -9,9 +9,9 @@ ach automates routine work you does when you participate AtCoder contests. ### Options ``` - --config string config file (default is $HOME/.ach/config.yaml) + --config string config file (default "$HOME/.ach/config.yaml") -h, --help help for ach - --task-config string task config file (default is ./achTaskConfig.yaml + --task-config string task config file (default "./achTaskConfig.yaml") ``` ### SEE ALSO diff --git a/docs/cmd/ach_contest.md b/docs/cmd/ach_contest.md index e154d63..6739cd4 100644 --- a/docs/cmd/ach_contest.md +++ b/docs/cmd/ach_contest.md @@ -15,8 +15,8 @@ manipulates an AtCoder contest. ### Options inherited from parent commands ``` - --config string config file (default is $HOME/.ach/config.yaml) - --task-config string task config file (default is ./achTaskConfig.yaml + --config string config file (default "$HOME/.ach/config.yaml") + --task-config string task config file (default "./achTaskConfig.yaml") ``` ### SEE ALSO diff --git a/docs/cmd/ach_contest_create.md b/docs/cmd/ach_contest_create.md index 39eaca3..3bd08aa 100644 --- a/docs/cmd/ach_contest_create.md +++ b/docs/cmd/ach_contest_create.md @@ -23,8 +23,8 @@ ach contest create [contestName] [flags] ### Options inherited from parent commands ``` - --config string config file (default is $HOME/.ach/config.yaml) - --task-config string task config file (default is ./achTaskConfig.yaml + --config string config file (default "$HOME/.ach/config.yaml") + --task-config string task config file (default "./achTaskConfig.yaml") ``` ### SEE ALSO diff --git a/docs/cmd/ach_test.md b/docs/cmd/ach_test.md index bea7dd6..da94b87 100644 --- a/docs/cmd/ach_test.md +++ b/docs/cmd/ach_test.md @@ -30,8 +30,8 @@ ach test [flags] ### Options inherited from parent commands ``` - --config string config file (default is $HOME/.ach/config.yaml) - --task-config string task config file (default is ./achTaskConfig.yaml + --config string config file (default "$HOME/.ach/config.yaml") + --task-config string task config file (default "./achTaskConfig.yaml") ``` ### SEE ALSO diff --git a/docs/cmd/ach_version.md b/docs/cmd/ach_version.md index 2a5660b..4ec7ac3 100644 --- a/docs/cmd/ach_version.md +++ b/docs/cmd/ach_version.md @@ -19,8 +19,8 @@ ach version [flags] ### Options inherited from parent commands ``` - --config string config file (default is $HOME/.ach/config.yaml) - --task-config string task config file (default is ./achTaskConfig.yaml + --config string config file (default "$HOME/.ach/config.yaml") + --task-config string task config file (default "./achTaskConfig.yaml") ``` ### SEE ALSO diff --git a/go.mod b/go.mod index dc6ddcd..cf0f568 100644 --- a/go.mod +++ b/go.mod @@ -9,4 +9,5 @@ require ( github.com/spf13/viper v1.7.0 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect + gopkg.in/yaml.v2 v2.2.8 ) diff --git a/internal/cmd/ach/ach.go b/internal/cmd/ach/ach.go index bf8bee0..2a733e0 100644 --- a/internal/cmd/ach/ach.go +++ b/internal/cmd/ach/ach.go @@ -3,8 +3,10 @@ package ach import ( "fmt" "log" - "os" + "os/user" "path" + "path/filepath" + "strings" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -27,8 +29,18 @@ func NewAchCmd() *cobra.Command { Long: `ach automates routine work you does when you participate AtCoder contests. `, } - cmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.ach/config.yaml)") - cmd.PersistentFlags().StringVar(&taskCfgFile, "task-config", "", "task config file (default is ./achTaskConfig.yaml") + defaultConfigFile := path.Join("$HOME", ".ach", "config.yaml") + + cmd.PersistentFlags().StringVar( + &cfgFile, + "config", + defaultConfigFile, + "config file") + cmd.PersistentFlags().StringVar( + &taskCfgFile, + "task-config", + "./achTaskConfig.yaml", + "task config file") registerSubcommands(cmd) @@ -51,49 +63,52 @@ func initConfig() { } func readAppConfig() { - var configFileName string - - if cfgFile != "" { - viper.SetConfigName(cfgFile) - configFileName = cfgFile - } else { - home, err := os.UserHomeDir() - if err != nil { - log.Fatal(err) - } - - viper.AddConfigPath(path.Join(home, ".ach")) - viper.SetConfigName("config") - configFileName = "config" + v := viper.New() + + user, err := user.Current() + if err != nil { + log.Fatal(fmt.Errorf("NewAchCmd: %w", err)) + } + + home := user.HomeDir + + homeReplacedCfgFile := strings.Replace(cfgFile, "$HOME", home, 1) + + absCfgFile, err := filepath.Abs(homeReplacedCfgFile) + if err != nil { + log.Fatal(fmt.Errorf("failed to convert config to its absolute path: %w", err)) } - if err := viper.ReadInConfig(); err != nil { - log.Fatal(fmt.Errorf("failed to read app config %s: %w", configFileName, err)) + v.SetConfigName(strings.TrimSuffix(absCfgFile, path.Ext(absCfgFile))) + v.AddConfigPath("/") + + if err := v.ReadInConfig(); err != nil { + log.Fatal(fmt.Errorf("failed to read app config %s: %w", absCfgFile, err)) } - if err := viper.UnmarshalExact(&config.GlobalAppConfig); err != nil { - log.Fatal(fmt.Errorf("failed to parse app config %s: %w", configFileName, err)) + if err := v.UnmarshalExact(&config.GlobalAppConfig); err != nil { + log.Fatal(fmt.Errorf("failed to parse app config %s: %w", absCfgFile, err)) } + + config.GlobalAppConfig.ConfigDir = filepath.Dir(absCfgFile) } func readTaskConfig() { - var configFileName string + v := viper.New() - if taskCfgFile != "" { - viper.SetConfigFile(taskCfgFile) - - configFileName = cfgFile - } else { - viper.AddConfigPath(".") - viper.SetConfigName("achTaskConfig") - configFileName = "achTaskConfig" + absTaskCfgFile, err := filepath.Abs(taskCfgFile) + if err != nil { + log.Fatal(fmt.Errorf("failed to convert task config to its absolute path: %w", err)) } - if err := viper.ReadInConfig(); err != nil { - log.Print(fmt.Errorf("failed to read task config %s: %w", configFileName, err)) + v.SetConfigName(strings.TrimSuffix(absTaskCfgFile, path.Ext(absTaskCfgFile))) + v.AddConfigPath("/") + + if err := v.ReadInConfig(); err != nil { + return } - if err := viper.UnmarshalExact(&config.GlobalTaskConfig); err != nil { - log.Fatal(fmt.Errorf("failed to parse app config %s: %w", configFileName, err)) + if err := v.UnmarshalExact(&config.GlobalTaskConfig); err != nil { + log.Fatal(fmt.Errorf("failed to parse app config %s: %w", absTaskCfgFile, err)) } } diff --git a/internal/cmd/ach/contest/create/create.go b/internal/cmd/ach/contest/create/create.go index 9f6acfa..fc1fab6 100644 --- a/internal/cmd/ach/contest/create/create.go +++ b/internal/cmd/ach/contest/create/create.go @@ -3,11 +3,13 @@ package create import ( "fmt" "log" + "os" "os/exec" - "os/user" "path" "github.com/spf13/cobra" + "github.com/yuchiki/atcoderHelper/internal/config" + yaml "gopkg.in/yaml.v2" ) // NewContestCreateCmd returns a new contest create command. @@ -35,55 +37,101 @@ D is for directory. } func runE(cmd *cobra.Command, args []string) error { - user, _ := user.Current() - templateDirName := path.Join(user.HomeDir, "projects", "private", "atcoder", "D") - taskNames := []string{"A", "B", "C", "D", "E", "F"} + template, err := config.GetDefaultTemplate() + if err != nil { + return err + } + taskNames := []string{"A", "B", "C", "D", "E", "F"} contestName := args[0] - err := exec.Command("mkdir", contestName).Run() + output, err := exec.Command("mkdir", contestName).CombinedOutput() if err != nil { - return err + return fmt.Errorf("%s: %w", output, err) } + absTemplateDir := getAbsTemplateDirectory(template.TemplateDirectory) + for _, taskName := range taskNames { taskDirName := path.Join(contestName, taskName) - if output, err := exec.Command("cp", "-r", templateDirName, taskDirName).Output(); err != nil { + if output, err := exec.Command("cp", "-r", absTemplateDir, taskDirName).CombinedOutput(); err != nil { fmt.Print(output) + return fmt.Errorf("%s: %w", output, err) + } + + taskConfig := config.TaskConfig{ + ContestID: contestName, + TaskID: taskName, + Template: config.GlobalAppConfig.DefaultTemplate, + } + + taskConfigYaml, err := yaml.Marshal(taskConfig) + if err != nil { + return err + } + + taskConfigName := path.Join(taskDirName, "achTaskConfig.yaml") + + taskConfigFile, err := os.Create(taskConfigName) + if err != nil { + return err + } + defer taskConfigFile.Close() + + _, err = taskConfigFile.Write(taskConfigYaml) + if err != nil { return err } sampleDirName := path.Join(taskDirName, "sampleCases") - if output, err := exec.Command("mkdir", sampleDirName).Output(); err != nil { - fmt.Print(output) + err = createSampleCases(sampleDirName, 5) + if err != nil { return err } + } - for i := 1; i <= 5; i++ { - inputFileName := path.Join(sampleDirName, fmt.Sprintf("case%d.input", i)) + return nil +} - output, err := exec.Command( //nolint:gosec // TODO: fix this and all the execs. - "bash", - "-c", - fmt.Sprintf(`echo "[skip ach test]" > %s`, inputFileName)). - Output() - if err != nil { - fmt.Printf("%s can not be initialized", inputFileName) - fmt.Print(output) +func createSampleCases(sampleDirName string, n int) error { + if output, err := exec.Command("mkdir", sampleDirName).Output(); err != nil { + fmt.Print(output) - return err - } + return err + } + + for i := 1; i <= n; i++ { + inputFileName := path.Join(sampleDirName, fmt.Sprintf("case%d.input", i)) - outputFileName := path.Join(sampleDirName, fmt.Sprintf("case%d.expected", i)) + output, err := exec.Command( //nolint:gosec // TODO: fix this and all the execs. + "bash", + "-c", + fmt.Sprintf(`echo "[skip ach test]" > %s`, inputFileName)). + CombinedOutput() + if err != nil { + fmt.Printf("%s can not be initialized", inputFileName) + fmt.Print(output) - err = exec.Command("touch", outputFileName).Run() - if err != nil { - return err - } + return err + } + + outputFileName := path.Join(sampleDirName, fmt.Sprintf("case%d.expected", i)) + + err = exec.Command("touch", outputFileName).Run() + if err != nil { + return err } } return nil } + +func getAbsTemplateDirectory(templateDir string) string { + if path.IsAbs(templateDir) { + return templateDir + } + + return path.Join(config.GlobalAppConfig.ConfigDir, templateDir) +} diff --git a/internal/config/config.go b/internal/config/config.go index 44fcef9..4238151 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -26,13 +26,14 @@ type AppConfig struct { Username string Languages []Language Templates []Template - DefaultTemplate string `mapstructure:"default-template"` + ConfigDir string `mapstructure:"-" yaml:"-"` + DefaultTemplate string `mapstructure:"default-template" yaml:"default-template"` } // Language is an information of a programming language used when solving a task. type Language struct { Name string - AtCoderName string `mapstructure:"atcoder-name"` + AtCoderName string `mapstructure:"atcoder-name" yaml:"atcoder-name"` Build string Run string } @@ -41,14 +42,14 @@ type Language struct { type Template struct { Name string Language string - TemplateDirectory string `mapstructure:"template-directory"` - SourceFile string `mapstructure:"source-file"` + TemplateDirectory string `mapstructure:"template-directory" yaml:"template-directory"` + SourceFile string `mapstructure:"source-file" yaml:"source-file"` } // TaskConfig is a configuration for each task. type TaskConfig struct { - ContestID string `mapstructure:"contest-id"` - TaskID string `mapstructure:"task-id"` + ContestID string `mapstructure:"contest-id" yaml:"contest-id"` + TaskID string `mapstructure:"task-id" yaml:"task-id"` Template string } @@ -63,7 +64,7 @@ func (t *AppConfig) GetLanguage(name string) (Language, error) { return Language{}, fmt.Errorf("language %s not found: %w", name, ErrLanguageNotFound) } -// GetLanguage finds a language by name. +// GetLanguage finds a language designated by task config. func GetLanguage() (Language, error) { template, err := GetTemplate() if err != nil { @@ -84,7 +85,12 @@ func (t *AppConfig) GetTemplate(name string) (Template, error) { return Template{}, fmt.Errorf("template %s not found: %w", name, ErrTemplateNotFound) } -// GetTemplate finds a template by name. +// GetTemplate finds a template designated by task config. func GetTemplate() (Template, error) { return GlobalAppConfig.GetTemplate(GlobalTaskConfig.Template) } + +// GetDefaultTemplate finds a template. +func GetDefaultTemplate() (Template, error) { + return GlobalAppConfig.GetTemplate(GlobalAppConfig.DefaultTemplate) +} diff --git a/internal/config/config_test.go b/internal/config/config_test.go index af705c4..8643cc4 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -206,6 +206,7 @@ func newSampleAppConfig() AppConfig { SourceFile: "Program.cs", }, }, + ConfigDir: "sample", DefaultTemplate: "csharp", } }