blob: 6cd6383fab0df9327d7a463074a3f4a263eb1a51 [file] [log] [blame]
// Copyright (C) 2016 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package template
import (
"bytes"
"fmt"
"android.googlesource.com/platform/tools/gpu/client/shell"
"golang.org/x/tools/imports"
)
const (
disable = '⋖'
enable = '⋗'
indent = '»'
unindent = '«'
suppress = '§'
newline = '¶'
whitespace = '•'
)
// Reflow does the primitive reflow, but no language specific handling.
func (f *Functions) Reflow(indentSize int, value string) (string, error) {
f.ctx.Print("Reflowing string")
result, err := reflow(value, indentSize)
if err != nil {
return "", fmt.Errorf("%s : %s", f.active.Name(), err)
}
return string(result), nil
}
const goIndent = 2 // Required by go style guide
// GoFmt reflows the string as if it were go code using the standard go fmt library.
func (f *Functions) GoFmt(value string) (string, error) {
f.ctx.Print("Reflowing go code")
result, err := reflow(value, goIndent)
if err != nil {
return "", fmt.Errorf("%s : %s", f.active.Name(), err)
}
opt := &imports.Options{
TabWidth: goIndent,
TabIndent: true,
Comments: true,
Fragment: true,
}
formatted, err := imports.Process(f.active.Name(), result, opt)
if err != nil {
f.ctx.Info().Fail(err, "GoFmt")
return string(result), nil
}
return string(formatted), nil
}
// Format reflows the string using an external command.
func (f *Functions) Format(command stringList, value string) (string, error) {
if len(command) == 0 {
return "", fmt.Errorf("%s : Invalid Format command", f.active.Name())
}
binary := command[0]
f.ctx.Info().S("binary", binary).Log("Reflowing code")
// indent level is arbitrary, because we expect the external formatter to redo it anyway
result, err := reflow(value, 4)
if err != nil {
return "", fmt.Errorf("%s : %s", f.active.Name(), err)
}
stdin := bytes.NewBuffer(result)
stdout := &bytes.Buffer{}
stderr := &bytes.Buffer{}
err = shell.Command(binary, command[1:]...).Capture(stdout, stderr).Read(stdin).Run(f.ctx)
if err != nil {
f.ctx.Info().S("stderr", stderr.String()).Fail(err, "Reformat")
return string(result), nil
}
return stdout.String(), nil
}
func panicWrite(buf *bytes.Buffer, r rune) {
_, err := buf.WriteRune(r)
if err != nil {
panic(err)
}
}
func reflow(in string, indentSize int) ([]byte, error) {
depth := 0
wasNewline := false
suppressing := true
join := false
enabled := true
buf := &bytes.Buffer{}
flushPending := func() {
if wasNewline && !suppressing {
// write the indent
panicWrite(buf, '\n')
for i := 0; i < depth*indentSize; i++ {
panicWrite(buf, ' ')
}
}
suppressing = false
wasNewline = false
join = false
}
for _, ch := range in {
if !enabled {
if ch == enable {
enabled = true
} else {
panicWrite(buf, ch)
}
} else {
switch ch {
case disable:
flushPending()
enabled = false
ch = 0
case whitespace:
ch = ' '
case suppress:
suppressing = true
ch = 0
case newline:
panicWrite(buf, '\n')
fallthrough
case '\n', '\r':
if !join {
wasNewline = true
}
ch = 0
case '\t', ' ':
if wasNewline {
ch = 0
}
case indent:
ch = 0
depth += 1
case '{', '[':
flushPending()
depth += 1
case unindent:
ch = 0
fallthrough
case '}', ']':
depth -= 1
}
if ch != 0 {
flushPending()
panicWrite(buf, ch)
}
}
}
return buf.Bytes(), nil
}