aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/ext_depends/D-YAML/source/dyaml/dumper.d
blob: 51f232fe966c6fbb701bef0a932622019ec03d15 (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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
//          Copyright Ferdinand Majerech 2011.
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          http://www.boost.org/LICENSE_1_0.txt)

/**
 * YAML dumper.
 *
 * Code based on $(LINK2 http://www.pyyaml.org, PyYAML).
 */
module dyaml.dumper;

import std.array;
import std.range.primitives;
import std.typecons;

import dyaml.emitter;
import dyaml.event;
import dyaml.exception;
import dyaml.linebreak;
import dyaml.node;
import dyaml.representer;
import dyaml.resolver;
import dyaml.serializer;
import dyaml.style;
import dyaml.tagdirective;


/**
 * Dumps YAML documents to files or streams.
 *
 * User specified Representer and/or Resolver can be used to support new
 * tags / data types.
 *
 * Setters are provided to affect output details (style, etc.).
 */
auto dumper()
{
    auto dumper = Dumper();
    dumper.resolver = Resolver.withDefaultResolvers;
    return dumper;
}

struct Dumper
{
    private:
        //Indentation width.
        int indent_ = 2;
        //Tag directives to use.
        TagDirective[] tags_;
    public:
        //Resolver to resolve tags.
        Resolver resolver;
        //Write scalars in canonical form?
        bool canonical;
        //Preferred text width.
        uint textWidth = 80;
        //Line break to use. Unix by default.
        LineBreak lineBreak = LineBreak.unix;
        //YAML version string. Default is 1.1.
        string YAMLVersion = "1.1";
        //Always explicitly write document start? Default is no explicit start.
        bool explicitStart = false;
        //Always explicitly write document end? Default is no explicit end.
        bool explicitEnd = false;

        //Name of the output file or stream, used in error messages.
        string name = "<unknown>";

        // Default style for scalar nodes. If style is $(D ScalarStyle.invalid), the _style is chosen automatically.
        ScalarStyle defaultScalarStyle = ScalarStyle.invalid;
        // Default style for collection nodes. If style is $(D CollectionStyle.invalid), the _style is chosen automatically.
        CollectionStyle defaultCollectionStyle = CollectionStyle.invalid;

        @disable bool opEquals(ref Dumper);
        @disable int opCmp(ref Dumper);

        ///Set indentation width. 2 by default. Must not be zero.
        @property void indent(uint indent) pure @safe nothrow
        in
        {
            assert(indent != 0, "Can't use zero YAML indent width");
        }
        do
        {
            indent_ = indent;
        }

        /**
         * Specify tag directives.
         *
         * A tag directive specifies a shorthand notation for specifying _tags.
         * Each tag directive associates a handle with a prefix. This allows for
         * compact tag notation.
         *
         * Each handle specified MUST start and end with a '!' character
         * (a single character "!" handle is allowed as well).
         *
         * Only alphanumeric characters, '-', and '__' may be used in handles.
         *
         * Each prefix MUST not be empty.
         *
         * The "!!" handle is used for default YAML _tags with prefix
         * "tag:yaml.org,2002:". This can be overridden.
         *
         * Params:  tags = Tag directives (keys are handles, values are prefixes).
         */
        @property void tagDirectives(string[string] tags) pure @safe
        {
            TagDirective[] t;
            foreach(handle, prefix; tags)
            {
                assert(handle.length >= 1 && handle[0] == '!' && handle[$ - 1] == '!',
                       "A tag handle is empty or does not start and end with a " ~
                       "'!' character : " ~ handle);
                assert(prefix.length >= 1, "A tag prefix is empty");
                t ~= TagDirective(handle, prefix);
            }
            tags_ = t;
        }
        ///
        @safe unittest
        {
            auto dumper = dumper();
            string[string] directives;
            directives["!short!"] = "tag:long.org,2011:";
            //This will emit tags starting with "tag:long.org,2011"
            //with a "!short!" prefix instead.
            dumper.tagDirectives(directives);
            dumper.dump(new Appender!string(), Node("foo"));
        }

        /**
         * Dump one or more YAML _documents to the file/stream.
         *
         * Note that while you can call dump() multiple times on the same
         * dumper, you will end up writing multiple YAML "files" to the same
         * file/stream.
         *
         * Params:  documents = Documents to _dump (root nodes of the _documents).
         *
         * Throws:  YAMLException on error (e.g. invalid nodes,
         *          unable to write to file/stream).
         */
        void dump(CharacterType = char, Range)(Range range, Node[] documents ...)
            if (isOutputRange!(Range, CharacterType) &&
                isOutputRange!(Range, char) || isOutputRange!(Range, wchar) || isOutputRange!(Range, dchar))
        {
            try
            {
                auto emitter = new Emitter!(Range, CharacterType)(range, canonical, indent_, textWidth, lineBreak);
                auto serializer = Serializer(resolver, explicitStart ? Yes.explicitStart : No.explicitStart,
                                             explicitEnd ? Yes.explicitEnd : No.explicitEnd, YAMLVersion, tags_);
                serializer.startStream(emitter);
                foreach(ref document; documents)
                {
                    auto data = representData(document, defaultScalarStyle, defaultCollectionStyle);
                    serializer.serialize(emitter, data);
                }
                serializer.endStream(emitter);
            }
            catch(YAMLException e)
            {
                throw new YAMLException("Unable to dump YAML to stream "
                                        ~ name ~ " : " ~ e.msg, e.file, e.line);
            }
        }
}
///Write to a file
@safe unittest
{
    auto node = Node([1, 2, 3, 4, 5]);
    dumper().dump(new Appender!string(), node);
}
///Write multiple YAML documents to a file
@safe unittest
{
    auto node1 = Node([1, 2, 3, 4, 5]);
    auto node2 = Node("This document contains only one string");
    dumper().dump(new Appender!string(), node1, node2);
    //Or with an array:
    dumper().dump(new Appender!string(), [node1, node2]);
}
///Write to memory
@safe unittest
{
    auto stream = new Appender!string();
    auto node = Node([1, 2, 3, 4, 5]);
    dumper().dump(stream, node);
}
///Use a custom resolver to support custom data types and/or implicit tags
@safe unittest
{
    import std.regex : regex;
    auto node = Node([1, 2, 3, 4, 5]);
    auto dumper = dumper();
    dumper.resolver.addImplicitResolver("!tag", regex("A.*"), "A");
    dumper.dump(new Appender!string(), node);
}
/// Set default scalar style
@safe unittest
{
    auto stream = new Appender!string();
    auto node = Node("Hello world!");
    auto dumper = dumper();
    dumper.defaultScalarStyle = ScalarStyle.singleQuoted;
    dumper.dump(stream, node);
}
/// Set default collection style
@safe unittest
{
    auto stream = new Appender!string();
    auto node = Node(["Hello", "world!"]);
    auto dumper = dumper();
    dumper.defaultCollectionStyle = CollectionStyle.flow;
    dumper.dump(stream, node);
}
// Make sure the styles are actually used
@safe unittest
{
    auto stream = new Appender!string();
    auto node = Node([Node("Hello world!"), Node(["Hello", "world!"])]);
    auto dumper = dumper();
    dumper.defaultScalarStyle = ScalarStyle.singleQuoted;
    dumper.defaultCollectionStyle = CollectionStyle.flow;
    dumper.explicitEnd = false;
    dumper.explicitStart = false;
    dumper.YAMLVersion = null;
    dumper.dump(stream, node);
    assert(stream.data == "[!!str 'Hello world!', [!!str 'Hello', !!str 'world!']]\n");
}
// Explicit document start/end markers
@safe unittest
{
    auto stream = new Appender!string();
    auto node = Node([1, 2, 3, 4, 5]);
    auto dumper = dumper();
    dumper.explicitEnd = true;
    dumper.explicitStart = true;
    dumper.YAMLVersion = null;
    dumper.dump(stream, node);
    //Skip version string
    assert(stream.data[0..3] == "---");
    //account for newline at end
    assert(stream.data[$-4..$-1] == "...");
}
// No explicit document start/end markers
@safe unittest
{
    auto stream = new Appender!string();
    auto node = Node([1, 2, 3, 4, 5]);
    auto dumper = dumper();
    dumper.explicitEnd = false;
    dumper.explicitStart = false;
    dumper.YAMLVersion = null;
    dumper.dump(stream, node);
    //Skip version string
    assert(stream.data[0..3] != "---");
    //account for newline at end
    assert(stream.data[$-4..$-1] != "...");
}
// Windows, macOS line breaks
@safe unittest
{
    auto node = Node(0);
    {
        auto stream = new Appender!string();
        auto dumper = dumper();
        dumper.explicitEnd = true;
        dumper.explicitStart = true;
        dumper.YAMLVersion = null;
        dumper.lineBreak = LineBreak.windows;
        dumper.dump(stream, node);
        assert(stream.data == "--- 0\r\n...\r\n");
    }
    {
        auto stream = new Appender!string();
        auto dumper = dumper();
        dumper.explicitEnd = true;
        dumper.explicitStart = true;
        dumper.YAMLVersion = null;
        dumper.lineBreak = LineBreak.macintosh;
        dumper.dump(stream, node);
        assert(stream.data == "--- 0\r...\r");
    }
}