blob: f2b88a7ca05eec0b81c73ac1a40de0f3461a35a0 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package codifier
import (
"fmt"
"log"
"regexp"
"strings"
)
// flattenString returns a shortened version of the string, limiting it to l
// characters and flattening it by removing linefeeds and indicating them with
// the string "\n".
// If l < 0, don't shorten.
func flattenString(s string, l int) string {
runes := []rune(s)
if l < 0 || l > len(runes) {
l = len(runes)
}
runes = runes[0:l]
return strings.Replace(string(runes), "\n", `\n`, -1)
}
// addIfNeeded adds the given line to the scope, if it is not already present
// and if the key string is present. The line is added following the last line
// that matches the location string. It returns the updated or unchanged string
// and boolean indicating whether a change was made.
//
// Notes:
// • It is not an error if the key or the line are already present.
// • The key and location are regular expression strings.
// • Note that this function does not match on word boundaries, so "fn" will
// match "foofn". Callers should ensure that the key and location strings are
// unique or pass in a regular expression fragment that disambiguates the
// matches.
//
// Example Usage: This function is useful for adding import lines: The scope is
// the file contents, the key is the function that requires the import, the
// location is the import keyword, and the line is the import line.
//
func addIfNeeded(scope, key, location, line string) (string, error) {
if strings.Contains(scope, line) {
log.Printf(" addIfNeeded(): line %q already present", line)
return scope, nil
}
r, err := regexp.Compile(fmt.Sprintf("%s", key))
if err != nil {
return scope, fmt.Errorf("couldn't compile key %q into regex", key)
}
if r.FindString(scope) == "" {
log.Printf(" addIfNeeded(): key %q not found, so not adding anything", key)
return scope, nil
}
r, err = regexp.Compile(location)
if err != nil {
return scope, fmt.Errorf("couldn't compile location %q into regex", location)
}
if r.FindString(scope) == "" {
return scope, fmt.Errorf("location string %q not found", location)
}
// Regular expression: match the location string followed by anything other
// than a line-ending character. Note that the match will not grab that final
// line-ending character.
r, err = regexp.Compile(fmt.Sprintf(`%s[^\r\n]*`, location))
if err != nil {
return scope, fmt.Errorf("couldn't compile %q into regex", location)
}
l := r.FindAllIndex([]byte(scope), -1)
if l == nil {
return scope, fmt.Errorf("location %q not found via regex", location)
}
// There can be several matches returned. Use the last one.
lastLoc := l[len(l)-1]
// The match didn't include the line-ending character, if there was one.
// Increment to include it.
if lastLoc[1] != len(scope) {
lastLoc[1]++
}
return scope[:lastLoc[1]] + line + scope[lastLoc[1]:], nil
}
// removeIfNotNeeded removes the given text from the scope, if it is present and
// if the key string is not present. It returns the updated or unchanged string.
// This function is useful for removing import lines, for example: The scope is
// the file contents, the key is the function that formerly required the import,
// but may still be present, and the text is the import line.
//
// Notes:
// • It is not an error if the key or line are not found.
// • This function does not match on word boundaries, so "fn" will match
// "foofn". Callers should ensure that the key and location strings are unique
// or pass in a regular expression fragment that disambiguates the matches.
// • This function may remove a needed import if it is needed by a different
// function than the given key.
// • The key is a regular expression string.
func removeIfNotNeeded(scope, key, text string) (string, error) {
if !strings.Contains(scope, text) {
log.Printf(" removeIfNotNeeded(): line %q not found", text)
return scope, nil
}
r, err := regexp.Compile(key)
if err != nil {
return scope, fmt.Errorf("couldn't compile key %q into regex", key)
}
if r.FindString(scope) != "" {
log.Printf(" removeIfNotNeeded(): key %q not found, so not removing anything", key)
return scope, nil
}
return strings.Replace(scope, text, "", -1), nil
}
// RemoveLineWith removes the line containing the given key. If more than one
// instance of the key is found an error is logged and the unchanged string
// returned.
func RemoveLineWith(scope, key string) string {
if !strings.Contains(scope, key) {
log.Printf(" RemoveLineWith() error: key %q not found", key)
return scope
}
// If the string has no line-endings, add one for the rexexp matcher.
if !strings.ContainsAny(scope, "\n\r") {
scope += "\n"
}
r, err := regexp.Compile(fmt.Sprintf(`(?m)^.*?%s.*?[\r\n]+`, key))
if err != nil {
log.Printf(" RemoveLineWith() error: couldn't compile key %q into regex", key)
return scope
}
if matches := r.FindAllString(scope, -1); len(matches) == 0 {
log.Printf(" RemoveLineWith() error: no line with key %q found", key)
return scope
}
if matches := r.FindAllString(scope, -1); len(matches) > 1 {
log.Printf(" RemoveLineWith() error: more than one copy of key %q found", key)
return scope
}
return r.ReplaceAllString(scope, "")
}