/
lang.go
249 lines (226 loc) · 8.55 KB
/
lang.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
package core
import (
"errors"
"os"
"path"
"path/filepath"
"sort"
"github.com/BurntSushi/toml"
"github.com/jauhararifin/cptool/internal/logger"
"github.com/spf13/afero"
)
// Language defines programming language used for competitive programming like c, c++, java, etc. VerboseName contains string
// that will displayed to the user. Name contains string that unique for every language, this identifies the language. Extension
// is the extension of language, every solution that written using this language must have same extension as this (like .pas for
// Pascal language).
//
// Every programming language must have compile script and run script, these script contains how to compile and run the solution.
// Bash script can be used to compile the solution and run the compiled solution. CompileScript contains path to the compile script
// location. RunScript contains path to the script for running the compiled solution.
// Some programming languages are debuggable. When the language is debuggable, it Debuggable property will set to true and
// DebugScript contains path to compile script in debug mode.
//
// In CPTool, A language is defined using a folder, the folder name identifies the language name. Below are example of folder structure
// of C language
//
// c/
// ├── compile
// ├── debugcompile
// ├── lang.conf
// └── run
//
// compile file contains script for compiling program. When the script executed, it will receive two arguments. The first one is the
// path to the solution's source code and the second one is the path where the compiled program should be located. Below is the example
// of compile script for c language
//
// #!/bin/bash
// SOURCE=$1
// DEST=$2
// gcc -x c -Wall -O2 -static -pipe -o "$DEST" "$SOURCE" -lm
// exit $?
//
// File debugcompile contains script for compiling program in debug mode. It receive the same arguments as compile script when executed
// Below is the example of compile script for c language in debug mode.
//
// #!/bin/bash
// SOURCE=$1
// DEST=$2
// gcc -x c -Wall -O2 -static -pipe -o "$DEST" "$SOURCE" -lm -g
// exit $?
//
// File lang.conf contains other information about language (VerboseName and Extension). Below the example for c language:
//
// verbose_name=C
// extension=c
//
// File run contains script for executing compiled program. When the script is executes, it will receive one argument that defines the
// location of compiled program. Below is example for c language:
//
// #!/bin/bash
// PROGRAM=$1
// ./$PROGRAM
// exit $?
//
type Language struct {
Name string
Extension string
VerboseName string
CompileScript string
RunScript string
DebugScript string
Debuggable bool
}
// ErrInvalidLanguageDirectory indicates directory is not a valid language definition.
var ErrInvalidLanguageDirectory = errors.New("Invalid language directory")
// ErrInvalidLanguageConfigurationFile indicates language configuration file has invalid format.
var ErrInvalidLanguageConfigurationFile = errors.New("Invalid language configuration file")
// ErrNoSuchLanguage indicates no language found.
var ErrNoSuchLanguage = errors.New("No such language")
// GetAllLanguages returns all known language as a pair of []Language, map[string]Language. The first element
// of pair contains array of all known languages. The second element of pair contains map of Language of string
// that map between language's name and itself.
func (cptool *CPTool) GetAllLanguages() ([]Language, map[string]Language) {
languages := make([]Language, 0)
for _, value := range cptool.languages {
languages = append(languages, value)
}
sort.SliceStable(languages, func(i, j int) bool {
return languages[i].Name < languages[j].Name
})
return languages, cptool.languages
}
// GetLanguageByName returns language that has specific name. ErrNoSuchLanguage will returned when no such language exists.
func (cptool *CPTool) GetLanguageByName(name string) (Language, error) {
if lang, ok := cptool.languages[name]; ok {
return lang, nil
}
return Language{}, ErrNoSuchLanguage
}
// GetDefaultLanguage returns default language. The default language is defined in "config" file in some of configuration path.
// Can be "/etc/cptool/config", "~/.cptool/config", "$CPTOOL_HOME/config" or ".cptool/config" in your current working directory.
// Below is example of config file that defines default language as C Plus Plus ("cpp" is the language's name, while "C Plus Plus"
// is the language verbose name).
//
// default_language=cpp
//
// When there is no valid config file, the default language is choosen between all known languages. When there is no known language,
// then ErrNoSuchLanguage error returned.
func (cptool *CPTool) GetDefaultLanguage() (Language, error) {
configurationPaths := cptool.GetConfigurationPaths()
for _, confPath := range configurationPaths {
userConfigPath := path.Join(confPath, "config")
if cptool.logger != nil {
cptool.logger.Println(logger.VERBOSE, "Searching default language config in: ", userConfigPath)
}
info, err := cptool.fs.Stat(userConfigPath)
if err != nil || info.IsDir() {
if cptool.logger != nil {
cptool.logger.Println(logger.VERBOSE, "Language config doesn't found in: ", userConfigPath)
}
continue
}
if userConfigFile, err := cptool.fs.Open(userConfigPath); err == nil {
result := &struct {
DefaultLanguage string `toml:"default_language"`
}{}
if _, err = toml.DecodeReader(userConfigFile, &result); err == nil {
if len(result.DefaultLanguage) > 0 {
if defaultLanguage, err := cptool.GetLanguageByName(result.DefaultLanguage); err == nil {
if cptool.logger != nil {
cptool.logger.Println(logger.VERBOSE, "Use default language: ", defaultLanguage.Name)
}
return defaultLanguage, nil
}
}
}
}
}
languages, _ := cptool.GetAllLanguages()
if len(languages) == 0 {
return Language{}, ErrNoSuchLanguage
}
return languages[0], nil
}
type languageConfFile struct {
VerboseName string `toml:"verbose_name"`
Extension string `toml:"extension"`
}
func (cptool *CPTool) getLanguagesPaths() []string {
configurationPaths := cptool.GetConfigurationPaths()
langPaths := make([]string, 0)
for _, confPath := range configurationPaths {
langPaths = append(langPaths, path.Join(confPath, "langs"))
}
return langPaths
}
func (cptool *CPTool) getLanguageFromDirectory(languagePath string) (Language, error) {
info, err := cptool.fs.Stat(languagePath)
if err != nil || !info.IsDir() {
return Language{}, ErrInvalidLanguageDirectory
}
language := Language{}
language.Name = info.Name()
language.VerboseName = language.Name
language.Extension = language.Name
configPath := path.Join(languagePath, "lang.conf")
info, err = cptool.fs.Stat(configPath)
if err == nil && !info.IsDir() {
configFile, _ := cptool.fs.Open(configPath)
languageConf := languageConfFile{}
if _, err = toml.DecodeReader(configFile, &languageConf); err != nil {
return Language{}, ErrInvalidLanguageConfigurationFile
}
if len(languageConf.VerboseName) > 0 {
language.VerboseName = languageConf.VerboseName
}
if len(languageConf.Extension) > 0 {
language.Extension = languageConf.Extension
}
}
language.CompileScript = path.Join(languagePath, "compile")
info, err = cptool.fs.Stat(language.CompileScript)
if err != nil {
return Language{}, err
}
if info.IsDir() {
return Language{}, ErrInvalidLanguageDirectory
}
language.RunScript = path.Join(languagePath, "run")
info, err = cptool.fs.Stat(language.RunScript)
if err != nil {
return Language{}, err
}
if info.IsDir() {
return Language{}, ErrInvalidLanguageDirectory
}
DebugScript := path.Join(languagePath, "debugcompile")
if info, err = cptool.fs.Stat(DebugScript); err == nil && !info.IsDir() {
language.Debuggable = true
language.DebugScript = DebugScript
}
return language, nil
}
func (cptool *CPTool) loadAllLanguages() {
langPaths := cptool.getLanguagesPaths()
for _, path := range langPaths {
info, err := cptool.fs.Stat(path)
if err != nil || !info.IsDir() {
continue
}
afero.Walk(cptool.fs, path, func(langPath string, info os.FileInfo, err error) error {
if info.IsDir() && langPath != path {
if lang, err := cptool.getLanguageFromDirectory(langPath); err == nil {
if _, ok := cptool.languages[lang.Name]; ok {
return filepath.SkipDir
}
cptool.languages[lang.Name] = lang
if cptool.logger != nil {
cptool.logger.Printf(logger.VERBOSE, "Found language: %s, in: %s\n", lang.Name, langPath)
}
}
return filepath.SkipDir
}
return nil
})
}
}