aboutsummaryrefslogtreecommitdiff
path: root/libbutl/manifest-serializer.mxx
blob: b73c255a6c5def942f4c21f02c176b1b98ecf331 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// file      : libbutl/manifest-serializer.mxx -*- C++ -*-
// license   : MIT; see accompanying LICENSE file

#ifndef __cpp_modules_ts
#pragma once
#endif

// C includes.

#ifndef __cpp_lib_modules_ts
#include <string>
#include <vector>
#include <iosfwd>
#include <cstddef>    // size_t
#include <stdexcept>  // runtime_error
#include <functional>
#endif

// Other includes.

#ifdef __cpp_modules_ts
export module butl.manifest_serializer;
#ifdef __cpp_lib_modules_ts
import std.core;
import std.io;
#endif
import butl.manifest_types;
#else
#include <libbutl/manifest-types.mxx>
#endif

#include <libbutl/export.hxx>

LIBBUTL_MODEXPORT namespace butl
{
  class LIBBUTL_SYMEXPORT manifest_serialization: public std::runtime_error
  {
  public:
    manifest_serialization (const std::string& name,
                            const std::string& description);

    std::string name;
    std::string description;
  };

  class LIBBUTL_SYMEXPORT manifest_serializer
  {
  public:
    // The filter, if specified, is called by next() prior to serializing the
    // pair into the stream. If the filter returns false, then the pair is
    // discarded.
    //
    // Note that currently there is no way for the filter to modify the name
    // or value. If we ever need this functionality, then we can add an
    // "extended" filter alternative with two "receiving" arguments:
    //
    // bool (..., optional<string>& n, optional<string>& v);
    //
    using filter_function = bool (const std::string& name,
                                  const std::string& value);

    // Unless long_lines is true, break lines in values (including multi-line)
    // so that their length does not exceed 78 codepoints (including '\n').
    //
    manifest_serializer (std::ostream& os,
                         const std::string& name,
                         bool long_lines = false,
                         std::function<filter_function> filter = {})
      : os_ (os),
        name_ (name),
        long_lines_ (long_lines),
        filter_ (std::move (filter))
    {
    }

    const std::string&
    name () const {return name_;}

    // The first name-value pair should be the special "start-of-manifest"
    // with empty name and value being the format version. After that we
    // have a sequence of ordinary pairs which are the manifest. At the
    // end of the manifest we have the special "end-of-manifest" pair
    // with empty name and value. After that we can either have another
    // start-of-manifest pair (in which case the whole sequence repeats
    // from the beginning) or we get another end-of-manifest pair which
    // signals the end of stream. The end-of-manifest pair can be omitted
    // if it is followed by the start-of-manifest pair.
    //
    void
    next (const std::string& name, const std::string& value);

    // Write a comment. The supplied text is prefixed with "# " and
    // terminated with a newline.
    //
    void
    comment (const std::string&);

    // Merge the manifest value and a comment into the single string, having
    // the '<value>; <comment>' form. Escape ';' characters in the value with
    // the backslash.
    //
    static std::string
    merge_comment (const std::string& value, const std::string& comment);

  private:
    friend class manifest_rewriter;

    void
    write_next (const std::string& name, const std::string& value);

    // Validate and write a name and return its length in codepoints.
    //
    size_t
    write_name (const std::string&);

    // Write a value assuming the current line already has the specified
    // codepoint offset. If the resulting line length would be too large then
    // the multi-line representation will be used. It is assumed that the
    // name, followed by the colon, is already written.
    //
    void
    write_value (const std::string&, std::size_t offset);

    // Write the specified number of characters from the specified string
    // (assuming there are no newlines) split into multiple lines at or near
    // the 78 codepoints boundary. Assume the current line already has the
    // specified codepoint offset.
    //
    void
    write_value (const char* s, std::size_t n, std::size_t offset);

  private:
    enum {start, body, end} s_ = start;
    std::string version_; // Current format version.

  private:
    std::ostream& os_;
    const std::string name_;
    bool long_lines_;
    const std::function<filter_function> filter_;
  };

  // Serialize a manifest to a stream adding the leading format version pair
  // and the trailing end-of-manifest pair. Unless eos is false, then also
  // write the end-of-stream pair.
  //
  LIBBUTL_SYMEXPORT void
  serialize_manifest (manifest_serializer&,
                      const std::vector<manifest_name_value>&,
                      bool eos = true);
}

#include <libbutl/manifest-serializer.ixx>