aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAvatar Ciaran McCreesh <ciaran.mccreesh@googlemail.com> 2006-12-11 14:35:04 +0000
committerAvatar Ciaran McCreesh <ciaran.mccreesh@googlemail.com> 2006-12-11 14:35:04 +0000
commit6eae5cfdf9fae7fdfafb0ecf48335ba48dd28466 (patch)
tree582dde8877e9a0a07b6da089539ed1897ad1fcb9
parent02db5244589d025fb62912cef65622b58b6458e7 (diff)
downloadpaludis-6eae5cfdf9fae7fdfafb0ecf48335ba48dd28466.tar.gz
paludis-6eae5cfdf9fae7fdfafb0ecf48335ba48dd28466.tar.xz
Crude initial yaml support for gems
-rw-r--r--paludis/repositories/gems/Makefile.am63
-rw-r--r--paludis/repositories/gems/cache.cc200
-rw-r--r--paludis/repositories/gems/cache.hh52
-rw-r--r--paludis/repositories/gems/cache.sr18
-rw-r--r--paludis/repositories/gems/cache_TEST.cc81
-rwxr-xr-xpaludis/repositories/gems/cache_TEST_cleanup.sh9
-rwxr-xr-xpaludis/repositories/gems/cache_TEST_setup.sh26
-rw-r--r--paludis/repositories/gems/gems_repository_exceptions.cc5
-rw-r--r--paludis/repositories/gems/gems_repository_exceptions.hh8
-rw-r--r--paludis/repositories/gems/yaml.cc371
-rw-r--r--paludis/repositories/gems/yaml.hh139
-rw-r--r--paludis/repositories/gems/yaml_TEST.cc145
12 files changed, 1110 insertions, 7 deletions
diff --git a/paludis/repositories/gems/Makefile.am b/paludis/repositories/gems/Makefile.am
index 831452a..f00b31f 100644
--- a/paludis/repositories/gems/Makefile.am
+++ b/paludis/repositories/gems/Makefile.am
@@ -1,5 +1,9 @@
CLEANFILES = *~ gmon.out *.gcov *.gcno *.gcda
-DISTCLEANFILES = gems_repository-sr.hh gems_repository-sr.cc
+
+DISTCLEANFILES = \
+ gems_repository-sr.hh gems_repository-sr.cc \
+ cache-sr.hh cache-sr.cc
+
MAINTAINERCLEANFILES = Makefile.in
AM_CXXFLAGS = -I$(top_srcdir) @PALUDIS_CXXFLAGS@ @PALUDIS_CXXFLAGS_VISIBILITY@
@@ -18,17 +22,23 @@ paludis_repositories_gems_include_HEADERS = \
gems_repository.hh \
gems_repository-sr.hh \
gems_repository_exceptions.hh \
- make_gems_repository.hh
+ cache.hh \
+ cache-sr.hh \
+ make_gems_repository.hh \
+ yaml.hh
libpaludisgemsrepository_la_SOURCES = \
gems_repository.cc \
gems_repository_exceptions.cc \
+ cache.cc \
make_gems_repository.cc \
+ yaml.cc \
$(paludis_repositories_gems_include_HEADERS)
libpaludisgemsrepository_la_LIBADD = \
$(top_builddir)/paludis/util/libpaludisutil.la \
$(top_builddir)/paludis/libpaludis.la \
+ -lyaml \
$(DYNAMIC_LD_LIBS)
gems_repository_TEST_SOURCES = gems_repository_TEST.cc
@@ -43,20 +53,54 @@ gems_repository_TEST_LDADD = \
gems_repository_TEST_CXXFLAGS = -I$(top_srcdir)
+yaml_TEST_SOURCES = yaml_TEST.cc
+
+yaml_TEST_LDADD = \
+ libpaludisgemsrepository.la \
+ $(top_builddir)/paludis/util/libpaludisutil.la \
+ $(top_builddir)/paludis/util/test_extras.o \
+ $(top_builddir)/paludis/libpaludis.la \
+ $(top_builddir)/paludis/environment/test/libpaludistestenvironment.la \
+ $(top_builddir)/test/libtest.a
+
+yaml_TEST_CXXFLAGS = -I$(top_srcdir)
+
+cache_TEST_SOURCES = cache_TEST.cc
+
+cache_TEST_LDADD = \
+ libpaludisgemsrepository.la \
+ $(top_builddir)/paludis/util/libpaludisutil.la \
+ $(top_builddir)/paludis/util/test_extras.o \
+ $(top_builddir)/paludis/libpaludis.la \
+ $(top_builddir)/paludis/environment/test/libpaludistestenvironment.la \
+ $(top_builddir)/test/libtest.a
+
+cache_TEST_CXXFLAGS = -I$(top_srcdir)
+
EXTRA_DIST = \
gems_repository_TEST.cc \
gems_repository_TEST_setup.sh \
gems_repository_TEST_cleanup.sh \
gems_repository.sr \
gems_repository-sr.hh \
- gems_repository-sr.cc
+ gems_repository-sr.cc \
+ cache_TEST.cc \
+ cache_TEST_setup.sh \
+ cache_TEST_cleanup.sh \
+ cache.sr \
+ cache-sr.hh \
+ cache-sr.cc \
+ yaml_TEST.cc
BUILT_SOURCES = \
gems_repository-sr.hh \
- gems_repository-sr.cc
+ gems_repository-sr.cc \
+ cache-sr.hh \
+ cache-sr.cc
check_SCRIPTS = \
- gems_repository_TEST_setup.sh gems_repository_TEST_cleanup.sh
+ gems_repository_TEST_setup.sh gems_repository_TEST_cleanup.sh \
+ cache_TEST_setup.sh cache_TEST_cleanup.sh
TESTS_ENVIRONMENT = env \
PALUDIS_EBUILD_DIR="$(top_srcdir)/ebuild/" \
@@ -66,7 +110,7 @@ TESTS_ENVIRONMENT = env \
LD_LIBRARY_PATH="`$(top_srcdir)/ebuild/utils/canonicalise $(top_builddir)/paludis/repositories/gems/`" \
bash $(top_srcdir)/test/run_test.sh
-TESTS = gems_repository_TEST
+TESTS = yaml_TEST gems_repository_TEST cache_TEST
check_PROGRAMS = $(TESTS)
gems_repository-sr.hh : gems_repository.sr $(top_srcdir)/misc/make_sr.bash
@@ -75,8 +119,13 @@ gems_repository-sr.hh : gems_repository.sr $(top_srcdir)/misc/make_sr.bash
gems_repository-sr.cc : gems_repository.sr $(top_srcdir)/misc/make_sr.bash
$(top_srcdir)/misc/make_sr.bash --source $(srcdir)/gems_repository.sr > $@
+cache-sr.hh : cache.sr $(top_srcdir)/misc/make_sr.bash
+ $(top_srcdir)/misc/make_sr.bash --header $(srcdir)/cache.sr > $@
+
+cache-sr.cc : cache.sr $(top_srcdir)/misc/make_sr.bash
+ $(top_srcdir)/misc/make_sr.bash --source $(srcdir)/cache.sr > $@
+
built-sources : $(BUILT_SOURCES)
for s in `echo $(SUBDIRS) | tr -d .` ; do $(MAKE) -C $$s built-sources || exit 1 ; done
-
diff --git a/paludis/repositories/gems/cache.cc b/paludis/repositories/gems/cache.cc
new file mode 100644
index 0000000..bd0d2b2
--- /dev/null
+++ b/paludis/repositories/gems/cache.cc
@@ -0,0 +1,200 @@
+/* vim: set sw=4 sts=4 et foldmethod=syntax : */
+
+/*
+ * Copyright (c) 2006 Ciaran McCreesh <ciaranm@ciaranm.org>
+ *
+ * This file is part of the Paludis package manager. Paludis is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU General
+ * Public License version 2, as published by the Free Software Foundation.
+ *
+ * Paludis is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "cache.hh"
+#include <paludis/repositories/gems/gems_repository_exceptions.hh>
+#include <paludis/repositories/gems/yaml.hh>
+#include <paludis/util/fs_entry.hh>
+#include <paludis/util/save.hh>
+#include <list>
+
+#include <yaml.h>
+
+using namespace paludis;
+
+#include <paludis/repositories/gems/cache-sr.cc>
+
+namespace paludis
+{
+ template<>
+ struct Implementation<GemsCache> :
+ InternalCounted<Implementation<GemsCache> >
+ {
+ std::list<GemsCacheEntry> entries;
+ };
+}
+
+namespace
+{
+ std::string
+ as_string(YamlNode::ConstPointer n)
+ {
+ struct Visitor :
+ YamlNodeVisitorTypes::ConstVisitor
+ {
+ std::string str;
+
+ void visit(const YamlSequenceNode *) PALUDIS_ATTRIBUTE((noreturn))
+ {
+ throw GemsCacheError("Expected a scalar node, not a sequence");
+ }
+
+ void visit(const YamlMappingNode *) PALUDIS_ATTRIBUTE((noreturn))
+ {
+ throw GemsCacheError("Expected a scalar node, not a mapping");
+ }
+
+ void visit(const YamlScalarNode * nn)
+ {
+ str = nn->value();
+ }
+ };
+
+ Visitor v;
+ n->accept(&v);
+ return v.str;
+ }
+
+ struct EntryVisitor :
+ YamlNodeVisitorTypes::ConstVisitor
+ {
+ const std::string id;
+
+ std::string name;
+ std::string version;
+ std::string summary;
+ std::string description;
+ std::string homepage;
+
+ EntryVisitor(const std::string & _id) :
+ id(_id)
+ {
+ }
+
+ GemsCacheEntry
+ entry() const
+ {
+ Context context("When creating GemsCacheEntry with id '" + id + "':");
+
+ return GemsCacheEntry::create()
+ .name(PackageNamePart(name))
+ .version(VersionSpec(version))
+ .summary(summary)
+ .description(description)
+ .homepage(homepage)
+ .required_ruby_version(SequentialCollection<std::string>::Pointer(0))
+ .authors(SequentialCollection<std::string>::Pointer(0))
+ .dependencies(SequentialCollection<std::string>::Pointer(0))
+ .requirements(SequentialCollection<std::string>::Pointer(0));
+ }
+
+ void visit(const YamlSequenceNode *) PALUDIS_ATTRIBUTE((noreturn))
+ {
+ throw GemsCacheError("Sequence node not expected here'");
+ }
+
+ void visit(const YamlScalarNode * n) PALUDIS_ATTRIBUTE((noreturn))
+ {
+ throw GemsCacheError("Node '" + n->value() + "' not expected here'");
+ }
+
+ void visit(const YamlMappingNode * n)
+ {
+ for (YamlMappingNode::Iterator i(n->begin()), i_end(n->end()) ; i != i_end ; ++i)
+ {
+ if (i->first->value() == "name")
+ name = as_string(i->second);
+ }
+ }
+ };
+
+ struct TopLevelVisitor :
+ YamlNodeVisitorTypes::ConstVisitor
+ {
+ Implementation<GemsCache>::Pointer imp;
+ bool top_level;
+
+ TopLevelVisitor(Implementation<GemsCache>::Pointer p) :
+ imp(p),
+ top_level(true)
+ {
+ }
+
+ void visit(const YamlSequenceNode * n)
+ {
+ std::for_each(n->begin(), n->end(), accept_visitor(this));
+ }
+
+ void visit(const YamlMappingNode * n)
+ {
+ for (YamlMappingNode::Iterator i(n->begin()), i_end(n->end()) ; i != i_end ; ++i)
+ {
+ if (top_level)
+ {
+ if (i->first->value() != "gems")
+ throw GemsCacheError("Node '" + i->first->value() + "' is not 'gems'");
+ Save<bool> save_top_level(&top_level, false);
+ i->second->accept(this);
+ }
+ else
+ {
+ EntryVisitor v(i->first->value());
+ i->second->accept(&v);
+ imp->entries.push_back(v.entry());
+ }
+ }
+ }
+
+ void visit(const YamlScalarNode * n) PALUDIS_ATTRIBUTE((noreturn))
+ {
+ throw GemsCacheError("Node '" + n->value() + "' not expected here'");
+ }
+ };
+}
+
+GemsCache::GemsCache(const FSEntry & loc) :
+ PrivateImplementationPattern<GemsCache>(new Implementation<GemsCache>)
+{
+ Context context("When creating Gems cache from '" + stringify(loc) + "':");
+
+ if (! loc.is_regular_file())
+ throw GemsCacheError("Cache '" + stringify(loc) + "' is not a regular file");
+
+ YamlDocument doc(loc);
+
+ TopLevelVisitor v(_imp);
+ doc.top()->accept(&v);
+}
+
+GemsCache::~GemsCache()
+{
+}
+
+GemsCache::Iterator
+GemsCache::begin() const
+{
+ return Iterator(_imp->entries.begin());
+}
+
+GemsCache::Iterator
+GemsCache::end() const
+{
+ return Iterator(_imp->entries.end());
+}
+
diff --git a/paludis/repositories/gems/cache.hh b/paludis/repositories/gems/cache.hh
new file mode 100644
index 0000000..e3b6ebb
--- /dev/null
+++ b/paludis/repositories/gems/cache.hh
@@ -0,0 +1,52 @@
+/* vim: set sw=4 sts=4 et foldmethod=syntax : */
+
+/*
+ * Copyright (c) 2006 Ciaran McCreesh <ciaranm@ciaranm.org>
+ *
+ * This file is part of the Paludis package manager. Paludis is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU General
+ * Public License version 2, as published by the Free Software Foundation.
+ *
+ * Paludis is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef PALUDIS_GUARD_PALUDIS_REPOSITORIES_GEMS_CACHE_HH
+#define PALUDIS_GUARD_PALUDIS_REPOSITORIES_GEMS_CACHE_HH 1
+
+#include <paludis/util/instantiation_policy.hh>
+#include <paludis/util/private_implementation_pattern.hh>
+#include <paludis/util/collection.hh>
+#include <paludis/name.hh>
+#include <paludis/version_spec.hh>
+
+#include <libwrapiter/libwrapiter.hh>
+
+namespace paludis
+{
+ class FSEntry;
+
+#include <paludis/repositories/gems/cache-sr.hh>
+
+ class GemsCache :
+ private InstantiationPolicy<GemsCache, instantiation_method::NonCopyableTag>,
+ private PrivateImplementationPattern<GemsCache>,
+ public InternalCounted<GemsCache>
+ {
+ public:
+ GemsCache(const FSEntry &);
+ ~GemsCache();
+
+ typedef libwrapiter::ForwardIterator<GemsCache, const GemsCacheEntry> Iterator;
+ Iterator begin() const;
+ Iterator end() const;
+ };
+}
+
+#endif
diff --git a/paludis/repositories/gems/cache.sr b/paludis/repositories/gems/cache.sr
new file mode 100644
index 0000000..7d2816c
--- /dev/null
+++ b/paludis/repositories/gems/cache.sr
@@ -0,0 +1,18 @@
+#!/bin/bash
+# vim: set sw=4 sts=4 et :
+
+make_class_GemsCacheEntry()
+{
+ key name PackageNamePart
+ key version VersionSpec
+ key summary std::string
+ key description std::string
+ key homepage std::string
+ key required_ruby_version "SequentialCollection<std::string>::Pointer"
+ key authors "SequentialCollection<std::string>::Pointer"
+ key dependencies "SequentialCollection<std::string>::Pointer"
+ key requirements "SequentialCollection<std::string>::Pointer"
+
+ allow_named_args
+}
+
diff --git a/paludis/repositories/gems/cache_TEST.cc b/paludis/repositories/gems/cache_TEST.cc
new file mode 100644
index 0000000..fe6288e
--- /dev/null
+++ b/paludis/repositories/gems/cache_TEST.cc
@@ -0,0 +1,81 @@
+/* vim: set sw=4 sts=4 et foldmethod=syntax : */
+
+/*
+ * Copyright (c) 2006 Ciaran McCreesh <ciaranm@ciaranm.org>
+ *
+ * This file is part of the Paludis package manager. Paludis is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU General
+ * Public License version 2, as published by the Free Software Foundation.
+ *
+ * Paludis is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <paludis/repositories/gems/cache.hh>
+#include <paludis/repositories/gems/gems_repository_exceptions.hh>
+#include <paludis/repositories/gems/yaml.hh>
+#include <paludis/util/collection_concrete.hh>
+#include <paludis/util/fs_entry.hh>
+#include <paludis/environment/test/test_environment.hh>
+#include <test/test_framework.hh>
+#include <test/test_runner.hh>
+
+using namespace test;
+using namespace paludis;
+
+namespace test_cases
+{
+ struct GemsRepositoryCacheEntriesTest : TestCase
+ {
+ GemsRepositoryCacheEntriesTest() : TestCase("cache entries") { }
+
+ void run()
+ {
+ GemsCache cache(FSEntry("cache_TEST_dir/entries"));
+
+ TEST_CHECK_EQUAL(std::distance(cache.begin(), cache.end()), 2);
+
+ GemsCache::Iterator c(cache.begin());
+ TEST_CHECK(c != cache.end());
+
+ TEST_CHECK_EQUAL(c->name, PackageNamePart("foo"));
+
+ ++c;
+ TEST_CHECK(c != cache.end());
+
+ TEST_CHECK_EQUAL(c->name, PackageNamePart("bar"));
+
+ ++c;
+ TEST_CHECK(c == cache.end());
+ }
+ } test_cache_entries;
+
+ struct GemsRepositoryCacheNoFileTest : TestCase
+ {
+ GemsRepositoryCacheNoFileTest() : TestCase("cache no file") { }
+
+ void run()
+ {
+ TEST_CHECK_THROWS(GemsCache(FSEntry("cache_TEST_dir/nofile")), GemsCacheError);
+ }
+ } test_cache_no_file;
+
+ struct GemsRepositoryCacheBrokenTest : TestCase
+ {
+ GemsRepositoryCacheBrokenTest() : TestCase("cache broken file") { }
+
+ void run()
+ {
+ TEST_CHECK_THROWS(GemsCache(FSEntry("cache_TEST_dir/broken")), YamlError);
+ }
+ } test_cache_broken;
+}
+
+
+
diff --git a/paludis/repositories/gems/cache_TEST_cleanup.sh b/paludis/repositories/gems/cache_TEST_cleanup.sh
new file mode 100755
index 0000000..c75183a
--- /dev/null
+++ b/paludis/repositories/gems/cache_TEST_cleanup.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+# vim: set ft=sh sw=4 sts=4 et :
+
+if [ -d cache_TEST_dir ] ; then
+ rm -fr cache_TEST_dir
+else
+ true
+fi
+
diff --git a/paludis/repositories/gems/cache_TEST_setup.sh b/paludis/repositories/gems/cache_TEST_setup.sh
new file mode 100755
index 0000000..7fb6cc6
--- /dev/null
+++ b/paludis/repositories/gems/cache_TEST_setup.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+# vim: set ft=sh sw=4 sts=4 et :
+
+mkdir cache_TEST_dir || exit 1
+cd cache_TEST_dir || exit 1
+
+cat <<END > entries
+--- !ruby/object:Gem::Cache
+gems:
+ foo-1.2.3: !ruby/object:Gem::Specification
+ name: foo
+ version: !ruby/object:Gem::Version
+ version: 1.2.3
+
+ bar-2.3.4: !ruby/object:Gem::Specification
+ name: bar
+ version: !ruby/object:Gem::Version
+ version: 2.3.4
+
+END
+
+cat <<"END" > broken
+foo:
+ [ [ foo:
+END
+
diff --git a/paludis/repositories/gems/gems_repository_exceptions.cc b/paludis/repositories/gems/gems_repository_exceptions.cc
index 9f11f1c..cc7905b 100644
--- a/paludis/repositories/gems/gems_repository_exceptions.cc
+++ b/paludis/repositories/gems/gems_repository_exceptions.cc
@@ -27,4 +27,9 @@ GemsRepositoryConfigurationError::GemsRepositoryConfigurationError(
{
}
+GemsCacheError::GemsCacheError(
+ const std::string & msg) throw () :
+ ConfigurationError("Gems repository cache error: " + msg)
+{
+}
diff --git a/paludis/repositories/gems/gems_repository_exceptions.hh b/paludis/repositories/gems/gems_repository_exceptions.hh
index 55bce82..38a0ca9 100644
--- a/paludis/repositories/gems/gems_repository_exceptions.hh
+++ b/paludis/repositories/gems/gems_repository_exceptions.hh
@@ -33,6 +33,14 @@ namespace paludis
*/
GemsRepositoryConfigurationError(const std::string & msg) throw ();
};
+
+ class PALUDIS_VISIBLE GemsCacheError :
+ public ConfigurationError
+ {
+ public:
+ GemsCacheError(const std::string & msg) throw ();
+ };
+
}
#endif
diff --git a/paludis/repositories/gems/yaml.cc b/paludis/repositories/gems/yaml.cc
new file mode 100644
index 0000000..20daff6
--- /dev/null
+++ b/paludis/repositories/gems/yaml.cc
@@ -0,0 +1,371 @@
+/* vim: set sw=4 sts=4 et foldmethod=syntax : */
+
+/*
+ * Copyright (c) 2006 Ciaran McCreesh <ciaranm@ciaranm.org>
+ *
+ * This file is part of the Paludis package manager. Paludis is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU General
+ * Public License version 2, as published by the Free Software Foundation.
+ *
+ * Paludis is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "yaml.hh"
+#include <paludis/util/fs_entry.hh>
+#include <paludis/util/stringify.hh>
+#include <yaml.h>
+#include <list>
+
+using namespace paludis;
+
+YamlNode::YamlNode()
+{
+}
+
+YamlNode::~YamlNode()
+{
+}
+
+YamlScalarNode::YamlScalarNode(const std::string & v, const std::string & t) :
+ _value(v),
+ _tag(t)
+{
+}
+
+namespace paludis
+{
+ template<>
+ struct Implementation<YamlMappingNode> :
+ InternalCounted<Implementation<YamlMappingNode> >
+ {
+ std::list<std::pair<YamlScalarNode::Pointer, YamlNode::Pointer> > nodes;
+ };
+
+ template<>
+ struct Implementation<YamlSequenceNode> :
+ InternalCounted<Implementation<YamlSequenceNode> >
+ {
+ std::list<YamlNode::Pointer> nodes;
+ };
+}
+
+
+YamlMappingNode::YamlMappingNode() :
+ PrivateImplementationPattern<YamlMappingNode>(new Implementation<YamlMappingNode>)
+{
+}
+
+YamlMappingNode::Iterator
+YamlMappingNode::begin() const
+{
+ return Iterator(_imp->nodes.begin());
+}
+
+YamlMappingNode::Iterator
+YamlMappingNode::end() const
+{
+ return Iterator(_imp->nodes.end());
+}
+
+std::pair<YamlScalarNode::Pointer, YamlNode::Pointer> &
+YamlMappingNode::back()
+{
+ return _imp->nodes.back();
+}
+
+YamlSequenceNode::YamlSequenceNode() :
+ PrivateImplementationPattern<YamlSequenceNode>(new Implementation<YamlSequenceNode>)
+{
+}
+
+YamlSequenceNode::Iterator
+YamlSequenceNode::begin() const
+{
+ return Iterator(_imp->nodes.begin());
+}
+
+YamlSequenceNode::Iterator
+YamlSequenceNode::end() const
+{
+ return Iterator(_imp->nodes.end());
+}
+
+void
+YamlSequenceNode::add(YamlNode::Pointer node)
+{
+ _imp->nodes.push_back(node);
+}
+
+void
+YamlMappingNode::add(YamlScalarNode::Pointer a, YamlNode::Pointer b)
+{
+ _imp->nodes.push_back(std::make_pair(a, b));
+}
+
+namespace paludis
+{
+ template<>
+ struct Implementation<YamlDocument> :
+ InternalCounted<Implementation<YamlDocument> >
+ {
+ YamlSequenceNode::Pointer top;
+
+ void parse(yaml_parser_t * parser);
+ YamlScalarNode::Pointer parse_scalar(yaml_parser_t * parser);
+
+ Implementation() :
+ top(new YamlSequenceNode)
+ {
+ }
+ };
+}
+
+namespace
+{
+ template <typename PtrType_, typename ReturnType_ = void>
+ class PtrHolder
+ {
+ private:
+ PtrType_ _ptr;
+ ReturnType_ (* _free_func) (PtrType_);
+ bool _new_used;
+
+ PtrHolder(const PtrHolder &);
+ void operator= (const PtrHolder &);
+
+ public:
+ PtrHolder(PtrType_ ptr, ReturnType_ (* free_func) (PtrType_), bool new_used = false) :
+ _ptr(ptr),
+ _free_func(free_func),
+ _new_used(new_used)
+ {
+ }
+
+ ~PtrHolder()
+ {
+ if (0 != _ptr)
+ {
+ _free_func(_ptr);
+ if (_new_used)
+ delete _ptr;
+ }
+ }
+
+ operator PtrType_ () const
+ {
+ return _ptr;
+ }
+ };
+
+ struct ScalarAdder :
+ YamlNodeVisitorTypes::Visitor
+ {
+ YamlScalarNode::Pointer a;
+
+ ScalarAdder(YamlScalarNode::Pointer aa) :
+ a(aa)
+ {
+ }
+
+ void visit(YamlMappingNode * n)
+ {
+ if (n->empty() || n->back().second)
+ n->add(a, YamlNode::Pointer(0));
+ else
+ n->back().second = a;
+ }
+
+ void visit(YamlScalarNode *) PALUDIS_ATTRIBUTE((noreturn))
+ {
+ throw YamlError("Can't add a YamlScalarNode to a YamlScalarNode");
+ }
+
+ void visit(YamlSequenceNode * n)
+ {
+ n->add(a);
+ }
+ };
+
+ struct NonScalarAdder :
+ YamlNodeVisitorTypes::Visitor
+ {
+ YamlNode::Pointer a;
+
+ NonScalarAdder(YamlNode::Pointer aa) :
+ a(aa)
+ {
+ }
+
+ void visit(YamlMappingNode * n)
+ {
+ if (n->empty() || n->back().second)
+ throw YamlError("Can't add a non YamlScalarNode to a YamlMappingNode on the left");
+ else
+ n->back().second = a;
+ }
+
+ void visit(YamlScalarNode *) PALUDIS_ATTRIBUTE((noreturn))
+ {
+ throw YamlError("Can't add a YamlScalarNode to a YamlScalarNode");
+ }
+
+ void visit(YamlSequenceNode * n)
+ {
+ n->add(a);
+ }
+ };
+}
+
+YamlDocument::YamlDocument(const std::string & s) :
+ PrivateImplementationPattern<YamlDocument>(new Implementation<YamlDocument>)
+{
+ Context context("When creating YamlDocument from string:");
+
+ PtrHolder<yaml_parser_t *> parser(new yaml_parser_t, yaml_parser_delete, true);
+ yaml_parser_initialize(parser);
+ /* silly c api */
+ yaml_parser_set_input_string(parser, const_cast<unsigned char *>(
+ reinterpret_cast<const unsigned char *>(s.c_str())), s.length());
+
+ _imp->parse(parser);
+}
+
+YamlDocument::YamlDocument(const FSEntry & loc) :
+ PrivateImplementationPattern<YamlDocument>(new Implementation<YamlDocument>)
+{
+ Context context("When creating YamlDocument from location '" + stringify(loc) + "':");
+
+ if (! loc.is_regular_file())
+ throw YamlError("Document '" + stringify(loc) + "' is not a regular file");
+
+ PtrHolder<FILE *, int> f(std::fopen(stringify(loc).c_str(), "rb"), &std::fclose);
+ if (! f)
+ throw YamlError("Document '" + stringify(loc) + "' is not readable");
+
+ PtrHolder<yaml_parser_t *> parser(new yaml_parser_t, yaml_parser_delete, true);
+ yaml_parser_initialize(parser);
+ yaml_parser_set_input_file(parser, f);
+
+ _imp->parse(parser);
+}
+
+void
+Implementation<YamlDocument>::parse(yaml_parser_t * parser)
+{
+ std::list<YamlNode::Pointer> stack;
+ stack.push_back(top);
+
+ bool done(false);
+
+ while (! done)
+ {
+ PtrHolder<yaml_event_t *> event_holder(new yaml_event_t, yaml_event_delete, true);
+ std::memset(event_holder, 0, sizeof(*event_holder));
+ if (! yaml_parser_parse(parser, event_holder))
+ throw YamlError("Error parsing document");
+
+ yaml_event_t * event(event_holder);
+
+ switch (event->type)
+ {
+ case YAML_STREAM_START_EVENT:
+ break;
+
+ case YAML_STREAM_END_EVENT:
+ done = true;
+ break;
+
+ case YAML_MAPPING_START_EVENT:
+ {
+ YamlMappingNode::Pointer node(new YamlMappingNode);
+ if (stack.empty())
+ throw YamlError("Error building tree: stack empty on YAML_SEQUENCE_START_EVENT");
+
+ if (stack.back())
+ {
+ NonScalarAdder a(node);
+ stack.back()->accept(&a);
+ }
+ else
+ stack.back() = node;
+ stack.push_back(node);
+ }
+ break;
+
+ case YAML_MAPPING_END_EVENT:
+ case YAML_SEQUENCE_END_EVENT:
+ if (stack.empty())
+ throw YamlError("Error building tree: stack empty on YAML_*_END_EVENT");
+ stack.pop_back();
+ break;
+
+ case YAML_SEQUENCE_START_EVENT:
+ {
+ YamlSequenceNode::Pointer node(new YamlSequenceNode);
+ if (stack.empty())
+ throw YamlError("Error building tree: stack empty on YAML_SEQUENCE_START_EVENT");
+ if (stack.back())
+ {
+ NonScalarAdder a(node);
+ stack.back()->accept(&a);
+ }
+ else
+ stack.back() = node;
+ stack.push_back(node);
+ }
+ break;
+
+ case YAML_SCALAR_EVENT:
+ {
+ if (stack.empty())
+ throw YamlError("Error building tree: stack empty on YAML_SCALAR_EVENT");
+ YamlScalarNode::Pointer node(new YamlScalarNode(
+ event->data.scalar.value ? reinterpret_cast<const char *>(event->data.scalar.value) : "",
+ event->data.scalar.tag ? reinterpret_cast<const char *>(event->data.scalar.tag) : ""));
+ if (stack.back())
+ {
+ ScalarAdder a(node);
+ stack.back()->accept(&a);
+ }
+ else
+ stack.back() = node;
+ }
+ break;
+
+ case YAML_NO_EVENT:
+ case YAML_DOCUMENT_START_EVENT:
+ case YAML_DOCUMENT_END_EVENT:
+ case YAML_ALIAS_EVENT:
+ break;
+ }
+ };
+
+ if (stack.empty())
+ throw YamlError("Error building tree: stack empty at end");
+ stack.pop_back();
+ if (! stack.empty())
+ throw YamlError("Error building tree: stack not empty at end");
+}
+
+YamlDocument::~YamlDocument()
+{
+}
+
+YamlNode::ConstPointer
+YamlDocument::top() const
+{
+ return _imp->top;
+}
+
+YamlError::YamlError(const std::string & msg) throw () :
+ ConfigurationError(msg)
+{
+}
+
diff --git a/paludis/repositories/gems/yaml.hh b/paludis/repositories/gems/yaml.hh
new file mode 100644
index 0000000..12cc369
--- /dev/null
+++ b/paludis/repositories/gems/yaml.hh
@@ -0,0 +1,139 @@
+/* vim: set sw=4 sts=4 et foldmethod=syntax : */
+
+/*
+ * Copyright (c) 2006 Ciaran McCreesh <ciaranm@ciaranm.org>
+ *
+ * This file is part of the Paludis package manager. Paludis is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU General
+ * Public License version 2, as published by the Free Software Foundation.
+ *
+ * Paludis is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef PALUDIS_GUARD_PALUDIS_REPOSITORIES_GEMS_YAML_HH
+#define PALUDIS_GUARD_PALUDIS_REPOSITORIES_GEMS_YAML_HH 1
+
+#include <paludis/util/visitor.hh>
+#include <paludis/util/instantiation_policy.hh>
+#include <paludis/util/private_implementation_pattern.hh>
+
+namespace paludis
+{
+ class FSEntry;
+
+ class YamlNode;
+ class YamlScalarNode;
+ class YamlSequenceNode;
+ class YamlMappingNode;
+
+ typedef VisitorTypes<YamlScalarNode *, YamlSequenceNode *, YamlMappingNode *> YamlNodeVisitorTypes;
+
+ class YamlError :
+ public ConfigurationError
+ {
+ public:
+ YamlError(const std::string & msg) throw ();
+ };
+
+ class YamlNode :
+ public virtual VisitableInterface<YamlNodeVisitorTypes>,
+ private InstantiationPolicy<YamlNode, instantiation_method::NonCopyableTag>,
+ public InternalCounted<YamlNode>
+ {
+ protected:
+ YamlNode();
+
+ public:
+ virtual ~YamlNode();
+ };
+
+ class YamlScalarNode :
+ public YamlNode,
+ public Visitable<YamlScalarNode, YamlNodeVisitorTypes>
+ {
+ private:
+ std::string _value;
+ std::string _tag;
+
+ public:
+ typedef CountedPtr<const YamlScalarNode, count_policy::InternalCountTag> ConstPointer;
+ typedef CountedPtr<YamlScalarNode, count_policy::InternalCountTag> Pointer;
+
+ YamlScalarNode(const std::string &, const std::string &);
+
+ std::string value() const
+ {
+ return _value;
+ }
+
+ std::string tag() const
+ {
+ return _tag;
+ }
+ };
+
+ class YamlMappingNode :
+ public YamlNode,
+ public Visitable<YamlMappingNode, YamlNodeVisitorTypes>,
+ private PrivateImplementationPattern<YamlMappingNode>
+ {
+ public:
+ typedef CountedPtr<const YamlMappingNode, count_policy::InternalCountTag> ConstPointer;
+ typedef CountedPtr<YamlMappingNode, count_policy::InternalCountTag> Pointer;
+
+ YamlMappingNode();
+
+ void add(YamlScalarNode::Pointer, YamlNode::Pointer);
+
+ typedef libwrapiter::ForwardIterator<YamlMappingNode,
+ const std::pair<YamlScalarNode::Pointer, YamlNode::Pointer> > Iterator;
+ Iterator begin() const;
+ Iterator end() const;
+
+ std::pair<YamlScalarNode::Pointer, YamlNode::Pointer> & back();
+
+ bool empty() const
+ {
+ return begin() == end();
+ }
+ };
+
+ class YamlSequenceNode :
+ public YamlNode,
+ public Visitable<YamlSequenceNode, YamlNodeVisitorTypes>,
+ private PrivateImplementationPattern<YamlSequenceNode>
+ {
+ public:
+ typedef CountedPtr<YamlSequenceNode, count_policy::InternalCountTag> Pointer;
+ typedef CountedPtr<const YamlSequenceNode, count_policy::InternalCountTag> ConstPointer;
+
+ YamlSequenceNode();
+
+ typedef libwrapiter::ForwardIterator<YamlSequenceNode, const YamlNode::Pointer> Iterator;
+ Iterator begin() const;
+ Iterator end() const;
+
+ void add(YamlNode::Pointer);
+ };
+
+ class YamlDocument :
+ private PrivateImplementationPattern<YamlDocument>,
+ private InstantiationPolicy<YamlDocument, instantiation_method::NonCopyableTag>
+ {
+ public:
+ YamlDocument(const std::string &);
+ YamlDocument(const FSEntry &);
+ ~YamlDocument();
+
+ YamlNode::ConstPointer top() const;
+ };
+}
+
+#endif
diff --git a/paludis/repositories/gems/yaml_TEST.cc b/paludis/repositories/gems/yaml_TEST.cc
new file mode 100644
index 0000000..0d463f8
--- /dev/null
+++ b/paludis/repositories/gems/yaml_TEST.cc
@@ -0,0 +1,145 @@
+/* vim: set sw=4 sts=4 et foldmethod=syntax : */
+
+/*
+ * Copyright (c) 2006 Ciaran McCreesh <ciaranm@ciaranm.org>
+ *
+ * This file is part of the Paludis package manager. Paludis is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU General
+ * Public License version 2, as published by the Free Software Foundation.
+ *
+ * Paludis is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <paludis/repositories/gems/yaml.hh>
+#include <test/test_framework.hh>
+#include <test/test_runner.hh>
+
+using namespace test;
+using namespace paludis;
+
+namespace
+{
+ struct Dumper :
+ YamlNodeVisitorTypes::ConstVisitor
+ {
+ std::string str;
+
+ void visit(const YamlSequenceNode * n)
+ {
+ str.append("seq<");
+ std::for_each(n->begin(), n->end(), accept_visitor(this));
+ str.append(">");
+ }
+
+ void visit(const YamlScalarNode * n)
+ {
+ str.append("scalar<");
+ str.append(n->value());
+ str.append(">");
+ }
+
+ void visit(const YamlMappingNode * n)
+ {
+ str.append("map<");
+ bool need_comma(false);
+ for (YamlMappingNode::Iterator i(n->begin()), i_end(n->end()) ; i != i_end ; ++i)
+ {
+ if (need_comma)
+ str.append(",");
+
+ i->first->accept(this);
+ str.append("=");
+ i->second->accept(this);
+ need_comma = true;
+ }
+ str.append(">");
+ }
+ };
+}
+
+namespace test_cases
+{
+ struct YamlTest : TestCase
+ {
+ YamlTest() : TestCase("yaml") { }
+
+ void run()
+ {
+ YamlDocument yaml("foo");
+ Dumper d;
+ yaml.top()->accept(&d);
+ TEST_CHECK_EQUAL(d.str, "seq<scalar<foo>>");
+ }
+ } test_yaml;
+
+ struct YamlSequenceTest : TestCase
+ {
+ YamlSequenceTest() : TestCase("yaml sequence") { }
+
+ void run()
+ {
+ YamlDocument yaml(
+ "- a\n"
+ "- b\n"
+ "- c\n");
+ Dumper d;
+ yaml.top()->accept(&d);
+ TEST_CHECK_EQUAL(d.str, "seq<seq<scalar<a>scalar<b>scalar<c>>>");
+ }
+ } test_yaml_sequence;
+
+ struct YamlMappingTest : TestCase
+ {
+ YamlMappingTest() : TestCase("yaml mapping") { }
+
+ void run()
+ {
+ YamlDocument yaml(
+ "a: b\n"
+ "c: d\n");
+ Dumper d;
+ yaml.top()->accept(&d);
+ TEST_CHECK_EQUAL(d.str, "seq<map<scalar<a>=scalar<b>,scalar<c>=scalar<d>>>");
+ }
+ } test_yaml_map;
+
+ struct YamlMixedTest : TestCase
+ {
+ YamlMixedTest() : TestCase("yaml mixed") { }
+
+ void run()
+ {
+ YamlDocument yaml(
+"a:\n"
+" b: !c\n"
+" d: e\n"
+" f:\n"
+" - g\n"
+" - h\n"
+"i: j\n");
+ Dumper d;
+ yaml.top()->accept(&d);
+ TEST_CHECK_EQUAL(d.str, "seq<map<scalar<a>=map<scalar<b>=map<scalar<d>=scalar<e>,"
+ "scalar<f>=seq<scalar<g>scalar<h>>>>,scalar<i>=scalar<j>>>");
+ }
+ } test_yaml_mixed;
+
+ struct YamlBadTest : TestCase
+ {
+ YamlBadTest() : TestCase("yaml bad") { }
+
+ void run()
+ {
+ TEST_CHECK_THROWS(YamlDocument yaml("[ [ foo:\n"), YamlError);
+ }
+ } test_yaml_bad;
+
+}
+