| // 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, "") |
| } |