-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathread.go
More file actions
132 lines (118 loc) · 3.25 KB
/
Copy pathread.go
File metadata and controls
132 lines (118 loc) · 3.25 KB
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
package io
import (
"bufio"
"fmt"
"io"
"os"
ex "os/exec"
"strings"
"github.com/ktye/iv/apl"
"github.com/ktye/iv/apl/domain"
"github.com/ktye/iv/apl/scan"
)
// Stdin is exported to be overwritable by tests.
var Stdin io.ReadCloser = os.Stdin
// read reads from a file
func read(a *apl.Apl, _, R apl.Value) (apl.Value, error) {
name, ok := R.(apl.String)
if ok == false {
// If R is 0, it reads from stdin.
if num, ok := R.(apl.Number); ok {
if n, ok := num.ToIndex(); ok && n == 0 {
return apl.LineReader(Stdin), nil
}
}
return nil, fmt.Errorf("io read: expect file name %T", R)
}
f, err := Open(string(name))
if err != nil {
return nil, err
}
return apl.LineReader(f), nil // LineReader closes the file.
}
// exec executes a program and sends the output through a channel.
// If called dyadically it uses R as an input, that can be a channel or a Value.
// If the program starts with a slash, it's location is looked up in the file system.
// TODO: should all arguments starting with a slash be replaced?
func exec(a *apl.Apl, L, R apl.Value) (apl.Value, error) {
r := R
var in io.Reader
if L != nil {
r = L
c, ok := R.(apl.Channel)
if ok {
in = bufio.NewReader(apl.NewChannelReader(a, c))
} else {
in = strings.NewReader(R.String(a.Format))
}
}
v, ok := domain.ToStringArray(nil).To(a, r)
if ok == false {
return nil, fmt.Errorf("io exec: argv must be strings: %T", r)
}
argv := v.(apl.StringArray).Strings
if len(argv) == 0 {
return nil, fmt.Errorf("io exec: argv empty")
}
// If the command starts with a slash, we may relocate it.
if strings.HasPrefix(argv[0], "/") {
fsys, mpt, err := lookup(argv[0])
if err != nil {
return nil, err
}
if f, ok := fsys.(fs); ok == false {
return nil, fmt.Errorf("exec: %s: file system is not an os fs: %s", argv[0], fsys.String())
} else {
relpath := strings.TrimPrefix(argv[0], mpt)
argv[0] = f.path(relpath)
}
}
cmd := ex.Command(argv[0], argv[1:]...)
cmd.Stdin = in
out, err := cmd.StdoutPipe()
if err != nil {
return nil, err
}
c := apl.LineReader(out)
if err := cmd.Start(); err != nil {
return nil, err
}
return c, nil
}
// Load reads the file R and executes it.
// It returns an error, if R is not a file.
// If L is given, the file is executed in a new environment and the resulting variables
// are copied to a package with the name of L.
func load(a *apl.Apl, L, R apl.Value) (apl.Value, error) {
s, ok := R.(apl.String)
if ok == false {
return nil, fmt.Errorf("io l: argument must be a file name: %T", R)
}
f, err := Open(string(s))
if err != nil {
return nil, err
}
defer f.Close()
if L == nil {
if err := a.EvalFile(f, string(s)); err != nil {
return nil, err
}
return apl.EmptyArray{}, nil
}
l, ok := L.(apl.String)
if ok == false {
return nil, fmt.Errorf("io l: left argument must be a package name: %T", L)
}
if err := a.LoadPkg(f, string(s), string(l)); err != nil {
return nil, err
}
return apl.EmptyArray{}, nil
}
func lCmd(t []scan.Token) []scan.Token {
// /l `/file.apl `pkg
if len(t) == 2 && t[0].T == scan.String && t[1].T == scan.String {
return []scan.Token{t[1], scan.Token{T: scan.Identifier, S: "io→l"}, t[0]}
}
l := scan.Token{T: scan.Identifier, S: "io→l"}
return append([]scan.Token{l}, t...)
}