lang: ast, interfaces: Move textarea to a common package
We're going to use it everywhere. We also make it more forgiving in the meanwhile while we're porting things over.
This commit is contained in:
198
lang/interfaces/textarea.go
Normal file
198
lang/interfaces/textarea.go
Normal file
@@ -0,0 +1,198 @@
|
||||
// Mgmt
|
||||
// Copyright (C) James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// Additional permission under GNU GPL version 3 section 7
|
||||
//
|
||||
// If you modify this program, or any covered work, by linking or combining it
|
||||
// with embedded mcl code and modules (and that the embedded mcl code and
|
||||
// modules which link with this program, contain a copy of their source code in
|
||||
// the authoritative form) containing parts covered by the terms of any other
|
||||
// license, the licensors of this program grant you additional permission to
|
||||
// convey the resulting work. Furthermore, the licensors of this program grant
|
||||
// the original author, James Shubin, additional permission to update this
|
||||
// additional permission if he deems it necessary to achieve the goals of this
|
||||
// additional permission.
|
||||
|
||||
package interfaces
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/purpleidea/mgmt/util"
|
||||
)
|
||||
|
||||
// Textarea stores the coordinates of a statement or expression in the form of a
|
||||
// starting line/column and ending line/column.
|
||||
type Textarea struct {
|
||||
// debug represents if we're running in debug mode or not.
|
||||
debug bool
|
||||
|
||||
// logf is a logger which should be used.
|
||||
logf func(format string, v ...interface{})
|
||||
|
||||
// sf is the SourceFinder function implementation that maps a filename
|
||||
// to the source.
|
||||
sf SourceFinderFunc
|
||||
|
||||
// path is the full path/filename where this text area exists.
|
||||
path string
|
||||
|
||||
// This data is zero-based. (Eg: first line of file is 0)
|
||||
startLine int // first
|
||||
startColumn int // left
|
||||
endLine int // last
|
||||
endColumn int // right
|
||||
|
||||
isSet bool
|
||||
|
||||
// Bug5819 works around issue https://github.com/golang/go/issues/5819
|
||||
Bug5819 interface{} // XXX: workaround
|
||||
}
|
||||
|
||||
// Setup is used during AST initialization in order to store in each AST node
|
||||
// the name of the source file from which it was generated.
|
||||
func (obj *Textarea) Setup(data *Data) {
|
||||
obj.debug = data.Debug
|
||||
obj.logf = data.Logf
|
||||
obj.sf = data.SourceFinder
|
||||
obj.path = data.AbsFilename()
|
||||
}
|
||||
|
||||
// IsSet returns if the position was already set with Locate already.
|
||||
func (obj *Textarea) IsSet() bool {
|
||||
return obj.isSet
|
||||
}
|
||||
|
||||
// Locate is used by the parser to store the token positions in AST nodes. The
|
||||
// path will be filled during AST node initialization usually, because the
|
||||
// parser does not know the name of the file it is processing.
|
||||
func (obj *Textarea) Locate(line int, col int, endline int, endcol int) {
|
||||
obj.startLine = line
|
||||
obj.startColumn = col
|
||||
obj.endLine = endline
|
||||
obj.endColumn = endcol
|
||||
obj.isSet = true
|
||||
}
|
||||
|
||||
// Pos returns the starting line/column of an AST node.
|
||||
func (obj *Textarea) Pos() (int, int) {
|
||||
return obj.startLine, obj.startColumn
|
||||
}
|
||||
|
||||
// End returns the end line/column of an AST node.
|
||||
func (obj *Textarea) End() (int, int) {
|
||||
return obj.endLine, obj.endColumn
|
||||
}
|
||||
|
||||
// Path returns the name of the source file that holds the code for an AST node.
|
||||
func (obj *Textarea) Path() string {
|
||||
return obj.path
|
||||
}
|
||||
|
||||
// Filename returns the printable filename that we'd like to display. It tries
|
||||
// to return a relative version if possible.
|
||||
func (obj *Textarea) Filename() string {
|
||||
if obj.path == "" {
|
||||
return "<unknown>" // TODO: should this be <stdin> ?
|
||||
}
|
||||
|
||||
wd, _ := os.Getwd() // ignore error since "" would just pass through
|
||||
wd += "/" // it's a dir
|
||||
if s, err := util.RemoveBasePath(obj.path, wd); err == nil {
|
||||
return s
|
||||
}
|
||||
|
||||
return obj.path
|
||||
}
|
||||
|
||||
// Byline gives a succinct representation of the Textarea, but is useful only in
|
||||
// debugging. In order to generate pretty error messages, see HighlightText.
|
||||
func (obj *Textarea) Byline() string {
|
||||
// We convert to 1-based for user display.
|
||||
return fmt.Sprintf("%s @ %d:%d-%d:%d", obj.Filename(), obj.startLine+1, obj.startColumn+1, obj.endLine+1, obj.endColumn+1)
|
||||
}
|
||||
|
||||
// HighlightText generates a generic description that just visually indicates
|
||||
// part of the line described by a Textarea. If the coordinates that are passed
|
||||
// span multiple lines, don't show those lines, but just a description of the
|
||||
// area. If it can't generate a valid snippet, then it returns the empty string.
|
||||
func (obj *Textarea) HighlightText() string {
|
||||
if obj.sf == nil {
|
||||
// XXX: when all functions are ported over to use ast.Textarea,
|
||||
// then uncomment this return and add in the panic below.
|
||||
return "" // XXX: temporary
|
||||
// programming error
|
||||
//panic("nil SourceFinderFunc")
|
||||
}
|
||||
b, err := obj.sf(obj.path) // source finder!
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
contents := string(b)
|
||||
|
||||
result := &strings.Builder{}
|
||||
|
||||
result.WriteString(obj.Byline())
|
||||
|
||||
lines := strings.Split(contents, "\n")
|
||||
if len(lines) < obj.endLine-1 {
|
||||
// XXX: out of bounds?
|
||||
return ""
|
||||
}
|
||||
|
||||
result.WriteString("\n\n")
|
||||
|
||||
if obj.startLine == obj.endLine {
|
||||
line := lines[obj.startLine] + "\n"
|
||||
text := strings.TrimLeft(line, " \t")
|
||||
indent := strings.TrimSuffix(line, text)
|
||||
offset := len(indent)
|
||||
|
||||
result.WriteString(line)
|
||||
result.WriteString(indent)
|
||||
result.WriteString(strings.Repeat(" ", obj.startColumn-offset))
|
||||
// TODO: add on the width of the second element as well
|
||||
result.WriteString(strings.Repeat("^", obj.endColumn-obj.startColumn+1))
|
||||
result.WriteString("\n")
|
||||
|
||||
return result.String()
|
||||
}
|
||||
|
||||
line := lines[obj.startLine] + "\n"
|
||||
text := strings.TrimLeft(line, " \t")
|
||||
indent := strings.TrimSuffix(line, text)
|
||||
offset := len(indent)
|
||||
|
||||
result.WriteString(line)
|
||||
result.WriteString(indent)
|
||||
result.WriteString(strings.Repeat(" ", obj.startColumn-offset))
|
||||
result.WriteString("^ from here ...\n")
|
||||
|
||||
line = lines[obj.endLine] + "\n"
|
||||
text = strings.TrimLeft(line, " \t")
|
||||
indent = strings.TrimSuffix(line, text)
|
||||
offset = len(indent)
|
||||
|
||||
result.WriteString(line)
|
||||
result.WriteString(indent)
|
||||
result.WriteString(strings.Repeat(" ", obj.startColumn-offset))
|
||||
result.WriteString("^ ... to here\n")
|
||||
|
||||
return result.String()
|
||||
}
|
||||
Reference in New Issue
Block a user