--- /dev/null
+#ifndef __PP_LINEAR_DOCUMENT_H__
+#define __PP_LINEAR_DOCUMENT_H__
+
+#include "pp/Format.h"
+#include "pp/IndentedStringBuilder.h"
+
+#include <vector>
+#include <string>
+
+namespace pp
+{
+
+class LinearDocument
+{
+public:
+ enum class Direction
+ {
+ Forward,
+ Reverse
+ };
+
+public:
+ LinearDocument() : _direction{Direction::Forward}
+ {
+ // DO NOTHING
+ }
+
+public:
+ LinearDocument(const Direction &direction) : _direction{direction}
+ {
+ // DO NOTHING
+ }
+
+public:
+ void indent(void);
+ void unindent(void);
+
+public:
+ void append(void);
+
+public:
+ void append(const std::string &line);
+ template <typename... Args> void append(const Args &... args) { append(fmt(args...)); }
+
+public:
+ void append(const LinearDocument &doc);
+
+public:
+ uint32_t lines(void) const { return _lines.size(); }
+
+public:
+ const std::string &line(uint32_t n) const;
+
+private:
+ Direction const _direction;
+ IndentedStringBuilder _indent;
+ std::vector<std::string> _lines;
+};
+
+} // namespace pp
+
+#endif // __PP_LINEAR_DOCUMENT_H__
--- /dev/null
+#include "pp/LinearDocument.h"
+
+#include <stdexcept>
+
+namespace pp
+{
+
+void LinearDocument::indent(void) { _indent.increase(); }
+void LinearDocument::unindent(void) { _indent.decrease(); }
+
+void LinearDocument::append(void)
+{
+ // NOTE Do NOT indent empty lines
+ _lines.emplace_back("");
+}
+
+void LinearDocument::append(const std::string &line)
+{
+ if (line.empty())
+ {
+ append();
+ return;
+ }
+
+ // Append indentation space(s), and insert the update string to lines
+ _lines.emplace_back(_indent.build(line));
+}
+
+void LinearDocument::append(const LinearDocument &doc)
+{
+ for (uint32_t n = 0; n < doc.lines(); ++n)
+ {
+ // NOTE Do NOT update _lines here and use append method
+ append(doc.line(n));
+ }
+}
+
+const std::string &LinearDocument::line(uint32_t n) const
+{
+ switch (_direction)
+ {
+ case Direction::Forward:
+ {
+ return _lines.at(n);
+ }
+ case Direction::Reverse:
+ {
+ return _lines.at(lines() - n - 1);
+ }
+ }
+
+ throw std::runtime_error{"unreachable"};
+}
+
+} // namespace pp
--- /dev/null
+#include "pp/LinearDocument.h"
+
+#include <gtest/gtest.h>
+
+TEST(LINEAR_DOCUMENT, append_void)
+{
+ pp::LinearDocument doc;
+
+ doc.indent();
+ doc.append();
+
+ ASSERT_EQ(doc.lines(), 1);
+ ASSERT_EQ(doc.line(0), "");
+}
+
+TEST(LINEAR_DOCUMENT, append_empty_string)
+{
+ pp::LinearDocument doc;
+
+ doc.indent();
+ doc.append("");
+
+ ASSERT_EQ(doc.lines(), 1);
+ ASSERT_EQ(doc.line(0), "");
+}
+
+TEST(LINEAR_DOCUMENT, formatted_append)
+{
+ pp::LinearDocument doc;
+
+ doc.append("Hello ", 1);
+ ASSERT_EQ(doc.lines(), 1);
+ ASSERT_EQ(doc.line(0), "Hello 1");
+}
+
+TEST(LINEAR_DOCUMENT, forward_append)
+{
+ pp::LinearDocument doc;
+
+ ASSERT_EQ(doc.lines(), 0);
+
+ doc.append("A");
+ doc.append("B");
+ doc.append("C");
+
+ ASSERT_EQ(doc.lines(), 3);
+ ASSERT_EQ(doc.line(0), "A");
+ ASSERT_EQ(doc.line(1), "B");
+ ASSERT_EQ(doc.line(2), "C");
+}
+
+TEST(LINEAR_DOCUMENT, reverse_append)
+{
+ pp::LinearDocument doc{pp::LinearDocument::Direction::Reverse};
+
+ ASSERT_EQ(doc.lines(), 0);
+
+ doc.append("A");
+ doc.append("B");
+ doc.append("C");
+
+ ASSERT_EQ(doc.lines(), 3);
+ ASSERT_EQ(doc.line(0), "C");
+ ASSERT_EQ(doc.line(1), "B");
+ ASSERT_EQ(doc.line(2), "A");
+}
+
+TEST(LINEAR_DOCUMENT, document_append)
+{
+ pp::LinearDocument doc{pp::LinearDocument::Direction::Forward};
+ pp::LinearDocument sub{pp::LinearDocument::Direction::Reverse};
+
+ doc.append("A");
+ doc.indent();
+
+ sub.append("D");
+ sub.indent();
+ sub.append("C");
+ sub.unindent();
+ sub.append("B");
+
+ doc.append(sub);
+ doc.unindent();
+ doc.append("E");
+
+ ASSERT_EQ(doc.lines(), 5);
+
+ ASSERT_EQ(doc.line(0), "A");
+ ASSERT_EQ(doc.line(1), " B");
+ ASSERT_EQ(doc.line(2), " C");
+ ASSERT_EQ(doc.line(3), " D");
+ ASSERT_EQ(doc.line(4), "E");
+}
+
+TEST(LINEAR_DOCUMENT, indent)
+{
+ pp::LinearDocument doc;
+
+ ASSERT_EQ(doc.lines(), 0);
+
+ doc.append("A");
+ doc.indent();
+ doc.append("B");
+ doc.unindent();
+ doc.append("C");
+
+ ASSERT_EQ(doc.lines(), 3);
+
+ ASSERT_EQ(doc.line(0), "A");
+ ASSERT_EQ(doc.line(1), " B");
+ ASSERT_EQ(doc.line(2), "C");
+}