strutil.cc revision 9e6e9301189479514d7b060491ff4f51b6d0b840
1// Copyright 2015 Google Inc. All rights reserved
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// +build ignore
16
17#include "strutil.h"
18
19#include <ctype.h>
20#include <limits.h>
21#include <unistd.h>
22
23#include <algorithm>
24#include <stack>
25#include <utility>
26
27#include "log.h"
28
29static bool isSpace(char c) {
30  return (9 <= c && c <= 13) || c == 32;
31}
32
33WordScanner::Iterator& WordScanner::Iterator::operator++() {
34  int len = static_cast<int>(in->size());
35  for (s = i; s < len; s++) {
36    if (!isSpace((*in)[s]))
37      break;
38  }
39  if (s == len) {
40    in = NULL;
41    s = 0;
42    i = 0;
43    return *this;
44  }
45  for (i = s; i < len; i++) {
46    if (isSpace((*in)[i]))
47      break;
48  }
49  return *this;
50}
51
52StringPiece WordScanner::Iterator::operator*() const {
53  return in->substr(s, i - s);
54}
55
56WordScanner::WordScanner(StringPiece in)
57    : in_(in) {
58}
59
60WordScanner::Iterator WordScanner::begin() const {
61  Iterator iter;
62  iter.in = &in_;
63  iter.s = 0;
64  iter.i = 0;
65  ++iter;
66  return iter;
67}
68
69WordScanner::Iterator WordScanner::end() const {
70  Iterator iter;
71  iter.in = NULL;
72  iter.s = 0;
73  iter.i = 0;
74  return iter;
75}
76
77void WordScanner::Split(vector<StringPiece>* o) {
78  for (StringPiece t : *this)
79    o->push_back(t);
80}
81
82WordWriter::WordWriter(string* o)
83    : out_(o),
84      needs_space_(false) {
85}
86
87void WordWriter::MaybeAddWhitespace() {
88  if (needs_space_) {
89    out_->push_back(' ');
90  } else {
91    needs_space_ = true;
92  }
93}
94
95void WordWriter::Write(StringPiece s) {
96  MaybeAddWhitespace();
97  AppendString(s, out_);
98}
99
100ScopedTerminator::ScopedTerminator(StringPiece s)
101    : s_(s), c_(s[s.size()]) {
102  const_cast<char*>(s_.data())[s_.size()] = '\0';
103}
104
105ScopedTerminator::~ScopedTerminator() {
106  const_cast<char*>(s_.data())[s_.size()] = c_;
107}
108
109void AppendString(StringPiece str, string* out) {
110  out->append(str.begin(), str.end());
111}
112
113bool HasPrefix(StringPiece str, StringPiece prefix) {
114  ssize_t size_diff = str.size() - prefix.size();
115  return size_diff >= 0 && str.substr(0, prefix.size()) == prefix;
116}
117
118bool HasSuffix(StringPiece str, StringPiece suffix) {
119  ssize_t size_diff = str.size() - suffix.size();
120  return size_diff >= 0 && str.substr(size_diff) == suffix;
121}
122
123bool HasWord(StringPiece str, StringPiece w) {
124  size_t found = str.find(w);
125  if (found == string::npos)
126    return false;
127  if (found != 0 && !isSpace(str[found-1]))
128    return false;
129  size_t end = found + w.size();
130  if (end != str.size() && !isSpace(str[end]))
131    return false;
132  return true;
133}
134
135StringPiece TrimSuffix(StringPiece str, StringPiece suffix) {
136  ssize_t size_diff = str.size() - suffix.size();
137  if (size_diff < 0 || str.substr(size_diff) != suffix)
138    return str;
139  return str.substr(0, size_diff);
140}
141
142Pattern::Pattern(StringPiece pat)
143    : pat_(pat), percent_index_(pat.find('%')) {
144}
145
146bool Pattern::Match(StringPiece str) const {
147  if (percent_index_ == string::npos)
148    return str == pat_;
149  return MatchImpl(str);
150}
151
152bool Pattern::MatchImpl(StringPiece str) const {
153  return (HasPrefix(str, pat_.substr(0, percent_index_)) &&
154          HasSuffix(str, pat_.substr(percent_index_ + 1)));
155}
156
157StringPiece Pattern::Stem(StringPiece str) const {
158  if (!Match(str))
159    return "";
160  return str.substr(percent_index_,
161                    str.size() - (pat_.size() - percent_index_ - 1));
162}
163
164void Pattern::AppendSubst(StringPiece str, StringPiece subst,
165                          string* out) const {
166  if (percent_index_ == string::npos) {
167    if (str == pat_) {
168      AppendString(subst, out);
169      return;
170    } else {
171      AppendString(str, out);
172      return;
173    }
174  }
175
176  if (MatchImpl(str)) {
177    size_t subst_percent_index = subst.find('%');
178    if (subst_percent_index == string::npos) {
179      AppendString(subst, out);
180      return;
181    } else {
182      AppendString(subst.substr(0, subst_percent_index), out);
183      AppendString(str.substr(percent_index_,
184                              str.size() - pat_.size() + 1), out);
185      AppendString(subst.substr(subst_percent_index + 1), out);
186      return;
187    }
188  }
189  AppendString(str, out);
190}
191
192void Pattern::AppendSubstRef(StringPiece str, StringPiece subst,
193                             string* out) const {
194  if (percent_index_ != string::npos && subst.find('%') != string::npos) {
195    AppendSubst(str, subst, out);
196    return;
197  }
198  StringPiece s = TrimSuffix(str, pat_);
199  out->append(s.begin(), s.end());
200  out->append(subst.begin(), subst.end());
201}
202
203string NoLineBreak(const string& s) {
204  size_t index = s.find('\n');
205  if (index == string::npos)
206    return s;
207  string r = s;
208  while (index != string::npos) {
209    r = r.substr(0, index) + "\\n" + r.substr(index + 1);
210    index = r.find('\n', index + 2);
211  }
212  return r;
213}
214
215StringPiece TrimLeftSpace(StringPiece s) {
216  size_t i = 0;
217  for (; i < s.size(); i++) {
218    if (isSpace(s[i]))
219      continue;
220    char n = s.get(i+1);
221    if (s[i] == '\\' && (n == '\r' || n == '\n')) {
222      i++;
223      continue;
224    }
225    break;
226  }
227  return s.substr(i, s.size() - i);
228}
229
230StringPiece TrimRightSpace(StringPiece s) {
231  size_t i = 0;
232  for (; i < s.size(); i++) {
233    char c = s[s.size() - 1 - i];
234    if (isSpace(c)) {
235      if ((c == '\r' || c == '\n') && s.get(s.size() - 2 - i) == '\\')
236        i++;
237      continue;
238    }
239    break;
240  }
241  return s.substr(0, s.size() - i);
242}
243
244StringPiece TrimSpace(StringPiece s) {
245  return TrimRightSpace(TrimLeftSpace(s));
246}
247
248StringPiece Dirname(StringPiece s) {
249  size_t found = s.rfind('/');
250  if (found == string::npos)
251    return StringPiece(".");
252  if (found == 0)
253    return StringPiece("");
254  return s.substr(0, found);
255}
256
257StringPiece Basename(StringPiece s) {
258  size_t found = s.rfind('/');
259  if (found == string::npos || found == 0)
260    return s;
261  return s.substr(found + 1);
262}
263
264StringPiece GetExt(StringPiece s) {
265  size_t found = s.rfind('.');
266  if (found == string::npos)
267    return StringPiece("");
268  return s.substr(found);
269}
270
271StringPiece StripExt(StringPiece s) {
272  size_t slash_index = s.rfind('/');
273  size_t found = s.rfind('.');
274  if (found == string::npos ||
275      (slash_index != string::npos && found < slash_index))
276    return s;
277  return s.substr(0, found);
278}
279
280void NormalizePath(string* o) {
281  if (o->empty())
282    return;
283  size_t start_index = 0;
284  if ((*o)[0] == '/')
285    start_index++;
286  size_t j = start_index;
287  size_t prev_start = start_index;
288  for (size_t i = start_index; i <= o->size(); i++) {
289    char c = (*o)[i];
290    if (c != '/' && c != 0) {
291      (*o)[j] = c;
292      j++;
293      continue;
294    }
295
296    StringPiece prev_dir = StringPiece(o->data() + prev_start, j - prev_start);
297    if (prev_dir == ".") {
298      j--;
299    } else if (prev_dir == ".." && j != 2 /* .. */) {
300      if (j == 3) {
301        // /..
302        j = start_index;
303      } else {
304        size_t orig_j = j;
305        j -= 4;
306        j = o->rfind('/', j);
307        if (j == string::npos) {
308          j = start_index;
309        } else {
310          j++;
311        }
312        if (StringPiece(o->data() + j, 3) == "../") {
313          j = orig_j;
314          (*o)[j] = c;
315          j++;
316        }
317      }
318    } else if (!prev_dir.empty()) {
319      if (c) {
320        (*o)[j] = c;
321        j++;
322      }
323    }
324    prev_start = j;
325  }
326  if (j > 1 && (*o)[j-1] == '/')
327    j--;
328  o->resize(j);
329}
330
331void AbsPath(StringPiece s, string* o) {
332  if (s.get(0) == '/') {
333    o->clear();
334  } else {
335    char buf[PATH_MAX];
336    if (!getcwd(buf, PATH_MAX)) {
337      fprintf(stderr, "getcwd failed\n");
338      CHECK(false);
339    }
340
341    CHECK(buf[0] == '/');
342    *o = buf;
343    *o += '/';
344  }
345  AppendString(s, o);
346  NormalizePath(o);
347}
348
349template<typename Cond>
350size_t FindOutsideParenImpl(StringPiece s, Cond cond) {
351  bool prev_backslash = false;
352  stack<char> paren_stack;
353  for (size_t i = 0; i < s.size(); i++) {
354    char c = s[i];
355    if (cond(c) && paren_stack.empty() && !prev_backslash) {
356      return i;
357    }
358    switch (c) {
359      case '(':
360        paren_stack.push(')');
361        break;
362      case '{':
363        paren_stack.push('}');
364        break;
365
366      case ')':
367      case '}':
368        if (!paren_stack.empty() && c == paren_stack.top()) {
369          paren_stack.pop();
370        }
371        break;
372    }
373    prev_backslash = c == '\\' && !prev_backslash;
374  }
375  return string::npos;
376}
377
378size_t FindOutsideParen(StringPiece s, char c) {
379  return FindOutsideParenImpl(s, [&c](char d){return c == d;});
380}
381
382size_t FindTwoOutsideParen(StringPiece s, char c1, char c2) {
383  return FindOutsideParenImpl(s, [&c1, &c2](char d){
384      return d == c1 || d == c2;
385    });
386}
387
388size_t FindThreeOutsideParen(StringPiece s, char c1, char c2, char c3) {
389  return FindOutsideParenImpl(s, [&c1, &c2, &c3](char d){
390      return d == c1 || d == c2 || d == c3;
391    });
392}
393
394size_t FindEndOfLine(StringPiece s, size_t e, size_t* lf_cnt) {
395  bool prev_backslash = false;
396  for (; e < s.size(); e++) {
397    char c = s[e];
398    if (c == '\\') {
399      prev_backslash = !prev_backslash;
400    } else if (c == '\n') {
401      ++*lf_cnt;
402      if (!prev_backslash) {
403        return e;
404      }
405      prev_backslash = false;
406    } else if (c != '\r') {
407      prev_backslash = false;
408    }
409  }
410  return e;
411}
412
413StringPiece TrimLeadingCurdir(StringPiece s) {
414  while (s.substr(0, 2) == "./")
415    s = s.substr(2);
416  return s;
417}
418
419void FormatForCommandSubstitution(string* s) {
420  while ((*s)[s->size()-1] == '\n')
421    s->pop_back();
422  for (size_t i = 0; i < s->size(); i++) {
423    if ((*s)[i] == '\n')
424      (*s)[i] = ' ';
425  }
426}
427
428string SortWordsInString(StringPiece s) {
429  vector<string> toks;
430  for (StringPiece tok : WordScanner(s)) {
431    toks.push_back(tok.as_string());
432  }
433  sort(toks.begin(), toks.end());
434  return JoinStrings(toks, " ");
435}
436
437string ConcatDir(StringPiece b, StringPiece n) {
438  string r;
439  if (!b.empty()) {
440    b.AppendToString(&r);
441    r += '/';
442  }
443  n.AppendToString(&r);
444  NormalizePath(&r);
445  return r;
446}
447
448string EchoEscape(const string str) {
449  const char *in = str.c_str();
450  string buf;
451  for (; *in; in++) {
452    switch(*in) {
453      case '\\':
454        buf += "\\\\\\\\";
455        break;
456      case '\n':
457        buf += "\\n";
458        break;
459      case '"':
460        buf += "\\\"";
461        break;
462      default:
463        buf += *in;
464    }
465  }
466  return buf;
467}
468