File: program.go

package info (click to toggle)
elvish 0.12%2Bds1-2
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 2,532 kB
  • sloc: python: 108; makefile: 94; sh: 72; xml: 9
file content (160 lines) | stat: -rw-r--r-- 4,473 bytes parent folder | download
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
// Package program provides the entry point to Elvish. Its subpackages
// correspond to subprograms of Elvish.
package program

// This package sets up the basic environment and calls the appropriate
// "subprogram", one of the daemon, the terminal interface, or the web
// interface.

import (
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"runtime/pprof"
	"strconv"

	"github.com/elves/elvish/program/daemon"
	"github.com/elves/elvish/program/shell"
	"github.com/elves/elvish/program/web"
	"github.com/elves/elvish/util"
)

// defaultPort is the default port on which the web interface runs. The number
// is chosen because it resembles "elvi".
const defaultWebPort = 3171

var logger = util.GetLogger("[main] ")

type flagSet struct {
	flag.FlagSet

	Log, LogPrefix, CPUProfile string

	Help, Version, BuildInfo, JSON bool

	CodeInArg, CompileOnly, NoRc bool

	Web  bool
	Port int

	Daemon bool
	Forked int

	Bin, DB, Sock string
}

func newFlagSet() *flagSet {
	f := flagSet{}
	f.Init("elvish", flag.ContinueOnError)
	f.Usage = func() {
		usage(os.Stderr, &f)
	}

	f.StringVar(&f.Log, "log", "", "a file to write debug log to")
	f.StringVar(&f.LogPrefix, "logprefix", "", "the prefix for the daemon log file")
	f.StringVar(&f.CPUProfile, "cpuprofile", "", "write cpu profile to file")

	f.BoolVar(&f.Help, "help", false, "show usage help and quit")
	f.BoolVar(&f.Version, "version", false, "show version and quit")
	f.BoolVar(&f.BuildInfo, "buildinfo", false, "show build info and quit")
	f.BoolVar(&f.JSON, "json", false, "show output in JSON. Useful with -buildinfo.")

	f.BoolVar(&f.CodeInArg, "c", false, "take first argument as code to execute")
	f.BoolVar(&f.CompileOnly, "compileonly", false, "Parse/Compile but do not execute")
	f.BoolVar(&f.NoRc, "norc", false, "run elvish without invoking rc.elv")

	f.BoolVar(&f.Web, "web", false, "run backend of web interface")
	f.IntVar(&f.Port, "port", defaultWebPort, "the port of the web backend")

	f.BoolVar(&f.Daemon, "daemon", false, "run daemon instead of shell")

	f.StringVar(&f.Bin, "bin", "", "path to the elvish binary")
	f.StringVar(&f.DB, "db", "", "path to the database")
	f.StringVar(&f.Sock, "sock", "", "path to the daemon socket")

	return &f
}

// usage prints usage to the specified output. It modifies the flagSet; there is
// no API for getting the current output of a flag.FlagSet, so we can neither
// use the current output of f to output our own usage string, nor restore the
// previous value of f's output.
func usage(out io.Writer, f *flagSet) {
	f.SetOutput(out)
	fmt.Fprintln(out, "Usage: elvish [flags] [script]")
	fmt.Fprintln(out, "Supported flags:")
	f.PrintDefaults()
}

func Main(allArgs []string) int {
	flag := newFlagSet()
	err := flag.Parse(allArgs[1:])
	if err != nil {
		// Error and usage messages are already shown.
		return 2
	}

	// Handle flags common to all subprograms.

	if flag.CPUProfile != "" {
		f, err := os.Create(flag.CPUProfile)
		if err != nil {
			log.Fatal(err)
		}
		pprof.StartCPUProfile(f)
		defer pprof.StopCPUProfile()
	}

	if flag.Log != "" {
		err = util.SetOutputFile(flag.Log)
	} else if flag.LogPrefix != "" {
		err = util.SetOutputFile(flag.LogPrefix + strconv.Itoa(os.Getpid()))
	}
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
	}

	return FindProgram(flag).Main(flag.Args())
}

// Program represents a subprogram.
type Program interface {
	// Main calls the subprogram with arguments. The return value will be used
	// as the exit status of the entire program.
	Main(args []string) int
}

// FindProgram finds a suitable Program according to flags. It does not have any
// side effects.
func FindProgram(flag *flagSet) Program {
	switch {
	case flag.Help:
		return ShowHelp{flag}
	case flag.Version:
		return ShowVersion{}
	case flag.BuildInfo:
		return ShowBuildInfo{flag.JSON}
	case flag.Daemon:
		if len(flag.Args()) > 0 {
			return ShowCorrectUsage{"arguments are not allowed with -daemon", flag}
		}
		return Daemon{inner: &daemon.Daemon{
			BinPath:       flag.Bin,
			DbPath:        flag.DB,
			SockPath:      flag.Sock,
			LogPathPrefix: flag.LogPrefix,
		}}
	case flag.Web:
		if len(flag.Args()) > 0 {
			return ShowCorrectUsage{"arguments are not allowed with -web", flag}
		}
		if flag.CodeInArg {
			return ShowCorrectUsage{"-c cannot be used together with -web", flag}
		}
		return web.New(flag.Bin, flag.Sock, flag.DB, flag.Port)
	default:
		return shell.New(flag.Bin, flag.Sock, flag.DB, flag.CodeInArg, flag.CompileOnly, flag.NoRc)
	}
}