stripLeftMargin
authorYedidya Feldblum <yfeldblum@fb.com>
Wed, 10 Feb 2016 00:28:21 +0000 (16:28 -0800)
committerfacebook-github-bot-0 <folly-bot@fb.com>
Wed, 10 Feb 2016 01:20:27 +0000 (17:20 -0800)
Summary:
[Folly] `stripLeftMargin`.

So you can do:

    TEST(MyClass, doSomethingWithString) {
      //  Multiline string literal is indented properly inside the test case
      //  instead of being aligned all the way to the left, which would make
      //  the test case read poorly.
      auto input = folly::stripLeftMargin(R"TEXT(
        first line
        second line
        third line
      )TEXT");
      auto expected = //...
      auto actual = MyClass::doSomethingWithString(input);
      EXPECT_EQ(expected, actual);
    }

Reviewed By: markisaa

Differential Revision: D2909736

fb-gh-sync-id: ebb07da05e1a788535064cfcd9e07f617a007800
shipit-source-id: ebb07da05e1a788535064cfcd9e07f617a007800

folly/String.cpp
folly/String.h
folly/test/StringTest.cpp

index 5042cec97a53a1a05daf50c4be5f4fa3425107f4..74ccb4ecf82312c5f58e0dff050bdee28bbdd664 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2015 Facebook, Inc.
+ * Copyright 2016 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
 
 #include <folly/String.h>
 
+#include <boost/regex.hpp>
 #include <folly/Format.h>
 #include <folly/ScopeGuard.h>
 
@@ -548,6 +549,25 @@ size_t hexDumpLine(const void* ptr, size_t offset, size_t size,
 
 } // namespace detail
 
+std::string stripLeftMargin(std::string s) {
+  using namespace boost;
+  static const auto kPre = regex(R"(\A[ \t]*\n)");
+  static const auto kPost = regex(R"([ \t]+\z)");
+  static const auto kScan = regex(R"(^[ \t]*(?=\S))");
+  s = regex_replace(s, kPre, "");
+  s = regex_replace(s, kPost, "");
+  const auto sentinel = std::numeric_limits<size_t>::max();
+  auto indent = sentinel;
+  sregex_iterator it(s.cbegin(), s.cend(), kScan);
+  sregex_iterator itend;
+  for (; it != itend; ++it) {
+    indent = std::min<size_t>(indent, it->length());
+  }
+  indent = indent == sentinel ? 0 : indent;
+  s = regex_replace(s, regex(sformat(R"(^[ \t]{{0,{0}}})", indent)), "");
+  return s;
+}
+
 }   // namespace folly
 
 #ifdef FOLLY_DEFINED_DMGL
index f9ec5fd58eb82b76209f90357a4e963fde6bc1d1..19a2c3b23727a93d32a3ab312ca7483740af9e3a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2015 Facebook, Inc.
+ * Copyright 2016 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -580,6 +580,17 @@ inline StringPiece skipWhitespace(StringPiece sp) {
   return ltrimWhitespace(sp);
 }
 
+/**
+ *  Strips the leading and the trailing whitespace-only lines. Then looks for
+ *  the least indented non-whitespace-only line and removes its amount of
+ *  leading whitespace from every line. Assumes leading whitespace is either all
+ *  spaces or all tabs.
+ *
+ *  Purpose: including a multiline string literal in source code, indented to
+ *  the level expected from context.
+ */
+std::string stripLeftMargin(std::string s);
+
 /**
  * Fast, in-place lowercasing of ASCII alphabetic characters in strings.
  * Leaves all other characters unchanged, including those with the 0x80
index 759c4477a907a954252fabbd55d84b121165d1e1..f687213fd9e45bdcf60824e9d78d9e4cea93f58c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2015 Facebook, Inc.
+ * Copyright 2016 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
 
 #include <folly/String.h>
 
+#include <boost/regex.hpp>
 #include <gtest/gtest.h>
 
 using namespace folly;
@@ -1131,6 +1132,112 @@ TEST(String, whitespace) {
   EXPECT_EQ("", rtrimWhitespace("\r   "));
 }
 
+TEST(String, stripLeftMargin_empty) {
+  auto input = R"TEXT(
+  )TEXT";
+  auto expected = "";
+  EXPECT_EQ(expected, stripLeftMargin(input));
+}
+
+TEST(String, stripLeftMargin_one_line) {
+  auto input = R"TEXT(
+    hi there bob!
+  )TEXT";
+  auto expected = "hi there bob!\n";
+  EXPECT_EQ(expected, stripLeftMargin(input));
+}
+
+TEST(String, stripLeftMargin_two_lines) {
+  auto input = R"TEXT(
+    hi there bob!
+    nice weather today!
+  )TEXT";
+  auto expected = "hi there bob!\nnice weather today!\n";
+  EXPECT_EQ(expected, stripLeftMargin(input));
+}
+
+TEST(String, stripLeftMargin_three_lines_uneven) {
+  auto input = R"TEXT(
+      hi there bob!
+    nice weather today!
+      so long!
+  )TEXT";
+  auto expected = "  hi there bob!\nnice weather today!\n  so long!\n";
+  EXPECT_EQ(expected, stripLeftMargin(input));
+}
+
+TEST(String, stripLeftMargin_preceding_blank_lines) {
+  auto input = R"TEXT(
+
+
+    hi there bob!
+  )TEXT";
+  auto expected = "\n\nhi there bob!\n";
+  EXPECT_EQ(expected, stripLeftMargin(input));
+}
+
+TEST(String, stripLeftMargin_succeeding_blank_lines) {
+  auto input = R"TEXT(
+    hi there bob!
+
+
+  )TEXT";
+  auto expected = "hi there bob!\n\n\n";
+  EXPECT_EQ(expected, stripLeftMargin(input));
+}
+
+TEST(String, stripLeftMargin_interstitial_undented_whiteline) {
+  //  using ~ as a marker
+  string input = R"TEXT(
+      hi there bob!
+    ~
+      so long!
+  )TEXT";
+  input = boost::regex_replace(input, boost::regex(" +~"), "");
+  EXPECT_EQ("\n      hi there bob!\n\n      so long!\n  ", input);
+  auto expected = "hi there bob!\n\nso long!\n";
+  EXPECT_EQ(expected, stripLeftMargin(input));
+}
+
+TEST(String, stripLeftMargin_interstitial_dedented_whiteline) {
+  //  using ~ as a marker
+  string input = R"TEXT(
+      hi there bob!
+    ~
+      so long!
+  )TEXT";
+  input = boost::regex_replace(input, boost::regex("~"), "");
+  EXPECT_EQ("\n      hi there bob!\n    \n      so long!\n  ", input);
+  auto expected = "hi there bob!\n\nso long!\n";
+  EXPECT_EQ(expected, stripLeftMargin(input));
+}
+
+TEST(String, stripLeftMargin_interstitial_equidented_whiteline) {
+  //  using ~ as a marker
+  string input = R"TEXT(
+      hi there bob!
+      ~
+      so long!
+  )TEXT";
+  input = boost::regex_replace(input, boost::regex("~"), "");
+  EXPECT_EQ("\n      hi there bob!\n      \n      so long!\n  ", input);
+  auto expected = "hi there bob!\n\nso long!\n";
+  EXPECT_EQ(expected, stripLeftMargin(input));
+}
+
+TEST(String, stripLeftMargin_interstitial_indented_whiteline) {
+  //  using ~ as a marker
+  string input = R"TEXT(
+      hi there bob!
+        ~
+      so long!
+  )TEXT";
+  input = boost::regex_replace(input, boost::regex("~"), "");
+  EXPECT_EQ("\n      hi there bob!\n        \n      so long!\n  ", input);
+  auto expected = "hi there bob!\n  \nso long!\n";
+  EXPECT_EQ(expected, stripLeftMargin(input));
+}
+
 const folly::StringPiece kTestUTF8 = "This is \U0001F602 stuff!";
 
 TEST(UTF8StringPiece, valid_utf8) {