Skip to content

Commit c0036d5

Browse files
Merge pull request #15 from RasmusLindroth/include
Use random port and parse includes
2 parents 3ee33f3 + 7a05435 commit c0036d5

File tree

7 files changed

+247
-15
lines changed

7 files changed

+247
-15
lines changed

README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,14 @@ go install
5959

6060
Example usage
6161
```
62+
//Run web interface with a random port
63+
i3keys web
64+
6265
//Run web interface on port 8080
6366
i3keys web 8080
6467
6568
//sway usage
69+
i3keys -s web
6670
i3keys -s web 8080
6771
6872
//or output text to the terminal
@@ -135,8 +139,8 @@ Usage:
135139
136140
The commands are:
137141
138-
web <port>
139-
start the web ui and listen on <port>
142+
web [port]
143+
start the web ui and listen on random port or [port]
140144
141145
text <layout> [mods]
142146
output available keybindings in the terminal
@@ -157,7 +161,6 @@ Arguments:
157161
158162
[dest]
159163
is optional. Where to output files, defaults to the current directory
160-
161164
```
162165

163166
### Disclaimer

i3keys.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@ import (
1010
"github.com/RasmusLindroth/i3keys/internal/web"
1111
)
1212

13-
const version string = "0.0.9"
13+
const version string = "0.0.10"
1414

1515
func helpText(exitCode int) {
1616
fmt.Print("Usage:\n\n\ti3keys [-s] <command> [arguments]\n")
1717
fmt.Print("\tAdd the flag -s for sway\n\n")
1818
fmt.Print("The commands are:\n\n")
19-
fmt.Print("\tweb <port>\n\t\tstart the web ui and listen on <port>\n\n")
19+
fmt.Print("\tweb [port]\n\t\tstart the web ui and listen on random port or [port]\n\n")
2020
fmt.Print("\ttext <layout> [mods]\n\t\toutput available keybindings in the terminal\n\n")
2121
fmt.Print("\tsvg <layout> [dest] [mods]\n\t\toutputs one SVG file for each modifier group\n\n")
2222
fmt.Print("\tversion\n\t\tprint i3keys version\n\n")
@@ -40,9 +40,9 @@ func main() {
4040
}
4141
cmd := os.Args[sIndex]
4242

43-
if cmd == "web" && len(os.Args) < 2+sIndex {
44-
fmt.Println("You need to set the <port>")
45-
os.Exit(2)
43+
port := "-1"
44+
if cmd == "web" && len(os.Args) >= 2+sIndex {
45+
port = os.Args[1+sIndex]
4646
}
4747

4848
layoutCheck := len(os.Args) > 1+sIndex && (strings.ToUpper(os.Args[1+sIndex]) != "ISO" && strings.ToUpper(os.Args[1+sIndex]) != "ANSI")
@@ -60,7 +60,7 @@ func main() {
6060

6161
switch cmd {
6262
case "web":
63-
web.Output(sway, os.Args[1+sIndex])
63+
web.Output(sway, port)
6464
case "text":
6565
if len(os.Args) < 3+sIndex {
6666
text.Output(sway, os.Args[1+sIndex], "")

internal/helpers/include.go

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
package helpers
2+
3+
import (
4+
"log"
5+
"os"
6+
"os/exec"
7+
"path/filepath"
8+
"strings"
9+
)
10+
11+
//Equal to os/exec Expand but changed ${} to $()
12+
func replaceDollar(s string, mapping func(string) string) string {
13+
var buf []byte
14+
// $() is all ASCII, so bytes are fine for this operation.
15+
i := 0
16+
for j := 0; j < len(s); j++ {
17+
if s[j] == '$' && j+1 < len(s) {
18+
if buf == nil {
19+
buf = make([]byte, 0, 2*len(s))
20+
}
21+
buf = append(buf, s[i:j]...)
22+
name, w := getShellName(s[j+1:])
23+
if name == "" && w > 0 {
24+
// Encountered invalid syntax; eat the
25+
// characters.
26+
} else if name == "" {
27+
// Valid syntax, but $ was not followed by a
28+
// name. Leave the dollar character untouched.
29+
buf = append(buf, s[j])
30+
} else {
31+
buf = append(buf, mapping(name)...)
32+
}
33+
j += w
34+
i = j + 1
35+
}
36+
}
37+
if buf == nil {
38+
return s
39+
}
40+
return string(buf) + s[i:]
41+
}
42+
43+
//Equal to os/exec Expand but changed ${} to $()
44+
func getShellName(s string) (string, int) {
45+
switch {
46+
case s[0] == '(':
47+
if len(s) > 2 && isShellSpecialVar(s[1]) && s[2] == ')' {
48+
return s[1:2], 3
49+
}
50+
// Scan to closing brace
51+
for i := 1; i < len(s); i++ {
52+
if s[i] == ')' {
53+
if i == 1 {
54+
return "", 2 // Bad syntax; eat "$()""
55+
}
56+
return s[1:i], i + 1
57+
}
58+
}
59+
return "", 1 // Bad syntax; eat "$("
60+
case isShellSpecialVar(s[0]):
61+
return s[0:1], 1
62+
}
63+
// Scan alphanumerics.
64+
var i int
65+
for i = 0; i < len(s) && isAlphaNum(s[i]); i++ {
66+
}
67+
return s[:i], i
68+
}
69+
70+
//Equal to os/exec Expand but changed ${} to $()
71+
func isShellSpecialVar(c uint8) bool {
72+
switch c {
73+
case '*', '#', '$', '@', '!', '?', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
74+
return true
75+
}
76+
return false
77+
}
78+
79+
//Equal to os/exec Expand but changed ${} to $()
80+
func isAlphaNum(c uint8) bool {
81+
return c == '_' || '0' <= c && c <= '9' || 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z'
82+
}
83+
84+
func runCMD(program string) string {
85+
a := strings.Split(program, " ")
86+
out, err := exec.Command(a[0], a[1:]...).Output()
87+
if err != nil {
88+
log.Printf("couldn't expand the following command in include: %s\n", program)
89+
return ""
90+
}
91+
return strings.TrimSuffix(string(out), "\n")
92+
}
93+
94+
func replaceAccent(s string, mapping func(string) string) string {
95+
r := ""
96+
for i := 0; i < len(s); i++ {
97+
if s[i] == '`' && (i > 0 || s[i-1] != '\\') && i+1 < len(s) {
98+
buf := ""
99+
closed := false
100+
for j := i + 1; j < len(s) && !closed; j++ {
101+
if s[j] != '`' {
102+
buf += string(s[j])
103+
} else {
104+
closed = true
105+
i = j
106+
}
107+
}
108+
if closed {
109+
r += mapping(buf)
110+
}
111+
} else {
112+
r += string(s[i])
113+
}
114+
}
115+
return r
116+
}
117+
118+
func ExpandCommand(s string) string {
119+
s = replaceDollar(s, runCMD)
120+
s = replaceAccent(s, runCMD)
121+
return s
122+
}
123+
124+
func checkPath(s string) []string {
125+
var r []string
126+
info, err := os.Stat(s)
127+
if err != nil {
128+
return []string{}
129+
}
130+
if !info.IsDir() {
131+
cp := filepath.Clean(s)
132+
if cp[0] == '~' {
133+
home, _ := os.LookupEnv("HOME")
134+
cp = home + cp[1:]
135+
}
136+
r = append(r, cp)
137+
}
138+
return r
139+
}
140+
141+
func GetPaths(s string) ([]string, error) {
142+
s = ExpandCommand(s)
143+
matches, err := filepath.Glob(s)
144+
if err != nil {
145+
return nil, err
146+
}
147+
var paths []string
148+
for _, m := range matches {
149+
paths = append(paths, checkPath(m)...)
150+
}
151+
return paths, nil
152+
}

internal/i3parse/parse.go

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"io"
77
"log"
8+
"os"
89
"sort"
910
"strings"
1011

@@ -38,6 +39,10 @@ func getLineType(parts []string, c context) lineType {
3839
if validateVariable(parts) {
3940
return variableLine
4041
}
42+
case "include":
43+
if validateInclude(parts) {
44+
return includeLine
45+
}
4146
case "bindsym":
4247
if validateBinding(parts) && parts[len(parts)-1] != "{" {
4348
return bindSymLine
@@ -103,16 +108,17 @@ func readLine(reader *bufio.Reader, c context) (string, []string, lineType, erro
103108
return line, lineParts, lineType, err
104109
}
105110

106-
func parse(confReader io.Reader, err error) ([]Mode, []Binding, error) {
111+
func parseConfig(confReader io.Reader, err error) ([]Mode, []Binding, []Variable, []string, error) {
107112
if err != nil {
108-
return []Mode{}, []Binding{}, errors.New("Couldn't get the config file")
113+
return []Mode{}, []Binding{}, []Variable{}, []string{}, errors.New("Couldn't get the config file")
109114
}
110115

111116
reader := bufio.NewReader(confReader)
112117

113118
var modes []Mode
114119
var bindings []Binding
115120
var variables []Variable
121+
var includes []string
116122

117123
context := mainContext
118124
var readErr error
@@ -124,18 +130,23 @@ func parse(confReader io.Reader, err error) ([]Mode, []Binding, error) {
124130
line, lineParts, lineType, readErr = readLine(reader, context)
125131

126132
if readErr != nil && readErr != io.EOF {
127-
return []Mode{}, []Binding{}, readErr
133+
return []Mode{}, []Binding{}, []Variable{}, []string{}, readErr
128134
}
129135

130136
switch lineType {
131137
case skipLine:
132138
continue
133139
case variableLine:
134140
variables = append(variables, parseVariable(lineParts))
141+
continue
135142
case modeLine:
136143
context = modeContext
137144
name := parseMode(line)
138145
modes = append(modes, Mode{Name: name})
146+
continue
147+
case includeLine:
148+
includes = append(includes, strings.Join(lineParts[1:], " "))
149+
continue
139150
case bindCodeBracket:
140151
if context == mainContext {
141152
context = bindCodeMainContext
@@ -185,6 +196,50 @@ func parse(confReader io.Reader, err error) ([]Mode, []Binding, error) {
185196
}
186197
}
187198

199+
var includePaths []string
200+
for _, incl := range includes {
201+
matches, err := helpers.GetPaths(incl)
202+
if err != nil {
203+
log.Printf("couldn't parse the following include \"%s\" got error %v", incl, err)
204+
continue
205+
}
206+
includePaths = append(includePaths, matches...)
207+
}
208+
209+
return modes, bindings, variables, includePaths, nil
210+
}
211+
212+
func parse(confReader io.Reader, err error) ([]Mode, []Binding, error) {
213+
modes, bindings, variables, includes, err := parseConfig(confReader, err)
214+
if err != nil {
215+
return []Mode{}, []Binding{}, errors.New("Couldn't get the config file")
216+
}
217+
var parsedIncludes []string
218+
for _, incl := range includes {
219+
done := false
220+
for _, ap := range parsedIncludes {
221+
if ap == incl {
222+
done = true
223+
}
224+
}
225+
if done {
226+
continue
227+
}
228+
f, ferr := os.Open(incl)
229+
if err != nil {
230+
log.Printf("couldn't open the included file %s, got err: %v\n", incl, ferr)
231+
}
232+
m, b, v, i, perr := parseConfig(f, err)
233+
if err != nil {
234+
log.Printf("couldn't parse the included file %s, got err: %v\n", incl, perr)
235+
}
236+
modes = append(modes, m...)
237+
bindings = append(bindings, b...)
238+
variables = append(variables, v...)
239+
includes = append(includes, i...)
240+
parsedIncludes = append(parsedIncludes, incl)
241+
}
242+
188243
bindings, modes = replaceVariables(variables, bindings, modes)
189244

190245
for key := range modes {

internal/i3parse/types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ type context uint
66
const (
77
skipLine lineType = iota
88
variableLine
9+
includeLine
910
bindCodeLine
1011
bindCodeBracket
1112
unBindCodeLine

internal/i3parse/validate.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,13 @@ func validateVariable(parts []string) bool {
44
if len(parts) < 2 || parts[1][0] != '$' {
55
return false
66
}
7-
87
return true
98
}
109

1110
func validateBinding(parts []string) bool {
1211
if len(parts) < 3 {
1312
return false
1413
}
15-
1614
return true
1715
}
1816

@@ -21,6 +19,12 @@ func validateMode(parts []string) bool {
2119
(parts[1] == "--pango_markup" || parts[1][0] == '"') {
2220
return true
2321
}
24-
2522
return false
2623
}
24+
25+
func validateInclude(parts []string) bool {
26+
if len(parts) < 2 {
27+
return false
28+
}
29+
return true
30+
}

0 commit comments

Comments
 (0)