"vscode:/vscode.git/clone" did not exist on "3bc43c683e6297cbc6b01e2d2468ca2b25052710"
Commit f56d4530 authored by beder's avatar beder
Browse files

Added api sketch

parents
#ifndef NONCOPYABLE_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#define NONCOPYABLE_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#if !defined(__GNUC__) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4) || (__GNUC__ >= 4) // GCC supports "pragma once" correctly since 3.4
#pragma once
#endif
#include "yaml-cpp/dll.h"
namespace YAML
{
// this is basically boost::noncopyable
class YAML_CPP_API noncopyable
{
protected:
noncopyable() {}
~noncopyable() {}
private:
noncopyable(const noncopyable&);
const noncopyable& operator = (const noncopyable&);
};
}
#endif // NONCOPYABLE_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#ifndef NULL_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#define NULL_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#if !defined(__GNUC__) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4) || (__GNUC__ >= 4) // GCC supports "pragma once" correctly since 3.4
#pragma once
#endif
#include "yaml-cpp/dll.h"
namespace YAML
{
class Node;
struct YAML_CPP_API _Null {};
inline bool operator == (const _Null&, const _Null&) { return true; }
inline bool operator != (const _Null&, const _Null&) { return false; }
YAML_CPP_API bool IsNull(const Node& node);
extern YAML_CPP_API _Null Null;
}
#endif // NULL_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#ifndef OSTREAM_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#define OSTREAM_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#if !defined(__GNUC__) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4) || (__GNUC__ >= 4) // GCC supports "pragma once" correctly since 3.4
#pragma once
#endif
#include <string>
namespace YAML
{
class ostream
{
public:
ostream();
~ostream();
void reserve(unsigned size);
void put(char ch);
const char *str() const { return m_buffer; }
unsigned row() const { return m_row; }
unsigned col() const { return m_col; }
unsigned pos() const { return m_pos; }
private:
char *m_buffer;
unsigned m_pos;
unsigned m_size;
unsigned m_row, m_col;
};
ostream& operator << (ostream& out, const char *str);
ostream& operator << (ostream& out, const std::string& str);
ostream& operator << (ostream& out, char ch);
}
#endif // OSTREAM_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#ifndef PARSER_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#define PARSER_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#if !defined(__GNUC__) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4) || (__GNUC__ >= 4) // GCC supports "pragma once" correctly since 3.4
#pragma once
#endif
#include "yaml-cpp/dll.h"
#include "yaml-cpp/noncopyable.h"
#include <ios>
#include <memory>
namespace YAML
{
struct Directives;
struct Mark;
struct Token;
class EventHandler;
class Node;
class Scanner;
class YAML_CPP_API Parser: private noncopyable
{
public:
Parser();
Parser(std::istream& in);
~Parser();
operator bool() const;
void Load(std::istream& in);
bool HandleNextDocument(EventHandler& eventHandler);
bool GetNextDocument(Node& document);
void PrintTokens(std::ostream& out);
private:
void ParseDirectives();
void HandleDirective(const Token& token);
void HandleYamlDirective(const Token& token);
void HandleTagDirective(const Token& token);
private:
std::auto_ptr<Scanner> m_pScanner;
std::auto_ptr<Directives> m_pDirectives;
};
}
#endif // PARSER_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#ifndef STLEMITTER_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#define STLEMITTER_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#if !defined(__GNUC__) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4) || (__GNUC__ >= 4) // GCC supports "pragma once" correctly since 3.4
#pragma once
#endif
#include <vector>
#include <list>
#include <set>
#include <map>
namespace YAML
{
template<typename Seq>
inline Emitter& EmitSeq(Emitter& emitter, const Seq& seq) {
emitter << BeginSeq;
for(typename Seq::const_iterator it=seq.begin();it!=seq.end();++it)
emitter << *it;
emitter << EndSeq;
return emitter;
}
template<typename T>
inline Emitter& operator << (Emitter& emitter, const std::vector<T>& v) {
return EmitSeq(emitter, v);
}
template<typename T>
inline Emitter& operator << (Emitter& emitter, const std::list<T>& v) {
return EmitSeq(emitter, v);
}
template<typename T>
inline Emitter& operator << (Emitter& emitter, const std::set<T>& v) {
return EmitSeq(emitter, v);
}
template <typename K, typename V>
inline Emitter& operator << (Emitter& emitter, const std::map<K, V>& m) {
typedef typename std::map <K, V> map;
emitter << BeginMap;
for(typename map::const_iterator it=m.begin();it!=m.end();++it)
emitter << Key << it->first << Value << it->second;
emitter << EndMap;
return emitter;
}
}
#endif // STLEMITTER_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#ifndef STLNODE_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#define STLNODE_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#if !defined(__GNUC__) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4) || (__GNUC__ >= 4) // GCC supports "pragma once" correctly since 3.4
#pragma once
#endif
#include <vector>
#include <map>
namespace YAML
{
template <typename T>
void operator >> (const Node& node, std::vector<T>& v)
{
v.clear();
v.resize(node.size());
for(unsigned i=0;i<node.size();++i)
node[i] >> v[i];
}
template <typename K, typename V>
void operator >> (const Node& node, std::map<K, V>& m)
{
m.clear();
for(Iterator it=node.begin();it!=node.end();++it) {
K k;
V v;
it.first() >> k;
it.second() >> v;
m[k] = v;
}
}
}
#endif // STLNODE_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#ifndef TRAITS_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#define TRAITS_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#if !defined(__GNUC__) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4) || (__GNUC__ >= 4) // GCC supports "pragma once" correctly since 3.4
#pragma once
#endif
namespace YAML
{
template <typename>
struct is_numeric { enum { value = false }; };
template <> struct is_numeric <char> { enum { value = true }; };
template <> struct is_numeric <unsigned char> { enum { value = true }; };
template <> struct is_numeric <int> { enum { value = true }; };
template <> struct is_numeric <unsigned int> { enum { value = true }; };
template <> struct is_numeric <long int> { enum { value = true }; };
template <> struct is_numeric <unsigned long int> { enum { value = true }; };
template <> struct is_numeric <short int> { enum { value = true }; };
template <> struct is_numeric <unsigned short int> { enum { value = true }; };
#if defined(_MSC_VER) && (_MSC_VER < 1310)
template <> struct is_numeric <__int64> { enum { value = true }; };
template <> struct is_numeric <unsigned __int64> { enum { value = true }; };
#else
template <> struct is_numeric <long long> { enum { value = true }; };
template <> struct is_numeric <unsigned long long> { enum { value = true }; };
#endif
template <> struct is_numeric <float> { enum { value = true }; };
template <> struct is_numeric <double> { enum { value = true }; };
template <> struct is_numeric <long double> { enum { value = true }; };
template <bool, class T = void>
struct enable_if_c {
typedef T type;
};
template <class T>
struct enable_if_c<false, T> {};
template <class Cond, class T = void>
struct enable_if : public enable_if_c<Cond::value, T> {};
template <bool, class T = void>
struct disable_if_c {
typedef T type;
};
template <class T>
struct disable_if_c<true, T> {};
template <class Cond, class T = void>
struct disable_if : public disable_if_c<Cond::value, T> {};
}
#endif // TRAITS_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#ifndef YAML_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#define YAML_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#if !defined(__GNUC__) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4) || (__GNUC__ >= 4) // GCC supports "pragma once" correctly since 3.4
#pragma once
#endif
#include "yaml-cpp/parser.h"
#include "yaml-cpp/node.h"
#include "yaml-cpp/stlnode.h"
#include "yaml-cpp/iterator.h"
#include "yaml-cpp/emitter.h"
#include "yaml-cpp/stlemitter.h"
#include "yaml-cpp/exceptions.h"
#endif // YAML_H_62B23520_7C8E_11DE_8A39_0800200C9A66
*** With CMake ***
yaml-cpp uses CMake to support cross-platform building. In a UNIX-like system, the basic steps to build are:
1. Download and install CMake (if you don't have root privileges, just install to a local directory, like ~/bin)
2. From the source directory, run:
mkdir build
cd build
cmake ..
and then the usual
make
make install
3. To clean up, just remove the 'build' directory.
*** Without CMake ***
If you don't want to use CMake, just add all .cpp files to a makefile. yaml-cpp does not need any special build settings, so no 'configure' file is necessary.
(Note: this is pretty tedious. It's sooo much easier to use CMake.)
Copyright (c) 2008 Jesse Beder.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
#include "yaml-cpp/aliasmanager.h"
#include "yaml-cpp/node.h"
#include <cassert>
#include <sstream>
namespace YAML
{
AliasManager::AliasManager(): m_curAnchor(0)
{
}
void AliasManager::RegisterReference(const Node& node)
{
m_anchorByIdentity.insert(std::make_pair(&node, _CreateNewAnchor()));
}
anchor_t AliasManager::LookupAnchor(const Node& node) const
{
AnchorByIdentity::const_iterator it = m_anchorByIdentity.find(&node);
if(it == m_anchorByIdentity.end())
return 0;
return it->second;
}
anchor_t AliasManager::_CreateNewAnchor()
{
return ++m_curAnchor;
}
}
#ifndef COLLECTIONSTACK_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#define COLLECTIONSTACK_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#if !defined(__GNUC__) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4) || (__GNUC__ >= 4) // GCC supports "pragma once" correctly since 3.4
#pragma once
#endif
#include <stack>
#include <cassert>
namespace YAML
{
struct CollectionType {
enum value { None, BlockMap, BlockSeq, FlowMap, FlowSeq, CompactMap };
};
class CollectionStack
{
public:
CollectionType::value GetCurCollectionType() const {
if(collectionStack.empty())
return CollectionType::None;
return collectionStack.top();
}
void PushCollectionType(CollectionType::value type) { collectionStack.push(type); }
void PopCollectionType(CollectionType::value type) { assert(type == GetCurCollectionType()); collectionStack.pop(); }
private:
std::stack<CollectionType::value> collectionStack;
};
}
#endif // COLLECTIONSTACK_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#include "yaml-cpp/parser.h"
#include "yaml-cpp/contrib/graphbuilder.h"
#include "graphbuilderadapter.h"
namespace YAML
{
void *BuildGraphOfNextDocument(Parser& parser, GraphBuilderInterface& graphBuilder)
{
GraphBuilderAdapter eventHandler(graphBuilder);
if (parser.HandleNextDocument(eventHandler)) {
return eventHandler.RootNode();
} else {
return NULL;
}
}
}
#include "graphbuilderadapter.h"
namespace YAML
{
int GraphBuilderAdapter::ContainerFrame::sequenceMarker;
void GraphBuilderAdapter::OnNull(const Mark& mark, anchor_t anchor)
{
void *pParent = GetCurrentParent();
void *pNode = m_builder.NewNull(mark, pParent);
RegisterAnchor(anchor, pNode);
DispositionNode(pNode);
}
void GraphBuilderAdapter::OnAlias(const Mark& mark, anchor_t anchor)
{
void *pReffedNode = m_anchors.Get(anchor);
DispositionNode(m_builder.AnchorReference(mark, pReffedNode));
}
void GraphBuilderAdapter::OnScalar(const Mark& mark, const std::string& tag, anchor_t anchor, const std::string& value)
{
void *pParent = GetCurrentParent();
void *pNode = m_builder.NewScalar(mark, tag, pParent, value);
RegisterAnchor(anchor, pNode);
DispositionNode(pNode);
}
void GraphBuilderAdapter::OnSequenceStart(const Mark& mark, const std::string& tag, anchor_t anchor)
{
void *pNode = m_builder.NewSequence(mark, tag, GetCurrentParent());
m_containers.push(ContainerFrame(pNode));
RegisterAnchor(anchor, pNode);
}
void GraphBuilderAdapter::OnSequenceEnd()
{
void *pSequence = m_containers.top().pContainer;
m_containers.pop();
DispositionNode(pSequence);
}
void GraphBuilderAdapter::OnMapStart(const Mark& mark, const std::string& tag, anchor_t anchor)
{
void *pNode = m_builder.NewMap(mark, tag, GetCurrentParent());
m_containers.push(ContainerFrame(pNode, m_pKeyNode));
m_pKeyNode = NULL;
RegisterAnchor(anchor, pNode);
}
void GraphBuilderAdapter::OnMapEnd()
{
void *pMap = m_containers.top().pContainer;
m_pKeyNode = m_containers.top().pPrevKeyNode;
m_containers.pop();
DispositionNode(pMap);
}
void *GraphBuilderAdapter::GetCurrentParent() const
{
if (m_containers.empty()) {
return NULL;
}
return m_containers.top().pContainer;
}
void GraphBuilderAdapter::RegisterAnchor(anchor_t anchor, void *pNode)
{
if (anchor) {
m_anchors.Register(anchor, pNode);
}
}
void GraphBuilderAdapter::DispositionNode(void *pNode)
{
if (m_containers.empty()) {
m_pRootNode = pNode;
return;
}
void *pContainer = m_containers.top().pContainer;
if (m_containers.top().isMap()) {
if (m_pKeyNode) {
m_builder.AssignInMap(pContainer, m_pKeyNode, pNode);
m_pKeyNode = NULL;
} else {
m_pKeyNode = pNode;
}
} else {
m_builder.AppendToSequence(pContainer, pNode);
}
}
}
#ifndef GRAPHBUILDERADAPTER_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#define GRAPHBUILDERADAPTER_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#if !defined(__GNUC__) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4) || (__GNUC__ >= 4) // GCC supports "pragma once" correctly since 3.4
#pragma once
#endif
#include <cstdlib>
#include <map>
#include <stack>
#include "yaml-cpp/eventhandler.h"
#include "yaml-cpp/contrib/anchordict.h"
#include "yaml-cpp/contrib/graphbuilder.h"
namespace YAML
{
class GraphBuilderAdapter : public EventHandler
{
public:
GraphBuilderAdapter(GraphBuilderInterface& builder)
: m_builder(builder), m_pRootNode(NULL), m_pKeyNode(NULL)
{
}
virtual void OnDocumentStart(const Mark& mark) {(void)mark;}
virtual void OnDocumentEnd() {}
virtual void OnNull(const Mark& mark, anchor_t anchor);
virtual void OnAlias(const Mark& mark, anchor_t anchor);
virtual void OnScalar(const Mark& mark, const std::string& tag, anchor_t anchor, const std::string& value);
virtual void OnSequenceStart(const Mark& mark, const std::string& tag, anchor_t anchor);
virtual void OnSequenceEnd();
virtual void OnMapStart(const Mark& mark, const std::string& tag, anchor_t anchor);
virtual void OnMapEnd();
void *RootNode() const {return m_pRootNode;}
private:
struct ContainerFrame
{
ContainerFrame(void *pSequence)
: pContainer(pSequence), pPrevKeyNode(&sequenceMarker)
{}
ContainerFrame(void *pMap, void* pPrevKeyNode)
: pContainer(pMap), pPrevKeyNode(pPrevKeyNode)
{}
void *pContainer;
void *pPrevKeyNode;
bool isMap() const {return pPrevKeyNode != &sequenceMarker;}
private:
static int sequenceMarker;
};
typedef std::stack<ContainerFrame> ContainerStack;
typedef AnchorDict<void*> AnchorMap;
GraphBuilderInterface& m_builder;
ContainerStack m_containers;
AnchorMap m_anchors;
void *m_pRootNode;
void *m_pKeyNode;
void *GetCurrentParent() const;
void RegisterAnchor(anchor_t anchor, void *pNode);
void DispositionNode(void *pNode);
};
}
#endif // GRAPHBUILDERADAPTER_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#include "yaml-cpp/conversion.h"
#include <algorithm>
////////////////////////////////////////////////////////////////
// Specializations for converting a string to specific types
namespace
{
// we're not gonna mess with the mess that is all the isupper/etc. functions
bool IsLower(char ch) { return 'a' <= ch && ch <= 'z'; }
bool IsUpper(char ch) { return 'A' <= ch && ch <= 'Z'; }
char ToLower(char ch) { return IsUpper(ch) ? ch + 'a' - 'A' : ch; }
std::string tolower(const std::string& str)
{
std::string s(str);
std::transform(s.begin(), s.end(), s.begin(), ToLower);
return s;
}
template <typename T>
bool IsEntirely(const std::string& str, T func)
{
for(std::size_t i=0;i<str.size();i++)
if(!func(str[i]))
return false;
return true;
}
// IsFlexibleCase
// . Returns true if 'str' is:
// . UPPERCASE
// . lowercase
// . Capitalized
bool IsFlexibleCase(const std::string& str)
{
if(str.empty())
return true;
if(IsEntirely(str, IsLower))
return true;
bool firstcaps = IsUpper(str[0]);
std::string rest = str.substr(1);
return firstcaps && (IsEntirely(rest, IsLower) || IsEntirely(rest, IsUpper));
}
}
namespace YAML
{
bool Convert(const std::string& input, bool& b)
{
// we can't use iostream bool extraction operators as they don't
// recognize all possible values in the table below (taken from
// http://yaml.org/type/bool.html)
static const struct {
std::string truename, falsename;
} names[] = {
{ "y", "n" },
{ "yes", "no" },
{ "true", "false" },
{ "on", "off" },
};
if(!IsFlexibleCase(input))
return false;
for(unsigned i=0;i<sizeof(names)/sizeof(names[0]);i++) {
if(names[i].truename == tolower(input)) {
b = true;
return true;
}
if(names[i].falsename == tolower(input)) {
b = false;
return true;
}
}
return false;
}
bool Convert(const std::string& input, _Null& /*output*/)
{
return input.empty() || input == "~" || input == "null" || input == "Null" || input == "NULL";
}
}
#include "directives.h"
namespace YAML
{
Directives::Directives()
{
// version
version.isDefault = true;
version.major = 1;
version.minor = 2;
}
const std::string Directives::TranslateTagHandle(const std::string& handle) const
{
std::map <std::string, std::string>::const_iterator it = tags.find(handle);
if(it == tags.end()) {
if(handle == "!!")
return "tag:yaml.org,2002:";
return handle;
}
return it->second;
}
}
#ifndef DIRECTIVES_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#define DIRECTIVES_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#if !defined(__GNUC__) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4) || (__GNUC__ >= 4) // GCC supports "pragma once" correctly since 3.4
#pragma once
#endif
#include <string>
#include <map>
namespace YAML
{
struct Version {
bool isDefault;
int major, minor;
};
struct Directives {
Directives();
const std::string TranslateTagHandle(const std::string& handle) const;
Version version;
std::map<std::string, std::string> tags;
};
}
#endif // DIRECTIVES_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#include "yaml-cpp/emitfromevents.h"
#include "yaml-cpp/emitter.h"
#include "yaml-cpp/null.h"
#include <cassert>
#include <sstream>
namespace {
std::string ToString(YAML::anchor_t anchor) {
std::stringstream stream;
stream << anchor;
return stream.str();
}
}
namespace YAML
{
EmitFromEvents::EmitFromEvents(Emitter& emitter): m_emitter(emitter)
{
}
void EmitFromEvents::OnDocumentStart(const Mark&)
{
}
void EmitFromEvents::OnDocumentEnd()
{
}
void EmitFromEvents::OnNull(const Mark&, anchor_t anchor)
{
BeginNode();
EmitProps("", anchor);
m_emitter << Null;
}
void EmitFromEvents::OnAlias(const Mark&, anchor_t anchor)
{
BeginNode();
m_emitter << Alias(ToString(anchor));
}
void EmitFromEvents::OnScalar(const Mark&, const std::string& tag, anchor_t anchor, const std::string& value)
{
BeginNode();
EmitProps(tag, anchor);
m_emitter << value;
}
void EmitFromEvents::OnSequenceStart(const Mark&, const std::string& tag, anchor_t anchor)
{
BeginNode();
EmitProps(tag, anchor);
m_emitter << BeginSeq;
m_stateStack.push(State::WaitingForSequenceEntry);
}
void EmitFromEvents::OnSequenceEnd()
{
m_emitter << EndSeq;
assert(m_stateStack.top() == State::WaitingForSequenceEntry);
m_stateStack.pop();
}
void EmitFromEvents::OnMapStart(const Mark&, const std::string& tag, anchor_t anchor)
{
BeginNode();
EmitProps(tag, anchor);
m_emitter << BeginMap;
m_stateStack.push(State::WaitingForKey);
}
void EmitFromEvents::OnMapEnd()
{
m_emitter << EndMap;
assert(m_stateStack.top() == State::WaitingForKey);
m_stateStack.pop();
}
void EmitFromEvents::BeginNode()
{
if(m_stateStack.empty())
return;
switch(m_stateStack.top()) {
case State::WaitingForKey:
m_emitter << Key;
m_stateStack.top() = State::WaitingForValue;
break;
case State::WaitingForValue:
m_emitter << Value;
m_stateStack.top() = State::WaitingForKey;
break;
default:
break;
}
}
void EmitFromEvents::EmitProps(const std::string& tag, anchor_t anchor)
{
if(!tag.empty() && tag != "?")
m_emitter << VerbatimTag(tag);
if(anchor)
m_emitter << Anchor(ToString(anchor));
}
}
#include "yaml-cpp/emitter.h"
#include "emitterstate.h"
#include "emitterutils.h"
#include "indentation.h"
#include "yaml-cpp/exceptions.h"
#include <sstream>
namespace YAML
{
Emitter::Emitter(): m_pState(new EmitterState)
{
}
Emitter::~Emitter()
{
}
const char *Emitter::c_str() const
{
return m_stream.str();
}
unsigned Emitter::size() const
{
return m_stream.pos();
}
// state checking
bool Emitter::good() const
{
return m_pState->good();
}
const std::string Emitter::GetLastError() const
{
return m_pState->GetLastError();
}
// global setters
bool Emitter::SetOutputCharset(EMITTER_MANIP value)
{
return m_pState->SetOutputCharset(value, GLOBAL);
}
bool Emitter::SetStringFormat(EMITTER_MANIP value)
{
return m_pState->SetStringFormat(value, GLOBAL);
}
bool Emitter::SetBoolFormat(EMITTER_MANIP value)
{
bool ok = false;
if(m_pState->SetBoolFormat(value, GLOBAL))
ok = true;
if(m_pState->SetBoolCaseFormat(value, GLOBAL))
ok = true;
if(m_pState->SetBoolLengthFormat(value, GLOBAL))
ok = true;
return ok;
}
bool Emitter::SetIntBase(EMITTER_MANIP value)
{
return m_pState->SetIntFormat(value, GLOBAL);
}
bool Emitter::SetSeqFormat(EMITTER_MANIP value)
{
return m_pState->SetFlowType(GT_SEQ, value, GLOBAL);
}
bool Emitter::SetMapFormat(EMITTER_MANIP value)
{
bool ok = false;
if(m_pState->SetFlowType(GT_MAP, value, GLOBAL))
ok = true;
if(m_pState->SetMapKeyFormat(value, GLOBAL))
ok = true;
return ok;
}
bool Emitter::SetIndent(unsigned n)
{
return m_pState->SetIndent(n, GLOBAL);
}
bool Emitter::SetPreCommentIndent(unsigned n)
{
return m_pState->SetPreCommentIndent(n, GLOBAL);
}
bool Emitter::SetPostCommentIndent(unsigned n)
{
return m_pState->SetPostCommentIndent(n, GLOBAL);
}
// SetLocalValue
// . Either start/end a group, or set a modifier locally
Emitter& Emitter::SetLocalValue(EMITTER_MANIP value)
{
if(!good())
return *this;
switch(value) {
case BeginDoc:
EmitBeginDoc();
break;
case EndDoc:
EmitEndDoc();
break;
case BeginSeq:
EmitBeginSeq();
break;
case EndSeq:
EmitEndSeq();
break;
case BeginMap:
EmitBeginMap();
break;
case EndMap:
EmitEndMap();
break;
case Key:
EmitKey();
break;
case Value:
EmitValue();
break;
case TagByKind:
EmitKindTag();
break;
case Newline:
EmitNewline();
break;
default:
m_pState->SetLocalValue(value);
break;
}
return *this;
}
Emitter& Emitter::SetLocalIndent(const _Indent& indent)
{
m_pState->SetIndent(indent.value, LOCAL);
return *this;
}
// GotoNextPreAtomicState
// . Runs the state machine, emitting if necessary, and returns 'true' if done (i.e., ready to emit an atom)
bool Emitter::GotoNextPreAtomicState()
{
if(!good())
return true;
unsigned curIndent = m_pState->GetCurIndent();
EMITTER_STATE curState = m_pState->GetCurState();
switch(curState) {
// document-level
case ES_WAITING_FOR_DOC:
m_pState->SwitchState(ES_WRITING_DOC);
return true;
case ES_WRITING_DOC:
return true;
case ES_DONE_WITH_DOC:
EmitBeginDoc();
return false;
// block sequence
case ES_WAITING_FOR_BLOCK_SEQ_ENTRY:
m_stream << IndentTo(curIndent) << "-";
m_pState->RequireSoftSeparation();
m_pState->SwitchState(ES_WRITING_BLOCK_SEQ_ENTRY);
return true;
case ES_WRITING_BLOCK_SEQ_ENTRY:
return true;
case ES_DONE_WITH_BLOCK_SEQ_ENTRY:
m_stream << '\n';
m_pState->SwitchState(ES_WAITING_FOR_BLOCK_SEQ_ENTRY);
return false;
// flow sequence
case ES_WAITING_FOR_FLOW_SEQ_ENTRY:
m_pState->SwitchState(ES_WRITING_FLOW_SEQ_ENTRY);
return true;
case ES_WRITING_FLOW_SEQ_ENTRY:
return true;
case ES_DONE_WITH_FLOW_SEQ_ENTRY:
EmitSeparationIfNecessary();
m_stream << ',';
m_pState->RequireSoftSeparation();
m_pState->SwitchState(ES_WAITING_FOR_FLOW_SEQ_ENTRY);
return false;
// block map
case ES_WAITING_FOR_BLOCK_MAP_ENTRY:
m_pState->SetError(ErrorMsg::EXPECTED_KEY_TOKEN);
return true;
case ES_WAITING_FOR_BLOCK_MAP_KEY:
if(m_pState->CurrentlyInLongKey()) {
m_stream << IndentTo(curIndent) << '?';
m_pState->RequireSoftSeparation();
}
m_pState->SwitchState(ES_WRITING_BLOCK_MAP_KEY);
return true;
case ES_WRITING_BLOCK_MAP_KEY:
return true;
case ES_DONE_WITH_BLOCK_MAP_KEY:
m_pState->SetError(ErrorMsg::EXPECTED_VALUE_TOKEN);
return true;
case ES_WAITING_FOR_BLOCK_MAP_VALUE:
m_pState->SwitchState(ES_WRITING_BLOCK_MAP_VALUE);
return true;
case ES_WRITING_BLOCK_MAP_VALUE:
return true;
case ES_DONE_WITH_BLOCK_MAP_VALUE:
m_pState->SetError(ErrorMsg::EXPECTED_KEY_TOKEN);
return true;
// flow map
case ES_WAITING_FOR_FLOW_MAP_ENTRY:
m_pState->SetError(ErrorMsg::EXPECTED_KEY_TOKEN);
return true;
case ES_WAITING_FOR_FLOW_MAP_KEY:
EmitSeparationIfNecessary();
m_pState->SwitchState(ES_WRITING_FLOW_MAP_KEY);
if(m_pState->CurrentlyInLongKey()) {
m_stream << '?';
m_pState->RequireSoftSeparation();
}
return true;
case ES_WRITING_FLOW_MAP_KEY:
return true;
case ES_DONE_WITH_FLOW_MAP_KEY:
m_pState->SetError(ErrorMsg::EXPECTED_VALUE_TOKEN);
return true;
case ES_WAITING_FOR_FLOW_MAP_VALUE:
EmitSeparationIfNecessary();
m_stream << ':';
m_pState->RequireSoftSeparation();
m_pState->SwitchState(ES_WRITING_FLOW_MAP_VALUE);
return true;
case ES_WRITING_FLOW_MAP_VALUE:
return true;
case ES_DONE_WITH_FLOW_MAP_VALUE:
m_pState->SetError(ErrorMsg::EXPECTED_KEY_TOKEN);
return true;
default:
assert(false);
}
assert(false);
return true;
}
// PreAtomicWrite
// . Depending on the emitter state, write to the stream to get it
// in position to do an atomic write (e.g., scalar, sequence, or map)
void Emitter::PreAtomicWrite()
{
if(!good())
return;
while(!GotoNextPreAtomicState())
;
}
// PostAtomicWrite
// . Clean up
void Emitter::PostAtomicWrite()
{
if(!good())
return;
EMITTER_STATE curState = m_pState->GetCurState();
switch(curState) {
// document-level
case ES_WRITING_DOC:
m_pState->SwitchState(ES_DONE_WITH_DOC);
break;
// block seq
case ES_WRITING_BLOCK_SEQ_ENTRY:
m_pState->SwitchState(ES_DONE_WITH_BLOCK_SEQ_ENTRY);
break;
// flow seq
case ES_WRITING_FLOW_SEQ_ENTRY:
m_pState->SwitchState(ES_DONE_WITH_FLOW_SEQ_ENTRY);
break;
// block map
case ES_WRITING_BLOCK_MAP_KEY:
if(!m_pState->CurrentlyInLongKey()) {
m_stream << ':';
m_pState->RequireSoftSeparation();
}
m_pState->SwitchState(ES_DONE_WITH_BLOCK_MAP_KEY);
break;
case ES_WRITING_BLOCK_MAP_VALUE:
m_pState->SwitchState(ES_DONE_WITH_BLOCK_MAP_VALUE);
break;
// flow map
case ES_WRITING_FLOW_MAP_KEY:
m_pState->SwitchState(ES_DONE_WITH_FLOW_MAP_KEY);
break;
case ES_WRITING_FLOW_MAP_VALUE:
m_pState->SwitchState(ES_DONE_WITH_FLOW_MAP_VALUE);
break;
default:
assert(false);
};
m_pState->ClearModifiedSettings();
}
// EmitSeparationIfNecessary
void Emitter::EmitSeparationIfNecessary()
{
if(!good())
return;
if(m_pState->RequiresSoftSeparation())
m_stream << ' ';
else if(m_pState->RequiresHardSeparation())
m_stream << '\n';
m_pState->UnsetSeparation();
}
// EmitBeginDoc
void Emitter::EmitBeginDoc()
{
if(!good())
return;
EMITTER_STATE curState = m_pState->GetCurState();
if(curState != ES_WAITING_FOR_DOC && curState != ES_WRITING_DOC && curState != ES_DONE_WITH_DOC) {
m_pState->SetError("Unexpected begin document");
return;
}
if(curState == ES_WRITING_DOC || curState == ES_DONE_WITH_DOC)
m_stream << '\n';
m_stream << "---\n";
m_pState->UnsetSeparation();
m_pState->SwitchState(ES_WAITING_FOR_DOC);
}
// EmitEndDoc
void Emitter::EmitEndDoc()
{
if(!good())
return;
EMITTER_STATE curState = m_pState->GetCurState();
if(curState != ES_WAITING_FOR_DOC && curState != ES_WRITING_DOC && curState != ES_DONE_WITH_DOC) {
m_pState->SetError("Unexpected end document");
return;
}
if(curState == ES_WRITING_DOC || curState == ES_DONE_WITH_DOC)
m_stream << '\n';
m_stream << "...\n";
m_pState->UnsetSeparation();
m_pState->SwitchState(ES_WAITING_FOR_DOC);
}
// EmitBeginSeq
void Emitter::EmitBeginSeq()
{
if(!good())
return;
// must have a long key if we're emitting a sequence
m_pState->StartLongKey();
PreAtomicWrite();
EMITTER_STATE curState = m_pState->GetCurState();
EMITTER_MANIP flowType = m_pState->GetFlowType(GT_SEQ);
if(flowType == Block) {
if(curState == ES_WRITING_BLOCK_SEQ_ENTRY ||
curState == ES_WRITING_BLOCK_MAP_KEY || curState == ES_WRITING_BLOCK_MAP_VALUE ||
curState == ES_WRITING_DOC
) {
if(m_pState->RequiresHardSeparation() || curState != ES_WRITING_DOC) {
m_stream << "\n";
m_pState->UnsetSeparation();
}
}
m_pState->PushState(ES_WAITING_FOR_BLOCK_SEQ_ENTRY);
} else if(flowType == Flow) {
EmitSeparationIfNecessary();
m_stream << "[";
m_pState->PushState(ES_WAITING_FOR_FLOW_SEQ_ENTRY);
} else
assert(false);
m_pState->BeginGroup(GT_SEQ);
}
// EmitEndSeq
void Emitter::EmitEndSeq()
{
if(!good())
return;
if(m_pState->GetCurGroupType() != GT_SEQ)
return m_pState->SetError(ErrorMsg::UNEXPECTED_END_SEQ);
EMITTER_STATE curState = m_pState->GetCurState();
FLOW_TYPE flowType = m_pState->GetCurGroupFlowType();
if(flowType == FT_BLOCK) {
// Note: block sequences are *not* allowed to be empty, but we convert it
// to a flow sequence if it is
assert(curState == ES_DONE_WITH_BLOCK_SEQ_ENTRY || curState == ES_WAITING_FOR_BLOCK_SEQ_ENTRY);
if(curState == ES_WAITING_FOR_BLOCK_SEQ_ENTRY) {
// Note: only one of these will actually output anything for a given situation
EmitSeparationIfNecessary();
unsigned curIndent = m_pState->GetCurIndent();
m_stream << IndentTo(curIndent);
m_stream << "[]";
}
} else if(flowType == FT_FLOW) {
// Note: flow sequences are allowed to be empty
assert(curState == ES_DONE_WITH_FLOW_SEQ_ENTRY || curState == ES_WAITING_FOR_FLOW_SEQ_ENTRY);
m_stream << "]";
} else
assert(false);
m_pState->PopState();
m_pState->EndGroup(GT_SEQ);
PostAtomicWrite();
}
// EmitBeginMap
void Emitter::EmitBeginMap()
{
if(!good())
return;
// must have a long key if we're emitting a map
m_pState->StartLongKey();
PreAtomicWrite();
EMITTER_STATE curState = m_pState->GetCurState();
EMITTER_MANIP flowType = m_pState->GetFlowType(GT_MAP);
if(flowType == Block) {
if(curState == ES_WRITING_BLOCK_SEQ_ENTRY ||
curState == ES_WRITING_BLOCK_MAP_KEY || curState == ES_WRITING_BLOCK_MAP_VALUE ||
curState == ES_WRITING_DOC
) {
if(m_pState->RequiresHardSeparation() || (curState != ES_WRITING_DOC && curState != ES_WRITING_BLOCK_SEQ_ENTRY)) {
m_stream << "\n";
m_pState->UnsetSeparation();
}
}
m_pState->PushState(ES_WAITING_FOR_BLOCK_MAP_ENTRY);
} else if(flowType == Flow) {
EmitSeparationIfNecessary();
m_stream << "{";
m_pState->PushState(ES_WAITING_FOR_FLOW_MAP_ENTRY);
} else
assert(false);
m_pState->BeginGroup(GT_MAP);
}
// EmitEndMap
void Emitter::EmitEndMap()
{
if(!good())
return;
if(m_pState->GetCurGroupType() != GT_MAP)
return m_pState->SetError(ErrorMsg::UNEXPECTED_END_MAP);
EMITTER_STATE curState = m_pState->GetCurState();
FLOW_TYPE flowType = m_pState->GetCurGroupFlowType();
if(flowType == FT_BLOCK) {
// Note: block sequences are *not* allowed to be empty, but we convert it
// to a flow sequence if it is
assert(curState == ES_DONE_WITH_BLOCK_MAP_VALUE || curState == ES_WAITING_FOR_BLOCK_MAP_ENTRY);
if(curState == ES_WAITING_FOR_BLOCK_MAP_ENTRY) {
// Note: only one of these will actually output anything for a given situation
EmitSeparationIfNecessary();
unsigned curIndent = m_pState->GetCurIndent();
m_stream << IndentTo(curIndent);
m_stream << "{}";
}
} else if(flowType == FT_FLOW) {
// Note: flow maps are allowed to be empty
assert(curState == ES_DONE_WITH_FLOW_MAP_VALUE || curState == ES_WAITING_FOR_FLOW_MAP_ENTRY);
EmitSeparationIfNecessary();
m_stream << "}";
} else
assert(false);
m_pState->PopState();
m_pState->EndGroup(GT_MAP);
PostAtomicWrite();
}
// EmitKey
void Emitter::EmitKey()
{
if(!good())
return;
EMITTER_STATE curState = m_pState->GetCurState();
FLOW_TYPE flowType = m_pState->GetCurGroupFlowType();
if(curState != ES_WAITING_FOR_BLOCK_MAP_ENTRY && curState != ES_DONE_WITH_BLOCK_MAP_VALUE
&& curState != ES_WAITING_FOR_FLOW_MAP_ENTRY && curState != ES_DONE_WITH_FLOW_MAP_VALUE)
return m_pState->SetError(ErrorMsg::UNEXPECTED_KEY_TOKEN);
if(flowType == FT_BLOCK) {
if(curState == ES_DONE_WITH_BLOCK_MAP_VALUE)
m_stream << '\n';
unsigned curIndent = m_pState->GetCurIndent();
m_stream << IndentTo(curIndent);
m_pState->UnsetSeparation();
m_pState->SwitchState(ES_WAITING_FOR_BLOCK_MAP_KEY);
} else if(flowType == FT_FLOW) {
EmitSeparationIfNecessary();
if(curState == ES_DONE_WITH_FLOW_MAP_VALUE) {
m_stream << ',';
m_pState->RequireSoftSeparation();
}
m_pState->SwitchState(ES_WAITING_FOR_FLOW_MAP_KEY);
} else
assert(false);
if(m_pState->GetMapKeyFormat() == LongKey)
m_pState->StartLongKey();
else if(m_pState->GetMapKeyFormat() == Auto)
m_pState->StartSimpleKey();
else
assert(false);
}
// EmitValue
void Emitter::EmitValue()
{
if(!good())
return;
EMITTER_STATE curState = m_pState->GetCurState();
FLOW_TYPE flowType = m_pState->GetCurGroupFlowType();
if(curState != ES_DONE_WITH_BLOCK_MAP_KEY && curState != ES_DONE_WITH_FLOW_MAP_KEY)
return m_pState->SetError(ErrorMsg::UNEXPECTED_VALUE_TOKEN);
if(flowType == FT_BLOCK) {
if(m_pState->CurrentlyInLongKey()) {
m_stream << '\n';
m_stream << IndentTo(m_pState->GetCurIndent());
m_stream << ':';
m_pState->RequireSoftSeparation();
}
m_pState->SwitchState(ES_WAITING_FOR_BLOCK_MAP_VALUE);
} else if(flowType == FT_FLOW) {
m_pState->SwitchState(ES_WAITING_FOR_FLOW_MAP_VALUE);
} else
assert(false);
}
// EmitNewline
void Emitter::EmitNewline()
{
if(!good())
return;
if(CanEmitNewline()) {
m_stream << '\n';
m_pState->UnsetSeparation();
}
}
bool Emitter::CanEmitNewline() const
{
FLOW_TYPE flowType = m_pState->GetCurGroupFlowType();
if(flowType == FT_BLOCK && m_pState->CurrentlyInLongKey())
return true;
EMITTER_STATE curState = m_pState->GetCurState();
return curState != ES_DONE_WITH_BLOCK_MAP_KEY && curState != ES_WAITING_FOR_BLOCK_MAP_VALUE && curState != ES_WRITING_BLOCK_MAP_VALUE;
}
// *******************************************************************************************
// overloads of Write
Emitter& Emitter::Write(const std::string& str)
{
if(!good())
return *this;
// literal scalars must use long keys
if(m_pState->GetStringFormat() == Literal && m_pState->GetCurGroupFlowType() != FT_FLOW)
m_pState->StartLongKey();
PreAtomicWrite();
EmitSeparationIfNecessary();
bool escapeNonAscii = m_pState->GetOutputCharset() == EscapeNonAscii;
EMITTER_MANIP strFmt = m_pState->GetStringFormat();
FLOW_TYPE flowType = m_pState->GetCurGroupFlowType();
unsigned curIndent = m_pState->GetCurIndent();
switch(strFmt) {
case Auto:
Utils::WriteString(m_stream, str, flowType == FT_FLOW, escapeNonAscii);
break;
case SingleQuoted:
if(!Utils::WriteSingleQuotedString(m_stream, str)) {
m_pState->SetError(ErrorMsg::SINGLE_QUOTED_CHAR);
return *this;
}
break;
case DoubleQuoted:
Utils::WriteDoubleQuotedString(m_stream, str, escapeNonAscii);
break;
case Literal:
if(flowType == FT_FLOW)
Utils::WriteString(m_stream, str, flowType == FT_FLOW, escapeNonAscii);
else
Utils::WriteLiteralString(m_stream, str, curIndent + m_pState->GetIndent());
break;
default:
assert(false);
}
PostAtomicWrite();
return *this;
}
void Emitter::PreWriteIntegralType(std::stringstream& str)
{
PreAtomicWrite();
EmitSeparationIfNecessary();
EMITTER_MANIP intFmt = m_pState->GetIntFormat();
switch(intFmt) {
case Dec:
str << std::dec;
break;
case Hex:
str << std::hex;
break;
case Oct:
str << std::oct;
break;
default:
assert(false);
}
}
void Emitter::PreWriteStreamable(std::stringstream& str)
{
PreAtomicWrite();
EmitSeparationIfNecessary();
str.precision(15);
}
void Emitter::PostWriteIntegralType(const std::stringstream& str)
{
m_stream << str.str();
PostAtomicWrite();
}
void Emitter::PostWriteStreamable(const std::stringstream& str)
{
m_stream << str.str();
PostAtomicWrite();
}
const char *Emitter::ComputeFullBoolName(bool b) const
{
const EMITTER_MANIP mainFmt = (m_pState->GetBoolLengthFormat() == ShortBool ? YesNoBool : m_pState->GetBoolFormat());
const EMITTER_MANIP caseFmt = m_pState->GetBoolCaseFormat();
switch(mainFmt) {
case YesNoBool:
switch(caseFmt) {
case UpperCase: return b ? "YES" : "NO";
case CamelCase: return b ? "Yes" : "No";
case LowerCase: return b ? "yes" : "no";
default: break;
}
break;
case OnOffBool:
switch(caseFmt) {
case UpperCase: return b ? "ON" : "OFF";
case CamelCase: return b ? "On" : "Off";
case LowerCase: return b ? "on" : "off";
default: break;
}
break;
case TrueFalseBool:
switch(caseFmt) {
case UpperCase: return b ? "TRUE" : "FALSE";
case CamelCase: return b ? "True" : "False";
case LowerCase: return b ? "true" : "false";
default: break;
}
break;
default:
break;
}
return b ? "y" : "n"; // should never get here, but it can't hurt to give these answers
}
Emitter& Emitter::Write(bool b)
{
if(!good())
return *this;
PreAtomicWrite();
EmitSeparationIfNecessary();
const char *name = ComputeFullBoolName(b);
if(m_pState->GetBoolLengthFormat() == ShortBool)
m_stream << name[0];
else
m_stream << name;
PostAtomicWrite();
return *this;
}
Emitter& Emitter::Write(const _Alias& alias)
{
if(!good())
return *this;
PreAtomicWrite();
EmitSeparationIfNecessary();
if(!Utils::WriteAlias(m_stream, alias.content)) {
m_pState->SetError(ErrorMsg::INVALID_ALIAS);
return *this;
}
PostAtomicWrite();
return *this;
}
Emitter& Emitter::Write(const _Anchor& anchor)
{
if(!good())
return *this;
PreAtomicWrite();
EmitSeparationIfNecessary();
if(!Utils::WriteAnchor(m_stream, anchor.content)) {
m_pState->SetError(ErrorMsg::INVALID_ANCHOR);
return *this;
}
m_pState->RequireHardSeparation();
// Note: no PostAtomicWrite() because we need another value for this node
return *this;
}
Emitter& Emitter::Write(const _Tag& tag)
{
if(!good())
return *this;
PreAtomicWrite();
EmitSeparationIfNecessary();
bool success = false;
if(tag.type == _Tag::Type::Verbatim)
success = Utils::WriteTag(m_stream, tag.content, true);
else if(tag.type == _Tag::Type::PrimaryHandle)
success = Utils::WriteTag(m_stream, tag.content, false);
else
success = Utils::WriteTagWithPrefix(m_stream, tag.prefix, tag.content);
if(!success) {
m_pState->SetError(ErrorMsg::INVALID_TAG);
return *this;
}
m_pState->RequireHardSeparation();
// Note: no PostAtomicWrite() because we need another value for this node
return *this;
}
void Emitter::EmitKindTag()
{
Write(LocalTag(""));
}
Emitter& Emitter::Write(const _Comment& comment)
{
if(!good())
return *this;
if(m_stream.col() > 0)
m_stream << Indentation(m_pState->GetPreCommentIndent());
Utils::WriteComment(m_stream, comment.content, m_pState->GetPostCommentIndent());
m_pState->RequireHardSeparation();
m_pState->ForceHardSeparation();
return *this;
}
Emitter& Emitter::Write(const _Null& /*null*/)
{
if(!good())
return *this;
PreAtomicWrite();
EmitSeparationIfNecessary();
m_stream << "~";
PostAtomicWrite();
return *this;
}
Emitter& Emitter::Write(const _Binary& binary)
{
Write(SecondaryTag("binary"));
if(!good())
return *this;
PreAtomicWrite();
EmitSeparationIfNecessary();
Utils::WriteBinary(m_stream, binary.data, binary.size);
PostAtomicWrite();
return *this;
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment