-
Notifications
You must be signed in to change notification settings - Fork 16
/
main.go
233 lines (227 loc) · 7.57 KB
/
main.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
package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"strings"
"gopkg.in/yaml.v3"
"github.com/vsariola/sointu"
"github.com/vsariola/sointu/version"
"github.com/vsariola/sointu/vm/compiler"
)
func filterExtensions(input map[string]string, extensions []string) map[string]string {
ret := map[string]string{}
for _, ext := range extensions {
extWithDot := "." + ext
if inputVal, ok := input[extWithDot]; ok {
ret[extWithDot] = inputVal
}
}
return ret
}
func main() {
safe := flag.Bool("n", false, "Never overwrite files; if file already exists and would be overwritten, give an error.")
list := flag.Bool("l", false, "Do not write files; just list files that would change instead.")
stdout := flag.Bool("s", false, "Do not write files; write to standard output instead.")
help := flag.Bool("h", false, "Show help.")
rowsync := flag.Bool("r", false, "Write the current fractional row as sync #0")
library := flag.Bool("a", false, "Compile Sointu into a library. Input files are not needed.")
jsonOut := flag.Bool("j", false, "Output the song as .json file instead of compiling.")
yamlOut := flag.Bool("y", false, "Output the song as .yml file instead of compiling.")
tmplDir := flag.String("t", "", "When compiling, use the templates in this directory instead of the standard templates.")
outPath := flag.String("o", "", "Directory or filename where to write compiled code. Extension is ignored. Directory and its parents are created if needed. By default, everything is placed in the same directory where the original song file is.")
extensionsOut := flag.String("e", "", "Output only the compiled files with these comma separated extensions. For example: h,asm")
targetArch := flag.String("arch", runtime.GOARCH, "Target architecture. Defaults to OS architecture. Possible values: 386, amd64, wasm")
output16bit := flag.Bool("i", false, "Compiled song should output 16-bit integers, instead of floats.")
targetOs := flag.String("os", runtime.GOOS, "Target OS. Defaults to current OS. Possible values: windows, darwin, linux. Anything else is assumed linuxy. Ignored when targeting wasm.")
versionFlag := flag.Bool("v", false, "Print version.")
flag.Usage = printUsage
flag.Parse()
if *versionFlag {
fmt.Println(version.VersionOrHash)
os.Exit(0)
}
if (flag.NArg() == 0 && !*library) || *help {
flag.Usage()
os.Exit(0)
}
compile := !*jsonOut && !*yamlOut // if the user gives nothing to output, then the default behaviour is to compile the file
var comp *compiler.Compiler
if compile || *library {
var err error
if *tmplDir != "" {
comp, err = compiler.NewFromTemplates(*targetOs, *targetArch, *output16bit, *rowsync, *tmplDir)
} else {
comp, err = compiler.New(*targetOs, *targetArch, *output16bit, *rowsync)
}
if err != nil {
fmt.Fprintf(os.Stderr, `error creating compiler: %v`, err)
os.Exit(1)
}
}
output := func(filename string, extension string, contents []byte) error {
if *stdout {
fmt.Print(string(contents))
return nil
}
_, name := filepath.Split(filename)
var dir string
if *outPath != "" {
// check if it's an already existing directory and the user just forgot trailing slash
if info, err := os.Stat(*outPath); err == nil && info.IsDir() {
dir = *outPath
} else {
outdir, outname := filepath.Split(*outPath)
if outdir != "" {
dir = outdir
}
if outname != "" {
name = outname
}
}
}
if dir == "" {
var err error
dir, err = os.Getwd()
if err != nil {
return fmt.Errorf("could not get working directory, specify the output directory explicitly: %v", err)
}
}
name = strings.TrimSuffix(name, filepath.Ext(name)) + extension
f := filepath.Join(dir, name)
original, err := ioutil.ReadFile(f)
if err == nil {
if bytes.Compare(original, contents) == 0 {
return nil // no need to update
}
if !*list && *safe {
return fmt.Errorf("file %v would be overwritten by compiler", f)
}
}
if *list {
fmt.Println(f)
} else {
if dir != "" {
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
return fmt.Errorf("could not create output directory %v: %v", dir, err)
}
}
err := ioutil.WriteFile(f, contents, 0644)
if err != nil {
return fmt.Errorf("could not write file %v: %v", f, err)
}
}
return nil
}
process := func(filename string) error {
inputBytes, err := ioutil.ReadFile(filename)
if err != nil {
return fmt.Errorf("could not read file %v: %v", filename, err)
}
var song sointu.Song
if errJSON := json.Unmarshal(inputBytes, &song); errJSON != nil {
if errYaml := yaml.Unmarshal(inputBytes, &song); errYaml != nil {
return fmt.Errorf("song could not be unmarshaled as a .json (%v) or .yml (%v)", errJSON, errYaml)
}
}
if song.RowsPerBeat == 0 {
song.RowsPerBeat = 4
}
if song.Score.Length == 0 {
song.Score.Length = len(song.Score.Tracks[0].Patterns)
}
var compiledPlayer map[string]string
if compile {
var err error
compiledPlayer, err = comp.Song(&song)
if err != nil {
return fmt.Errorf("compiling player failed: %v", err)
}
if len(*extensionsOut) > 0 {
compiledPlayer = filterExtensions(compiledPlayer, strings.Split(*extensionsOut, ","))
}
for extension, code := range compiledPlayer {
if err := output(filename, extension, []byte(code)); err != nil {
return fmt.Errorf("error outputting %v file: %v", extension, err)
}
}
}
if *jsonOut {
jsonSong, err := json.Marshal(song)
if err != nil {
return fmt.Errorf("could not marshal the song as json file: %v", err)
}
if err := output(filename, ".json", jsonSong); err != nil {
return fmt.Errorf("error outputting json file: %v", err)
}
}
if *yamlOut {
yamlSong, err := yaml.Marshal(song)
if err != nil {
return fmt.Errorf("could not marshal the song as yaml file: %v", err)
}
if err := output(filename, ".yml", yamlSong); err != nil {
return fmt.Errorf("error outputting yaml file: %v", err)
}
}
return nil
}
retval := 0
if *library {
compiledLibrary, err := comp.Library()
if err != nil {
fmt.Fprintf(os.Stderr, "compiling library failed: %v\n", err)
retval = 1
} else {
if len(*extensionsOut) > 0 {
compiledLibrary = filterExtensions(compiledLibrary, strings.Split(*extensionsOut, ","))
}
for extension, code := range compiledLibrary {
if err := output("sointu", extension, []byte(code)); err != nil {
fmt.Fprintf(os.Stderr, "error outputting %v file: %v", extension, err)
retval = 1
}
}
}
}
for _, param := range flag.Args() {
if info, err := os.Stat(param); err == nil && info.IsDir() {
jsonfiles, err := filepath.Glob(filepath.Join(param, "*.json"))
if err != nil {
fmt.Fprintf(os.Stderr, "could not glob the path %v for json files: %v\n", param, err)
retval = 1
continue
}
ymlfiles, err := filepath.Glob(filepath.Join(param, "*.yml"))
if err != nil {
fmt.Fprintf(os.Stderr, "could not glob the path %v for yml files: %v\n", param, err)
retval = 1
continue
}
files := append(ymlfiles, jsonfiles...)
for _, file := range files {
err := process(file)
if err != nil {
fmt.Fprintf(os.Stderr, "could not process file %v: %v\n", file, err)
retval = 1
}
}
} else {
err := process(param)
if err != nil {
fmt.Fprintf(os.Stderr, "could not process file %v: %v\n", param, err)
retval = 1
}
}
}
os.Exit(retval)
}
func printUsage() {
fmt.Fprintf(os.Stderr, "Sointu compiler. Input .yml or .json songs, outputs compiled songs (e.g. .asm and .h files).\nUsage: %s [flags] [path ...]\n", os.Args[0])
flag.PrintDefaults()
}