forked from hashicorp/otto
/
meta.go
209 lines (179 loc) · 5.62 KB
/
meta.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
package command
import (
"bufio"
"flag"
"fmt"
"io"
"os"
"path/filepath"
"github.com/hashicorp/otto/appfile"
"github.com/hashicorp/otto/directory"
"github.com/hashicorp/otto/otto"
"github.com/hashicorp/otto/ui"
"github.com/mitchellh/cli"
"github.com/mitchellh/go-homedir"
)
const (
// DefaultAppfile is the default filename for the Appfile
DefaultAppfile = "Appfile"
// DefaultLocalDataDir is the default path to the local data
// directory.
DefaultLocalDataDir = "~/.otto.d"
DefaultLocalDataDetectorDir = "detect"
// DefaultOutputDir is the default filename for the output directory
DefaultOutputDir = ".otto"
DefaultOutputDirCompiledAppfile = "appfile"
DefaultOutputDirCompiledData = "compiled"
DefaultOutputDirLocalData = "data"
// DefaultDataDir is the default directory for the directory
// data if a directory in the Appfile isn't specified.
DefaultDataDir = "otto-data"
)
// FlagSetFlags is an enum to define what flags are present in the
// default FlagSet returned by Meta.FlagSet
type FlagSetFlags uint
const (
FlagSetNone FlagSetFlags = 0
)
// Meta are the meta-options that are available on all or most commands.
type Meta struct {
CoreConfig *otto.CoreConfig
Ui cli.Ui
}
// Appfile loads the compiled Appfile. If the Appfile isn't compiled yet,
// then an error will be returned.
func (m *Meta) Appfile() (*appfile.Compiled, error) {
// Find the root directory
rootDir, err := m.RootDir()
if err != nil {
return nil, err
}
return appfile.LoadCompiled(filepath.Join(
rootDir, DefaultOutputDir, DefaultOutputDirCompiledAppfile))
}
// Core returns the core for the given Appfile. The file where the
// Appfile was loaded from should be set in appfile.File.Path. This
// root appfile path will be used as the default output directory
// for Otto.
func (m *Meta) Core(f *appfile.Compiled) (*otto.Core, error) {
rootDir, err := m.RootDir()
if err != nil {
return nil, err
}
rootDir, err = filepath.Abs(rootDir)
if err != nil {
return nil, err
}
dataDir, err := m.DataDir()
if err != nil {
return nil, err
}
config := *m.CoreConfig
config.Appfile = f
config.DataDir = dataDir
config.LocalDir = filepath.Join(
rootDir, DefaultOutputDir, DefaultOutputDirLocalData)
config.CompileDir = filepath.Join(
rootDir, DefaultOutputDir, DefaultOutputDirCompiledData)
config.Ui = m.OttoUi()
config.Directory, err = m.Directory(&config)
if err != nil {
return nil, err
}
return otto.NewCore(&config)
}
// DataDir returns the user-local data directory for Otto.
func (m *Meta) DataDir() (string, error) {
return homedir.Expand(DefaultLocalDataDir)
}
// RootDir finds the "root" directory. This is the working directory of
// the Appfile and Otto itself. To find the root directory, we traverse
// upwards until we find the ".otto" directory and assume that is where
// it is.
func (m *Meta) RootDir() (string, error) {
// First, get our current directory
current, err := os.Getwd()
if err != nil {
return "", err
}
// Traverse upwards until we find the directory. We also protect this
// loop with a basic infinite loop guard.
i := 0
prev := ""
for prev != current && i < 1000 {
if _, err := os.Stat(filepath.Join(current, DefaultOutputDir)); err == nil {
// Found it
return current, nil
}
prev = current
current = filepath.Dir(current)
i++
}
return "", fmt.Errorf(
"Otto doesn't appear to have compiled your Appfile yet!\n\n" +
"Run `otto compile` in the directory with the Appfile or\n" +
"with the `-appfile` flag in order to compile the files for\n" +
"developing, building, and deploying your application.\n\n" +
"Once the Appfile is compiled, you can run `otto` in any\n" +
"subdirectory.")
}
// Directory returns the Otto directory backend for the given
// Appfile. If no directory backend is specified, a local folder
// will be used.
func (m *Meta) Directory(config *otto.CoreConfig) (directory.Backend, error) {
return &directory.BoltBackend{
Dir: filepath.Join(config.DataDir, "directory"),
}, nil
}
// FlagSet returns a FlagSet with the common flags that every
// command implements. The exact behavior of FlagSet can be configured
// using the flags as the second parameter.
func (m *Meta) FlagSet(n string, fs FlagSetFlags) *flag.FlagSet {
f := flag.NewFlagSet(n, flag.ContinueOnError)
// Create an io.Writer that writes to our Ui properly for errors.
// This is kind of a hack, but it does the job. Basically: create
// a pipe, use a scanner to break it into lines, and output each line
// to the UI. Do this forever.
errR, errW := io.Pipe()
errScanner := bufio.NewScanner(errR)
go func() {
for errScanner.Scan() {
m.Ui.Error(errScanner.Text())
}
}()
f.SetOutput(errW)
return f
}
// OttoUi returns the ui.Ui object.
func (m *Meta) OttoUi() ui.Ui {
return NewUi(m.Ui)
}
// confirmDestroy is a little helper that will ask the user to confirm a
// destroy action using the provided msg, unless -force is included in args it
// returns true if the destroy should be considered confirmed, and false if
// the destroy should be aborted.
func (m *Meta) confirmDestroy(msg string, args []string) bool {
destroyForce := false
for _, arg := range args {
if arg == "-force" {
destroyForce = true
}
}
if !destroyForce {
v, err := m.OttoUi().Input(&ui.InputOpts{
Id: "destroy",
Query: "Do you really want to destroy?",
Description: fmt.Sprintf("%s\n"+
"There is no undo. Only 'yes' will be accepted to confirm.", msg),
})
if err != nil {
m.Ui.Error(fmt.Sprintf("Error asking for confirmation: %s", err))
return false
}
if v != "yes" {
m.Ui.Output("Destroy cancelled.")
return false
}
}
return true
}