-
Notifications
You must be signed in to change notification settings - Fork 1
/
parse.go
132 lines (111 loc) · 2.53 KB
/
parse.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
// Copyright 2015 Giulio Iotti. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package sshconfig
import (
"bufio"
"fmt"
"io"
"strings"
"unicode/utf8"
)
const spaces = " \t"
// Section represents a Host configuration section, for example:
//
// Host test
// ConfigOption yes
// OtherOption no
//
// Where the Host part is the Name, the options the Values in map.
type Section struct {
Name string
Values map[string]string
}
// Creates a new empty Section
func NewSection(name string) *Section {
return &Section{Name: name, Values: make(map[string]string)}
}
// Internal token representation with line number
type token struct {
line int
val string
}
// Tokenize a single line, skipping spaces and comments
func parseLine(nline int, line string, tokens chan<- token) {
var end int
line = strings.TrimLeft(line, spaces)
if len(line) < 1 || line[0] == '#' {
return
}
for end = 0; end < len(line); {
r, s := utf8.DecodeRuneInString(line[end:])
if strings.ContainsRune(spaces, r) {
break
}
end = end + s
}
if end <= 0 {
return
}
tokens <- token{nline, line[0:end]}
line = strings.Trim(line[end:], spaces)
for end = 0; end < len(line); {
r, s := utf8.DecodeRuneInString(line[end:])
if r == '#' {
break
}
end = end + s
}
if end > 0 {
tokens <- token{nline, strings.Trim(line[0:end], spaces)}
} else {
tokens <- token{nline, ""}
}
}
// Tokenize line by line the entire reader
func parseFile(f io.Reader, tokens chan<- token) {
var nline int
scanner := bufio.NewScanner(f)
defer close(tokens)
for scanner.Scan() {
nline++
parseLine(nline, scanner.Text(), tokens)
}
}
// Consume tokens from the channel until the next
// Host section
func (s *Section) loadMap(tokens <-chan token) {
var hasKey bool
var key string
for token := range tokens {
if token.val == "Host" {
return
}
if hasKey {
s.Values[key] = token.val
hasKey = false
} else {
key = token.val
hasKey = true
}
}
}
// Parse parses a SSH config file and returns either an error or the
// slice of parsed Sections.
func Parse(r io.Reader) ([]*Section, error) {
tokens := make(chan token)
go parseFile(r, tokens)
sections := make([]*Section, 0)
token := <-tokens
if token.val != "Host" {
for range tokens {
}
return nil, fmt.Errorf("line %s: expected Host", token.line)
}
for token := range tokens {
sec := NewSection(token.val)
sec.loadMap(tokens)
sections = append(sections, sec)
}
return sections, nil
}