mirror of
https://github.com/mainflux/mainflux.git
synced 2025-04-27 13:48:49 +08:00

* Use normalizer as stream source Renamed 'writer' service to 'normalizer' and dropped Cassandra facilities from it. Extracted the common dependencies to 'mainflux' package for easier sharing. Fixed the API docs and unified environment variables. Signed-off-by: Dejan Mijic <dejan@mainflux.com> * Use docker build arguments to specify build Signed-off-by: Dejan Mijic <dejan@mainflux.com> * Remove cassandra libraries Signed-off-by: Dejan Mijic <dejan@mainflux.com> * Update go-kit version to 0.6.0 Signed-off-by: Dejan Mijic <dejan@mainflux.com> * Fix manager configuration Signed-off-by: Dejan Mijic <dejan@mainflux.com> * Refactor docker-compose Merged individual compose files and dropped external links. Remove CoAP container since it is not referenced from NginX config at the moment. Update port mapping in compose and nginx.conf. Dropped bin scripts. Updated service documentation. Signed-off-by: Dejan Mijic <dejan@mainflux.com> * Drop content-type check Signed-off-by: Dejan Mijic <dejan@mainflux.com> * Implement users data access layer in PostgreSQL Signed-off-by: Dejan Mijic <dejan@mainflux.com> * Bump version to 0.1.0 Signed-off-by: Dejan Mijic <dejan@mainflux.com> * Use go-kit logger everywhere (except CoAP) Signed-off-by: Dejan Mijic <dejan@mainflux.com> * Improve factory methods naming Signed-off-by: Dejan Mijic <dejan@mainflux.com> * Implement clients data access layer on PostgreSQL Signed-off-by: Dejan Mijic <dejan@mainflux.com> * Make tests stateless All tests are refactored to use map-based table-driven tests. No cross-tests dependencies is present anymore. Signed-off-by: Dejan Mijic <dejan@mainflux.com> * Remove gitignore Signed-off-by: Dejan Mijic <dejan@mainflux.com> * Fix nginx proxying Signed-off-by: Dejan Mijic <dejan@mainflux.com> * Mark client-user FK explicit Signed-off-by: Dejan Mijic <dejan@mainflux.com> * Update API documentation Signed-off-by: Dejan Mijic <dejan@mainflux.com> * Update channel model Signed-off-by: Dejan Mijic <dejan@mainflux.com> * Add channel PostgreSQL repository tests Signed-off-by: Dejan Mijic <dejan@mainflux.com> * Implement PostgreSQL channels DAO Replaced update queries with raw SQL. Explicitly defined M2M table due to difficulties of ensuring the referential integrity through GORM. Signed-off-by: Dejan Mijic <dejan@mainflux.com> * Expose connection endpoints Signed-off-by: Dejan Mijic <dejan@mainflux.com> * Fix swagger docs and remove DB logging Signed-off-by: Dejan Mijic <dejan@mainflux.com> * Fix nested query remarks Signed-off-by: Dejan Mijic <dejan@mainflux.com> * Add unique indices Signed-off-by: Dejan Mijic <dejan@mainflux.com>
363 lines
10 KiB
Go
363 lines
10 KiB
Go
// Copyright 2012 Neal van Veen. All rights reserved.
|
|
// Usage of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
package gotty
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
var exp = [...]string{
|
|
"%%",
|
|
"%c",
|
|
"%s",
|
|
"%p(\\d)",
|
|
"%P([A-z])",
|
|
"%g([A-z])",
|
|
"%'(.)'",
|
|
"%{([0-9]+)}",
|
|
"%l",
|
|
"%\\+|%-|%\\*|%/|%m",
|
|
"%&|%\\||%\\^",
|
|
"%=|%>|%<",
|
|
"%A|%O",
|
|
"%!|%~",
|
|
"%i",
|
|
"%(:[\\ #\\-\\+]{0,4})?(\\d+\\.\\d+|\\d+)?[doxXs]",
|
|
"%\\?(.*?);",
|
|
}
|
|
|
|
var regex *regexp.Regexp
|
|
var staticVar map[byte]stacker
|
|
|
|
// Parses the attribute that is received with name attr and parameters params.
|
|
func (term *TermInfo) Parse(attr string, params ...interface{}) (string, error) {
|
|
// Get the attribute name first.
|
|
iface, err := term.GetAttribute(attr)
|
|
str, ok := iface.(string)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if !ok {
|
|
return str, errors.New("Only string capabilities can be parsed.")
|
|
}
|
|
// Construct the hidden parser struct so we can use a recursive stack based
|
|
// parser.
|
|
ps := &parser{}
|
|
// Dynamic variables only exist in this context.
|
|
ps.dynamicVar = make(map[byte]stacker, 26)
|
|
ps.parameters = make([]stacker, len(params))
|
|
// Convert the parameters to insert them into the parser struct.
|
|
for i, x := range params {
|
|
ps.parameters[i] = x
|
|
}
|
|
// Recursively walk and return.
|
|
result, err := ps.walk(str)
|
|
return result, err
|
|
}
|
|
|
|
// Parses the attribute that is received with name attr and parameters params.
|
|
// Only works on full name of a capability that is given, which it uses to
|
|
// search for the termcap name.
|
|
func (term *TermInfo) ParseName(attr string, params ...interface{}) (string, error) {
|
|
tc := GetTermcapName(attr)
|
|
return term.Parse(tc, params)
|
|
}
|
|
|
|
// Identify each token in a stack based manner and do the actual parsing.
|
|
func (ps *parser) walk(attr string) (string, error) {
|
|
// We use a buffer to get the modified string.
|
|
var buf bytes.Buffer
|
|
// Next, find and identify all tokens by their indices and strings.
|
|
tokens := regex.FindAllStringSubmatch(attr, -1)
|
|
if len(tokens) == 0 {
|
|
return attr, nil
|
|
}
|
|
indices := regex.FindAllStringIndex(attr, -1)
|
|
q := 0 // q counts the matches of one token
|
|
// Iterate through the string per character.
|
|
for i := 0; i < len(attr); i++ {
|
|
// If the current position is an identified token, execute the following
|
|
// steps.
|
|
if q < len(indices) && i >= indices[q][0] && i < indices[q][1] {
|
|
// Switch on token.
|
|
switch {
|
|
case tokens[q][0][:2] == "%%":
|
|
// Literal percentage character.
|
|
buf.WriteByte('%')
|
|
case tokens[q][0][:2] == "%c":
|
|
// Pop a character.
|
|
c, err := ps.st.pop()
|
|
if err != nil {
|
|
return buf.String(), err
|
|
}
|
|
buf.WriteByte(c.(byte))
|
|
case tokens[q][0][:2] == "%s":
|
|
// Pop a string.
|
|
str, err := ps.st.pop()
|
|
if err != nil {
|
|
return buf.String(), err
|
|
}
|
|
if _, ok := str.(string); !ok {
|
|
return buf.String(), errors.New("Stack head is not a string")
|
|
}
|
|
buf.WriteString(str.(string))
|
|
case tokens[q][0][:2] == "%p":
|
|
// Push a parameter on the stack.
|
|
index, err := strconv.ParseInt(tokens[q][1], 10, 8)
|
|
index--
|
|
if err != nil {
|
|
return buf.String(), err
|
|
}
|
|
if int(index) >= len(ps.parameters) {
|
|
return buf.String(), errors.New("Parameters index out of bound")
|
|
}
|
|
ps.st.push(ps.parameters[index])
|
|
case tokens[q][0][:2] == "%P":
|
|
// Pop a variable from the stack as a dynamic or static variable.
|
|
val, err := ps.st.pop()
|
|
if err != nil {
|
|
return buf.String(), err
|
|
}
|
|
index := tokens[q][2]
|
|
if len(index) > 1 {
|
|
errorStr := fmt.Sprintf("%s is not a valid dynamic variables index",
|
|
index)
|
|
return buf.String(), errors.New(errorStr)
|
|
}
|
|
// Specify either dynamic or static.
|
|
if index[0] >= 'a' && index[0] <= 'z' {
|
|
ps.dynamicVar[index[0]] = val
|
|
} else if index[0] >= 'A' && index[0] <= 'Z' {
|
|
staticVar[index[0]] = val
|
|
}
|
|
case tokens[q][0][:2] == "%g":
|
|
// Push a variable from the stack as a dynamic or static variable.
|
|
index := tokens[q][3]
|
|
if len(index) > 1 {
|
|
errorStr := fmt.Sprintf("%s is not a valid static variables index",
|
|
index)
|
|
return buf.String(), errors.New(errorStr)
|
|
}
|
|
var val stacker
|
|
if index[0] >= 'a' && index[0] <= 'z' {
|
|
val = ps.dynamicVar[index[0]]
|
|
} else if index[0] >= 'A' && index[0] <= 'Z' {
|
|
val = staticVar[index[0]]
|
|
}
|
|
ps.st.push(val)
|
|
case tokens[q][0][:2] == "%'":
|
|
// Push a character constant.
|
|
con := tokens[q][4]
|
|
if len(con) > 1 {
|
|
errorStr := fmt.Sprintf("%s is not a valid character constant", con)
|
|
return buf.String(), errors.New(errorStr)
|
|
}
|
|
ps.st.push(con[0])
|
|
case tokens[q][0][:2] == "%{":
|
|
// Push an integer constant.
|
|
con, err := strconv.ParseInt(tokens[q][5], 10, 32)
|
|
if err != nil {
|
|
return buf.String(), err
|
|
}
|
|
ps.st.push(con)
|
|
case tokens[q][0][:2] == "%l":
|
|
// Push the length of the string that is popped from the stack.
|
|
popStr, err := ps.st.pop()
|
|
if err != nil {
|
|
return buf.String(), err
|
|
}
|
|
if _, ok := popStr.(string); !ok {
|
|
errStr := fmt.Sprintf("Stack head is not a string")
|
|
return buf.String(), errors.New(errStr)
|
|
}
|
|
ps.st.push(len(popStr.(string)))
|
|
case tokens[q][0][:2] == "%?":
|
|
// If-then-else construct. First, the whole string is identified and
|
|
// then inside this substring, we can specify which parts to switch on.
|
|
ifReg, _ := regexp.Compile("%\\?(.*)%t(.*)%e(.*);|%\\?(.*)%t(.*);")
|
|
ifTokens := ifReg.FindStringSubmatch(tokens[q][0])
|
|
var (
|
|
ifStr string
|
|
err error
|
|
)
|
|
// Parse the if-part to determine if-else.
|
|
if len(ifTokens[1]) > 0 {
|
|
ifStr, err = ps.walk(ifTokens[1])
|
|
} else { // else
|
|
ifStr, err = ps.walk(ifTokens[4])
|
|
}
|
|
// Return any errors
|
|
if err != nil {
|
|
return buf.String(), err
|
|
} else if len(ifStr) > 0 {
|
|
// Self-defined limitation, not sure if this is correct, but didn't
|
|
// seem like it.
|
|
return buf.String(), errors.New("If-clause cannot print statements")
|
|
}
|
|
var thenStr string
|
|
// Pop the first value that is set by parsing the if-clause.
|
|
choose, err := ps.st.pop()
|
|
if err != nil {
|
|
return buf.String(), err
|
|
}
|
|
// Switch to if or else.
|
|
if choose.(int) == 0 && len(ifTokens[1]) > 0 {
|
|
thenStr, err = ps.walk(ifTokens[3])
|
|
} else if choose.(int) != 0 {
|
|
if len(ifTokens[1]) > 0 {
|
|
thenStr, err = ps.walk(ifTokens[2])
|
|
} else {
|
|
thenStr, err = ps.walk(ifTokens[5])
|
|
}
|
|
}
|
|
if err != nil {
|
|
return buf.String(), err
|
|
}
|
|
buf.WriteString(thenStr)
|
|
case tokens[q][0][len(tokens[q][0])-1] == 'd': // Fallthrough for printing
|
|
fallthrough
|
|
case tokens[q][0][len(tokens[q][0])-1] == 'o': // digits.
|
|
fallthrough
|
|
case tokens[q][0][len(tokens[q][0])-1] == 'x':
|
|
fallthrough
|
|
case tokens[q][0][len(tokens[q][0])-1] == 'X':
|
|
fallthrough
|
|
case tokens[q][0][len(tokens[q][0])-1] == 's':
|
|
token := tokens[q][0]
|
|
// Remove the : that comes before a flag.
|
|
if token[1] == ':' {
|
|
token = token[:1] + token[2:]
|
|
}
|
|
digit, err := ps.st.pop()
|
|
if err != nil {
|
|
return buf.String(), err
|
|
}
|
|
// The rest is determined like the normal formatted prints.
|
|
digitStr := fmt.Sprintf(token, digit.(int))
|
|
buf.WriteString(digitStr)
|
|
case tokens[q][0][:2] == "%i":
|
|
// Increment the parameters by one.
|
|
if len(ps.parameters) < 2 {
|
|
return buf.String(), errors.New("Not enough parameters to increment.")
|
|
}
|
|
val1, val2 := ps.parameters[0].(int), ps.parameters[1].(int)
|
|
val1++
|
|
val2++
|
|
ps.parameters[0], ps.parameters[1] = val1, val2
|
|
default:
|
|
// The rest of the tokens is a special case, where two values are
|
|
// popped and then operated on by the token that comes after them.
|
|
op1, err := ps.st.pop()
|
|
if err != nil {
|
|
return buf.String(), err
|
|
}
|
|
op2, err := ps.st.pop()
|
|
if err != nil {
|
|
return buf.String(), err
|
|
}
|
|
var result stacker
|
|
switch tokens[q][0][:2] {
|
|
case "%+":
|
|
// Addition
|
|
result = op2.(int) + op1.(int)
|
|
case "%-":
|
|
// Subtraction
|
|
result = op2.(int) - op1.(int)
|
|
case "%*":
|
|
// Multiplication
|
|
result = op2.(int) * op1.(int)
|
|
case "%/":
|
|
// Division
|
|
result = op2.(int) / op1.(int)
|
|
case "%m":
|
|
// Modulo
|
|
result = op2.(int) % op1.(int)
|
|
case "%&":
|
|
// Bitwise AND
|
|
result = op2.(int) & op1.(int)
|
|
case "%|":
|
|
// Bitwise OR
|
|
result = op2.(int) | op1.(int)
|
|
case "%^":
|
|
// Bitwise XOR
|
|
result = op2.(int) ^ op1.(int)
|
|
case "%=":
|
|
// Equals
|
|
result = op2 == op1
|
|
case "%>":
|
|
// Greater-than
|
|
result = op2.(int) > op1.(int)
|
|
case "%<":
|
|
// Lesser-than
|
|
result = op2.(int) < op1.(int)
|
|
case "%A":
|
|
// Logical AND
|
|
result = op2.(bool) && op1.(bool)
|
|
case "%O":
|
|
// Logical OR
|
|
result = op2.(bool) || op1.(bool)
|
|
case "%!":
|
|
// Logical complement
|
|
result = !op1.(bool)
|
|
case "%~":
|
|
// Bitwise complement
|
|
result = ^(op1.(int))
|
|
}
|
|
ps.st.push(result)
|
|
}
|
|
|
|
i = indices[q][1] - 1
|
|
q++
|
|
} else {
|
|
// We are not "inside" a token, so just skip until the end or the next
|
|
// token, and add all characters to the buffer.
|
|
j := i
|
|
if q != len(indices) {
|
|
for !(j >= indices[q][0] && j < indices[q][1]) {
|
|
j++
|
|
}
|
|
} else {
|
|
j = len(attr)
|
|
}
|
|
buf.WriteString(string(attr[i:j]))
|
|
i = j
|
|
}
|
|
}
|
|
// Return the buffer as a string.
|
|
return buf.String(), nil
|
|
}
|
|
|
|
// Push a stacker-value onto the stack.
|
|
func (st *stack) push(s stacker) {
|
|
*st = append(*st, s)
|
|
}
|
|
|
|
// Pop a stacker-value from the stack.
|
|
func (st *stack) pop() (stacker, error) {
|
|
if len(*st) == 0 {
|
|
return nil, errors.New("Stack is empty.")
|
|
}
|
|
newStack := make(stack, len(*st)-1)
|
|
val := (*st)[len(*st)-1]
|
|
copy(newStack, (*st)[:len(*st)-1])
|
|
*st = newStack
|
|
return val, nil
|
|
}
|
|
|
|
// Initialize regexes and the static vars (that don't get changed between
|
|
// calls.
|
|
func init() {
|
|
// Initialize the main regex.
|
|
expStr := strings.Join(exp[:], "|")
|
|
regex, _ = regexp.Compile(expStr)
|
|
// Initialize the static variables.
|
|
staticVar = make(map[byte]stacker, 26)
|
|
}
|