Skip to content

Commit

Permalink
Merge pull request #15 from RasmusLindroth/include
Browse files Browse the repository at this point in the history
Use random port and parse includes
  • Loading branch information
RasmusLindroth authored Feb 23, 2020
2 parents 3ee33f3 + 7a05435 commit c0036d5
Show file tree
Hide file tree
Showing 7 changed files with 247 additions and 15 deletions.
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,14 @@ go install

Example usage
```
//Run web interface with a random port
i3keys web
//Run web interface on port 8080
i3keys web 8080
//sway usage
i3keys -s web
i3keys -s web 8080
//or output text to the terminal
Expand Down Expand Up @@ -135,8 +139,8 @@ Usage:
The commands are:
web <port>
start the web ui and listen on <port>
web [port]
start the web ui and listen on random port or [port]
text <layout> [mods]
output available keybindings in the terminal
Expand All @@ -157,7 +161,6 @@ Arguments:
[dest]
is optional. Where to output files, defaults to the current directory
```

### Disclaimer
Expand Down
12 changes: 6 additions & 6 deletions i3keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ import (
"github.com/RasmusLindroth/i3keys/internal/web"
)

const version string = "0.0.9"
const version string = "0.0.10"

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

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

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

switch cmd {
case "web":
web.Output(sway, os.Args[1+sIndex])
web.Output(sway, port)
case "text":
if len(os.Args) < 3+sIndex {
text.Output(sway, os.Args[1+sIndex], "")
Expand Down
152 changes: 152 additions & 0 deletions internal/helpers/include.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package helpers

import (
"log"
"os"
"os/exec"
"path/filepath"
"strings"
)

//Equal to os/exec Expand but changed ${} to $()
func replaceDollar(s string, mapping func(string) string) string {
var buf []byte
// $() is all ASCII, so bytes are fine for this operation.
i := 0
for j := 0; j < len(s); j++ {
if s[j] == '$' && j+1 < len(s) {
if buf == nil {
buf = make([]byte, 0, 2*len(s))
}
buf = append(buf, s[i:j]...)
name, w := getShellName(s[j+1:])
if name == "" && w > 0 {
// Encountered invalid syntax; eat the
// characters.
} else if name == "" {
// Valid syntax, but $ was not followed by a
// name. Leave the dollar character untouched.
buf = append(buf, s[j])
} else {
buf = append(buf, mapping(name)...)
}
j += w
i = j + 1
}
}
if buf == nil {
return s
}
return string(buf) + s[i:]
}

//Equal to os/exec Expand but changed ${} to $()
func getShellName(s string) (string, int) {
switch {
case s[0] == '(':
if len(s) > 2 && isShellSpecialVar(s[1]) && s[2] == ')' {
return s[1:2], 3
}
// Scan to closing brace
for i := 1; i < len(s); i++ {
if s[i] == ')' {
if i == 1 {
return "", 2 // Bad syntax; eat "$()""
}
return s[1:i], i + 1
}
}
return "", 1 // Bad syntax; eat "$("
case isShellSpecialVar(s[0]):
return s[0:1], 1
}
// Scan alphanumerics.
var i int
for i = 0; i < len(s) && isAlphaNum(s[i]); i++ {
}
return s[:i], i
}

//Equal to os/exec Expand but changed ${} to $()
func isShellSpecialVar(c uint8) bool {
switch c {
case '*', '#', '$', '@', '!', '?', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
return true
}
return false
}

//Equal to os/exec Expand but changed ${} to $()
func isAlphaNum(c uint8) bool {
return c == '_' || '0' <= c && c <= '9' || 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z'
}

func runCMD(program string) string {
a := strings.Split(program, " ")
out, err := exec.Command(a[0], a[1:]...).Output()
if err != nil {
log.Printf("couldn't expand the following command in include: %s\n", program)
return ""
}
return strings.TrimSuffix(string(out), "\n")
}

func replaceAccent(s string, mapping func(string) string) string {
r := ""
for i := 0; i < len(s); i++ {
if s[i] == '`' && (i > 0 || s[i-1] != '\\') && i+1 < len(s) {
buf := ""
closed := false
for j := i + 1; j < len(s) && !closed; j++ {
if s[j] != '`' {
buf += string(s[j])
} else {
closed = true
i = j
}
}
if closed {
r += mapping(buf)
}
} else {
r += string(s[i])
}
}
return r
}

func ExpandCommand(s string) string {
s = replaceDollar(s, runCMD)
s = replaceAccent(s, runCMD)
return s
}

func checkPath(s string) []string {
var r []string
info, err := os.Stat(s)
if err != nil {
return []string{}
}
if !info.IsDir() {
cp := filepath.Clean(s)
if cp[0] == '~' {
home, _ := os.LookupEnv("HOME")
cp = home + cp[1:]
}
r = append(r, cp)
}
return r
}

func GetPaths(s string) ([]string, error) {
s = ExpandCommand(s)
matches, err := filepath.Glob(s)
if err != nil {
return nil, err
}
var paths []string
for _, m := range matches {
paths = append(paths, checkPath(m)...)
}
return paths, nil
}
61 changes: 58 additions & 3 deletions internal/i3parse/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"io"
"log"
"os"
"sort"
"strings"

Expand Down Expand Up @@ -38,6 +39,10 @@ func getLineType(parts []string, c context) lineType {
if validateVariable(parts) {
return variableLine
}
case "include":
if validateInclude(parts) {
return includeLine
}
case "bindsym":
if validateBinding(parts) && parts[len(parts)-1] != "{" {
return bindSymLine
Expand Down Expand Up @@ -103,16 +108,17 @@ func readLine(reader *bufio.Reader, c context) (string, []string, lineType, erro
return line, lineParts, lineType, err
}

func parse(confReader io.Reader, err error) ([]Mode, []Binding, error) {
func parseConfig(confReader io.Reader, err error) ([]Mode, []Binding, []Variable, []string, error) {
if err != nil {
return []Mode{}, []Binding{}, errors.New("Couldn't get the config file")
return []Mode{}, []Binding{}, []Variable{}, []string{}, errors.New("Couldn't get the config file")
}

reader := bufio.NewReader(confReader)

var modes []Mode
var bindings []Binding
var variables []Variable
var includes []string

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

if readErr != nil && readErr != io.EOF {
return []Mode{}, []Binding{}, readErr
return []Mode{}, []Binding{}, []Variable{}, []string{}, readErr
}

switch lineType {
case skipLine:
continue
case variableLine:
variables = append(variables, parseVariable(lineParts))
continue
case modeLine:
context = modeContext
name := parseMode(line)
modes = append(modes, Mode{Name: name})
continue
case includeLine:
includes = append(includes, strings.Join(lineParts[1:], " "))
continue
case bindCodeBracket:
if context == mainContext {
context = bindCodeMainContext
Expand Down Expand Up @@ -185,6 +196,50 @@ func parse(confReader io.Reader, err error) ([]Mode, []Binding, error) {
}
}

var includePaths []string
for _, incl := range includes {
matches, err := helpers.GetPaths(incl)
if err != nil {
log.Printf("couldn't parse the following include \"%s\" got error %v", incl, err)
continue
}
includePaths = append(includePaths, matches...)
}

return modes, bindings, variables, includePaths, nil
}

func parse(confReader io.Reader, err error) ([]Mode, []Binding, error) {
modes, bindings, variables, includes, err := parseConfig(confReader, err)
if err != nil {
return []Mode{}, []Binding{}, errors.New("Couldn't get the config file")
}
var parsedIncludes []string
for _, incl := range includes {
done := false
for _, ap := range parsedIncludes {
if ap == incl {
done = true
}
}
if done {
continue
}
f, ferr := os.Open(incl)
if err != nil {
log.Printf("couldn't open the included file %s, got err: %v\n", incl, ferr)
}
m, b, v, i, perr := parseConfig(f, err)
if err != nil {
log.Printf("couldn't parse the included file %s, got err: %v\n", incl, perr)
}
modes = append(modes, m...)
bindings = append(bindings, b...)
variables = append(variables, v...)
includes = append(includes, i...)
parsedIncludes = append(parsedIncludes, incl)
}

bindings, modes = replaceVariables(variables, bindings, modes)

for key := range modes {
Expand Down
1 change: 1 addition & 0 deletions internal/i3parse/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ type context uint
const (
skipLine lineType = iota
variableLine
includeLine
bindCodeLine
bindCodeBracket
unBindCodeLine
Expand Down
10 changes: 7 additions & 3 deletions internal/i3parse/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,13 @@ func validateVariable(parts []string) bool {
if len(parts) < 2 || parts[1][0] != '$' {
return false
}

return true
}

func validateBinding(parts []string) bool {
if len(parts) < 3 {
return false
}

return true
}

Expand All @@ -21,6 +19,12 @@ func validateMode(parts []string) bool {
(parts[1] == "--pango_markup" || parts[1][0] == '"') {
return true
}

return false
}

func validateInclude(parts []string) bool {
if len(parts) < 2 {
return false
}
return true
}
Loading

0 comments on commit c0036d5

Please sign in to comment.