Skip to content

Commit 0c55f0b

Browse files
authored
Merge pull request #52 from chermehdi/custom/templates
Add custom template support
2 parents 36cea94 + 104e9f6 commit 0c55f0b

File tree

9 files changed

+206
-22
lines changed

9 files changed

+206
-22
lines changed

README.md

+57-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ and help execute tests locally via their favorite programming language.
1010
- There are two ways for installing egor.
1111

1212
### Download compiled binary
13-
- You can download the binary that corresponds to your operating system and add it to your `PATH` variable, you can find the binaries in the [releases](https://github.com/chermehdi/go-egor/releases) page.
13+
- You can download the binary corresponding to your operating system and add it to your `PATH` variable, you can find the binaries in the [releases](https://github.com/chermehdi/go-egor/releases) page.
1414
- For people running Mac OSX Catalina, this won't work and you will be forced to go with the Build from source solution.
1515

1616
### Build from source
@@ -68,6 +68,62 @@ $ egor config set cpp.lib.location
6868
$ ls /home/directory/include
6969
kratos
7070
```
71+
72+
## Custom templates
73+
74+
- Egor also support the use of custom templates per each language, powered by the golang template engine. to take full advantage of it you can
75+
take a look at all the details in the official [documentation](https://golang.org/pkg/text/template/).
76+
77+
- A typical configuration for a template, is the following:
78+
```
79+
//
80+
// {{ if .Problem}} {{ .Problem }} {{ end }} {{ if .Url }} {{ .Url }} {{ end }}
81+
{{- if .Author }}
82+
// @author {{ .Author }}
83+
// created {{ .Time }}
84+
{{- end }}
85+
//
86+
#include <iostream>
87+
#include <vector>
88+
#include <set>
89+
#include <algorithm>
90+
#include <map>
91+
92+
using namespace std;
93+
{{ if .MultipleTestCases }}
94+
void solve() {
95+
}
96+
{{ end }}
97+
98+
int main() {
99+
{{- if .FastIO }}
100+
ios_base::sync_with_stdio(false);
101+
cin.tie(0);
102+
{{- end}}
103+
104+
{{- if .MultipleTestCases }}
105+
int t; cin >> t;
106+
while(t--) {
107+
solve();
108+
}
109+
{{- end}}
110+
}
111+
```
112+
Each template is provided by a model containing some basic information about the task that is going to be generated
113+
to help create dynamic templates, the model reference is
114+
```go
115+
type TemplateContext struct {
116+
Author string
117+
Time string
118+
MultipleTestCases bool
119+
Interactive bool
120+
FastIO bool
121+
Problem string
122+
Url string
123+
}
124+
```
125+
You can access any of the model fields in the template, and make changes accordingly dependending on your preferences.
126+
- Running the command `egor config set config.templates.{lang} /path/to/template/file` will register the given template and will use it for the given language for future tasks.
71127
## Contribution
72128

73129
- Contribution to the project can be done in multiple ways, you can report bugs and issues or you can discuss new features that you like being added in future versions by creating a new issue

commands/config.go

+7
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ func UpdateConfiguration(config *config.Config, key, value string) error {
2626
config.Author = value
2727
} else if lowerKey == "cpp.lib.location" {
2828
config.CppLibraryLocation = value
29+
} else if strings.HasPrefix(key, "custom.template") {
30+
lang := key[strings.LastIndex(key, ".")+1:]
31+
config.CustomTemplate[lang] = value
2932
} else {
3033
// Unknow key
3134
return errors.New(fmt.Sprintf("Unknown configuration property %s", key))
@@ -74,6 +77,10 @@ func GetAction(context *cli.Context) error {
7477
color.Green("lang.default \t\t %s\n", configuration.Lang.Default)
7578
color.Green("author \t\t %s\n", configuration.Author)
7679
color.Green("cpp.lib.location\t\t %s\n", configuration.CppLibraryLocation)
80+
color.Green("config.templates: \n\n")
81+
for k, v := range configuration.CustomTemplate {
82+
color.Green("\t%s\t\t %s\n", k, v)
83+
}
7784
return nil
7885
} else {
7986
value, err := config.GetConfigurationValue(configuration, context.Args().First())

commands/create.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ func CreateTaskAction(context *cli.Context) error {
125125
return err
126126
}
127127
}
128-
location, err := CreateDirectoryStructure(task, *configuration, curDir)
128+
location, err := CreateDirectoryStructure(task, *configuration, curDir, nil)
129129
if err != nil {
130130
return err
131131
}

commands/parse.go

+24-7
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func SerializeTask(meta EgorMeta) (string, error) {
3131
return buffer.String(), nil
3232
}
3333

34-
func CreateDirectoryStructure(task Task, config Config, rootDir string) (string, error) {
34+
func CreateDirectoryStructure(task Task, config Config, rootDir string, context *cli.Context) (string, error) {
3535
taskDir := path.Join(rootDir, task.Name)
3636
if err := os.Mkdir(taskDir, 0777); err != nil {
3737
return "", err
@@ -78,7 +78,7 @@ func CreateDirectoryStructure(task Task, config Config, rootDir string) (string,
7878
return "", err
7979
}
8080
}
81-
templateContent, err := templates.ResolveTemplateByLanguage(config.Lang.Default)
81+
templateContent, err := templates.ResolveTemplateByLanguage(config)
8282
if err != nil {
8383
return "", err
8484
}
@@ -91,7 +91,10 @@ func CreateDirectoryStructure(task Task, config Config, rootDir string) (string,
9191
if err != nil {
9292
return "", err
9393
}
94-
if err = compiledTemplate.Execute(taskFile, config); err != nil {
94+
templateContext := CreateTemplateContext(config, task)
95+
templateContext.MultipleTestCases = context.Bool("multiple")
96+
templateContext.FastIO = context.Bool("fast-io")
97+
if err = compiledTemplate.Execute(taskFile, templateContext); err != nil {
9598
return "", err
9699
}
97100
return taskDir, nil
@@ -141,7 +144,7 @@ func waitForShutDown(server *http.Server, done chan<- string, quit <-chan string
141144
done <- content
142145
}
143146

144-
func ParseAction(_ *cli.Context) error {
147+
func ParseAction(context *cli.Context) error {
145148
msgReceiveChannel := make(chan string, 1)
146149
msgReadChannel := make(chan string, 1)
147150

@@ -170,7 +173,7 @@ func ParseAction(_ *cli.Context) error {
170173
if err != nil {
171174
return err
172175
}
173-
taskDir, err := CreateDirectoryStructure(*task, *config, cwd)
176+
taskDir, err := CreateDirectoryStructure(*task, *config, cwd, context)
174177
if err != nil {
175178
color.Red("Error happened %v", err)
176179
return err
@@ -185,8 +188,22 @@ func ParseAction(_ *cli.Context) error {
185188
// and an additional `egor-meta.json` file, and finally your task file, which is usually a `main.cpp` or `Main.java`
186189
// file depending on the default configured language.
187190
var ParseCommand = cli.Command{
188-
Name: "parse",
189-
Aliases: []string{"p"},
191+
Name: "parse",
192+
Aliases: []string{"p"},
193+
Flags: []cli.Flag{
194+
&cli.BoolFlag{
195+
Name: "fast-io",
196+
Usage: "Indicates that this task will require the use of Fast IO",
197+
Aliases: []string{"io", "fio"},
198+
Value: false,
199+
},
200+
&cli.BoolFlag{
201+
Name: "multiple",
202+
Usage: "Indicates if this task has multiple test cases",
203+
Aliases: []string{"m", "mul"},
204+
Value: false,
205+
},
206+
},
190207
Usage: "Parse task from navigator",
191208
UsageText: "egor parse",
192209
Action: ParseAction,

commands/parse_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ func TestCreateDirectoryStructure(t *testing.T) {
8787
CreateDirectory(rootDir)
8888
defer DeleteDir(rootDir)
8989

90-
_, err := CreateDirectoryStructure(task, configuration, rootDir)
90+
_, err := CreateDirectoryStructure(task, configuration, rootDir, nil)
9191
assert.NoError(t, err)
9292

9393
taskDir := path.Join(rootDir, task.Name)

config/config.go

+4
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ type Config struct {
2727
Version string `yaml:"version"`
2828
Author string `yaml:"author"`
2929
CppLibraryLocation string `yaml:"cpp_lib_location"`
30+
// Should contain template locations per programming language code
31+
// The expected format is: cpp -> /path/to/template_cpp.template
32+
CustomTemplate map[string]string `yaml:"custom_template"`
3033
}
3134

3235
func (conf *Config) HasCppLibrary() bool {
@@ -57,6 +60,7 @@ func createDefaultConfiguration() *Config {
5760
Version: LatestVersion,
5861
ConfigFileName: "egor-meta.json",
5962
CppLibraryLocation: path.Join(homeDir, "include"),
63+
CustomTemplate: make(map[string]string),
6064
}
6165
}
6266

config/task.go

+27
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package config
22

3+
import "time"
4+
35
// Represents a Competitive programming task's input and output for a some test case.
46
type TestCase struct {
57
Input string
@@ -33,3 +35,28 @@ type Task struct {
3335
Output IOType
3436
Languages map[string]LanguageDescription
3537
}
38+
39+
// Template parameters
40+
// This type will hold all the available information to the templates loaded by the user
41+
type TemplateContext struct {
42+
Author string
43+
Time string
44+
MultipleTestCases bool
45+
Interactive bool
46+
FastIO bool
47+
Problem string
48+
Url string
49+
}
50+
51+
// Creates a new template context from the configuration and the task metadata
52+
func CreateTemplateContext(config Config, task Task) TemplateContext {
53+
return TemplateContext{
54+
Author: config.Author,
55+
Time: time.Now().Format(time.UnixDate),
56+
MultipleTestCases: false,
57+
Interactive: false,
58+
FastIO: false,
59+
Problem: task.Name,
60+
Url: task.Url,
61+
}
62+
}

templates/resolver.go

+16-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package templates
33
import (
44
"errors"
55
"fmt"
6+
"github.com/chermehdi/egor/config"
7+
"io/ioutil"
68
)
79

810
const CppTemplate = `
@@ -57,7 +59,20 @@ const PythonTemplate = `
5759
# {{end}}
5860
`
5961

60-
func ResolveTemplateByLanguage(lang string) (string, error) {
62+
func ResolveTemplateByLanguage(config config.Config) (string, error) {
63+
templates := config.CustomTemplate
64+
path, has := templates[config.Lang.Default]
65+
if !has {
66+
return resolveWithDefaultTemplate(config.Lang.Default)
67+
}
68+
content, err := ioutil.ReadFile(path)
69+
if err != nil {
70+
return "", err
71+
}
72+
return string(content), nil
73+
}
74+
75+
func resolveWithDefaultTemplate(lang string) (string, error) {
6176
switch lang {
6277
case "cpp":
6378
return CppTemplate, nil

templates/resolver_test.go

+69-11
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,89 @@
11
package templates
22

33
import (
4+
"github.com/chermehdi/egor/config"
5+
"io/ioutil"
6+
"os"
7+
"path"
48
"testing"
59

610
"github.com/stretchr/testify/assert"
711
)
812

13+
const TempJavaTemplate = `
14+
import java.util.*;
15+
class Solution {
16+
public static void main(String[] args) {
17+
}
18+
}
19+
`
20+
const TempCppTemplate = `
21+
#include <bits/stdc++.h>
22+
using namespace std;
23+
24+
int main() {
25+
ios_base::sync_with_stdio(false);
26+
cin.tie(0);
27+
}
28+
`
29+
930
func TestResolveTemplateByLanguageJava(t *testing.T) {
10-
temp, err := ResolveTemplateByLanguage("java")
11-
assert.NoError(t, err)
12-
assert.Equal(t, JavaTemplate, temp)
31+
testResolveTemplate(t, "java", JavaTemplate)
1332
}
1433

1534
func TestResolveTemplateByLanguagePython(t *testing.T) {
16-
temp, err := ResolveTemplateByLanguage("python")
17-
assert.NoError(t, err)
18-
assert.Equal(t, PythonTemplate, temp)
35+
testResolveTemplate(t, "python", PythonTemplate)
1936
}
2037

2138
func TestResolveTemplateByLanguageCpp(t *testing.T) {
22-
temp, err := ResolveTemplateByLanguage("cpp")
23-
assert.NoError(t, err)
24-
assert.Equal(t, CppTemplate, temp)
39+
testResolveTemplate(t, "cpp", CppTemplate)
2540
}
2641

27-
func TestResolveTemplateByLanguageUnknow(t *testing.T) {
28-
temp, err := ResolveTemplateByLanguage("dummy")
42+
func TestResolveTemplateByLanguageUnknown(t *testing.T) {
43+
//testResolveTemplate(t, "dummy")
44+
configuration := config.Config{
45+
Lang: struct {
46+
Default string `yaml:"default"`
47+
}{Default: "dummy"},
48+
}
49+
temp, err := ResolveTemplateByLanguage(configuration)
2950
assert.Error(t, err)
3051
assert.Empty(t, temp)
3152
}
53+
54+
func TestResolveTemplateByLanguage_CustomTemplateCpp(t *testing.T) {
55+
testResolveFromMap(t, "cpp", CppTemplate)
56+
}
57+
58+
func TestResolveTemplateByLanguage_CustomTemplateJava(t *testing.T) {
59+
testResolveFromMap(t, "java", TempJavaTemplate)
60+
}
61+
62+
func testResolveFromMap(t *testing.T, lang string, expectedTemplate string) {
63+
tempDir := os.TempDir()
64+
filePath := path.Join(tempDir, "config_template.template")
65+
err := ioutil.WriteFile(filePath, []byte(expectedTemplate), 0777)
66+
assert.NoError(t, err)
67+
defer os.Remove(filePath)
68+
69+
configuration := config.Config{
70+
Lang: struct {
71+
Default string `yaml:"default"`
72+
}{Default: lang},
73+
CustomTemplate: map[string]string{lang: filePath},
74+
}
75+
fileTemplate, err := ResolveTemplateByLanguage(configuration)
76+
assert.NoError(t, err)
77+
assert.Equal(t, expectedTemplate, fileTemplate)
78+
}
79+
80+
func testResolveTemplate(t *testing.T, lang, expected string) {
81+
configuration := config.Config{
82+
Lang: struct {
83+
Default string `yaml:"default"`
84+
}{Default: lang},
85+
}
86+
temp, err := ResolveTemplateByLanguage(configuration)
87+
assert.NoError(t, err)
88+
assert.Equal(t, temp, expected)
89+
}

0 commit comments

Comments
 (0)