aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2023-05-25 21:29:41 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2023-05-29 19:07:54 +0300
commit60698e1786782a834dcc293968c15ecd8821f548 (patch)
tree6125f8661c47031d8facda78d6943a9561062e9c
parentdd49798056b38a39410ffe209e55fad68fc17a60 (diff)
Add package-description, package-description-type, and changes-type package manifest values
-rw-r--r--libbpkg/manifest.cxx682
-rw-r--r--libbpkg/manifest.hxx119
-rw-r--r--tests/manifest/testscript343
3 files changed, 851 insertions, 293 deletions
diff --git a/libbpkg/manifest.cxx b/libbpkg/manifest.cxx
index 3f4b6af..152a513 100644
--- a/libbpkg/manifest.cxx
+++ b/libbpkg/manifest.cxx
@@ -661,6 +661,132 @@ namespace bpkg
return *this;
}
+ // text_type
+ //
+ string
+ to_string (text_type t)
+ {
+ switch (t)
+ {
+ case text_type::plain: return "text/plain";
+ case text_type::github_mark: return "text/markdown;variant=GFM";
+ case text_type::common_mark: return "text/markdown;variant=CommonMark";
+ }
+
+ assert (false); // Can't be here.
+ return string ();
+ }
+
+ optional<text_type>
+ to_text_type (const string& t)
+ {
+ auto bad_type = [] (const string& d) {throw invalid_argument (d);};
+
+ // Parse the media type representation (see RFC2045 for details) into the
+ // type/subtype value and the parameter list. Note: we don't support
+ // parameter quoting and comments for simplicity.
+ //
+ size_t p (t.find (';'));
+ const string& tp (p != string::npos ? trim (string (t, 0, p)) : t);
+
+ small_vector<pair<string, string>, 1> ps;
+
+ while (p != string::npos)
+ {
+ // Extract parameter name.
+ //
+ size_t b (p + 1);
+ p = t.find ('=', b);
+
+ if (p == string::npos)
+ bad_type ("missing '='");
+
+ string n (trim (string (t, b, p - b)));
+
+ // Extract parameter value.
+ //
+ b = p + 1;
+ p = t.find (';', b);
+
+ string v (trim (string (t,
+ b,
+ p != string::npos ? p - b : string::npos)));
+
+ ps.emplace_back (move (n), move (v));
+ }
+
+ // Calculate the resulting text type, failing on unrecognized media type,
+ // unexpected parameter name or value.
+ //
+ // Note that type, subtype, and parameter names are matched
+ // case-insensitively.
+ //
+ optional<text_type> r;
+
+ // Currently only the plain and markdown text types are allowed. Later we
+ // can potentially introduce some other text types.
+ //
+ if (icasecmp (tp, "text/plain") == 0)
+ {
+ // Currently, we don't expect parameters for plain text. Later we can
+ // potentially introduce some plain text variants.
+ //
+ if (ps.empty ())
+ r = text_type::plain;
+ }
+ else if (icasecmp (tp, "text/markdown") == 0)
+ {
+ // Currently, a single optional variant parameter with the two possible
+ // values is allowed for markdown. Later we can potentially introduce
+ // some other markdown variants.
+ //
+ if (ps.empty () ||
+ (ps.size () == 1 && icasecmp (ps[0].first, "variant") == 0))
+ {
+ // Note that markdown variants are matched case-insensitively (see
+ // RFC7763 for details).
+ //
+ string v;
+ if (ps.empty () || icasecmp (v = move (ps[0].second), "GFM") == 0)
+ r = text_type::github_mark;
+ else if (icasecmp (v, "CommonMark") == 0)
+ r = text_type::common_mark;
+ }
+ }
+ else if (icasecmp (tp, "text/", 5) != 0)
+ bad_type ("text type expected");
+
+ return r;
+ }
+
+ // typed_text_file
+ //
+ optional<text_type> typed_text_file::
+ effective_type (bool iu) const
+ {
+ optional<text_type> r;
+
+ if (type)
+ {
+ r = to_text_type (*type);
+ }
+ else if (file)
+ {
+ string ext (path.extension ());
+ if (ext.empty () || icasecmp (ext, "txt") == 0)
+ r = text_type::plain;
+ else if (icasecmp (ext, "md") == 0 || icasecmp (ext, "markdown") == 0)
+ r = text_type::github_mark;
+ }
+ else
+ r = text_type::plain;
+
+ if (!r && !iu)
+ throw invalid_argument ("unknown text type");
+
+ return r;
+ }
+
// manifest_url
//
manifest_url::
@@ -2943,104 +3069,6 @@ namespace bpkg
match_classes (cs, im, expr, r);
}
- // text_type
- //
- string
- to_string (text_type t)
- {
- switch (t)
- {
- case text_type::plain: return "text/plain";
- case text_type::github_mark: return "text/markdown;variant=GFM";
- case text_type::common_mark: return "text/markdown;variant=CommonMark";
- }
-
- assert (false); // Can't be here.
- return string ();
- }
-
- optional<text_type>
- to_text_type (const string& t)
- {
- auto bad_type = [] (const string& d) {throw invalid_argument (d);};
-
- // Parse the media type representation (see RFC2045 for details) into the
- // type/subtype value and the parameter list. Note: we don't support
- // parameter quoting and comments for simplicity.
- //
- size_t p (t.find (';'));
- const string& tp (p != string::npos ? trim (string (t, 0, p)) : t);
-
- small_vector<pair<string, string>, 1> ps;
-
- while (p != string::npos)
- {
- // Extract parameter name.
- //
- size_t b (p + 1);
- p = t.find ('=', b);
-
- if (p == string::npos)
- bad_type ("missing '='");
-
- string n (trim (string (t, b, p - b)));
-
- // Extract parameter value.
- //
- b = p + 1;
- p = t.find (';', b);
-
- string v (trim (string (t,
- b,
- p != string::npos ? p - b : string::npos)));
-
- ps.emplace_back (move (n), move (v));
- }
-
- // Calculate the resulting text type, failing on unrecognized media type,
- // unexpected parameter name or value.
- //
- // Note that type, subtype, and parameter names are matched
- // case-insensitively.
- //
- optional<text_type> r;
-
- // Currently only the plain and markdown text types are allowed. Later we
- // can potentially introduce some other text types.
- //
- if (icasecmp (tp, "text/plain") == 0)
- {
- // Currently, we don't expect parameters for plain text. Later we can
- // potentially introduce some plain text variants.
- //
- if (ps.empty ())
- r = text_type::plain;
- }
- else if (icasecmp (tp, "text/markdown") == 0)
- {
- // Currently, a single optional variant parameter with the two possible
- // values is allowed for markdown. Later we can potentially introduce
- // some other markdown variants.
- //
- if (ps.empty () ||
- (ps.size () == 1 && icasecmp (ps[0].first, "variant") == 0))
- {
- // Note that markdown variants are matched case-insensitively (see
- // RFC7763 for details).
- //
- string v;
- if (ps.empty () || icasecmp (v = move (ps[0].second), "GFM") == 0)
- r = text_type::github_mark;
- else if (icasecmp (v, "CommonMark") == 0)
- r = text_type::common_mark;
- }
- }
- else if (icasecmp (tp, "text/", 5) != 0)
- bad_type ("text type expected");
-
- return r;
- }
-
// test_dependency_type
//
string
@@ -3563,11 +3591,15 @@ namespace bpkg
vector<name_value> requirements;
small_vector<name_value, 1> tests;
- // We will cache the description and its type values to validate them
- // later, after both are parsed.
+ // We will cache the descriptions and changes and their type values to
+ // validate them later, after all are parsed.
//
optional<name_value> description;
optional<name_value> description_type;
+ optional<name_value> package_description;
+ optional<name_value> package_description_type;
+ vector<name_value> changes;
+ optional<name_value> changes_type;
m.build_configs.emplace_back ("default");
@@ -3743,28 +3775,28 @@ namespace bpkg
if (description)
{
if (description->name == "description-file")
- bad_name ("package description and description-file are "
+ bad_name ("project description and description file are "
"mutually exclusive");
else
- bad_name ("package description redefinition");
+ bad_name ("project description redefinition");
}
if (v.empty ())
- bad_value ("empty package description");
+ bad_value ("empty project description");
description = move (nv);
}
else if (n == "description-file")
{
if (flag (package_manifest_flags::forbid_file))
- bad_name ("package description-file not allowed");
+ bad_name ("project description file not allowed");
if (description)
{
if (description->name == "description-file")
- bad_name ("package description-file redefinition");
+ bad_name ("project description file redefinition");
else
- bad_name ("package description-file and description are "
+ bad_name ("project description file and description are "
"mutually exclusive");
}
@@ -3773,32 +3805,69 @@ namespace bpkg
else if (n == "description-type")
{
if (description_type)
- bad_name ("package description-type redefinition");
+ bad_name ("project description type redefinition");
description_type = move (nv);
}
+ else if (n == "package-description")
+ {
+ if (package_description)
+ {
+ if (package_description->name == "package-description-file")
+ bad_name ("package description and description file are "
+ "mutually exclusive");
+ else
+ bad_name ("package description redefinition");
+ }
+
+ if (v.empty ())
+ bad_value ("empty package description");
+
+ package_description = move (nv);
+ }
+ else if (n == "package-description-file")
+ {
+ if (flag (package_manifest_flags::forbid_file))
+ bad_name ("package description file not allowed");
+
+ if (package_description)
+ {
+ if (package_description->name == "package-description-file")
+ bad_name ("package description file redefinition");
+ else
+ bad_name ("package description file and description are "
+ "mutually exclusive");
+ }
+
+ package_description = move (nv);
+ }
+ else if (n == "package-description-type")
+ {
+ if (package_description_type)
+ bad_name ("package description type redefinition");
+
+ package_description_type = move (nv);
+ }
else if (n == "changes")
{
if (v.empty ())
bad_value ("empty package changes specification");
- m.changes.emplace_back (move (v));
+ changes.emplace_back (move (nv));
}
else if (n == "changes-file")
{
if (flag (package_manifest_flags::forbid_file))
bad_name ("package changes-file not allowed");
- auto vc (parser::split_comment (v));
- path p (move (vc.first));
-
- if (p.empty ())
- bad_value ("no path in package changes-file");
-
- if (p.absolute ())
- bad_value ("package changes-file path is absolute");
+ changes.emplace_back (move (nv));
+ }
+ else if (n == "changes-type")
+ {
+ if (changes_type)
+ bad_name ("package changes type redefinition");
- m.changes.emplace_back (move (p), move (vc.second));
+ changes_type = move (nv);
}
else if (n == "url")
{
@@ -4150,23 +4219,24 @@ namespace bpkg
m.upstream_version = move (nv.value);
}
- // Verify that description is specified if the description type is
- // specified.
+ // Parse and validate a text/file manifest value and its respective type
+ // value, if present. Return a typed_text_file object.
//
- if (description_type && !description)
- bad_value ("no package description for specified description type");
-
- // Validate (and set) description and its type.
- //
- if (description)
+ auto parse_text_file = [iu, &nv, &bad_value] (name_value&& text_file,
+ optional<name_value>&& type,
+ const char* what)
+ -> typed_text_file
{
+ typed_text_file r;
+
// Restore as bad_value() uses its line/column.
//
- nv = move (*description);
+ nv = move (text_file);
string& v (nv.value);
+ const string& n (nv.name);
- if (nv.name == "description-file")
+ if (n.size () > 5 && n.compare (n.size () - 5, 5, "-file") == 0)
{
auto vc (parser::split_comment (v));
@@ -4177,44 +4247,134 @@ namespace bpkg
}
catch (const invalid_path& e)
{
- bad_value (string ("invalid package description file: ") +
- e.what ());
+ bad_value (string ("invalid ") + what + " file: " + e.what ());
}
if (p.empty ())
- bad_value ("no path in package description-file");
+ bad_value (string ("no path in ") + what + " file");
if (p.absolute ())
- bad_value ("package description-file path is absolute");
+ bad_value (string (what) + " file path is absolute");
- m.description = text_file (move (p), move (vc.second));
+ r = typed_text_file (move (p), move (vc.second));
}
else
- m.description = text_file (move (v));
+ r = typed_text_file (move (v));
- if (description_type)
- m.description_type = move (description_type->value);
+ if (type)
+ r.type = move (type->value);
- // Verify the description type.
+ // Verify the text type.
//
try
{
- m.effective_description_type (iu);
+ r.effective_type (iu);
}
catch (const invalid_argument& e)
{
- if (description_type)
+ if (type)
{
- // Restore as bad_value() uses its line/column.
+ // Restore as bad_value() uses its line/column. Note that we don't
+ // need to restore the moved out type value.
//
- nv = move (*description_type);
+ nv = move (*type);
- bad_value (string ("invalid package description type: ") +
- e.what ());
+ bad_value (string ("invalid ") + what + " type: " + e.what ());
}
else
- bad_value (string ("invalid package description file: ") +
- e.what ());
+ bad_value (string ("invalid ") + what + " file: " + e.what ());
+ }
+
+ return r;
+ };
+
+ // As above but also accepts nullopt as the text_file argument, in which
+ // case throws manifest_parsing if the type is specified and return
+ // nullopt otherwise.
+ //
+ auto parse_text_file_opt = [&nv, &bad_name, &parse_text_file]
+ (optional<name_value>&& text_file,
+ optional<name_value>&& type,
+ const char* what) -> optional<typed_text_file>
+ {
+ // Verify that the text/file value is specified if the type value is
+ // specified.
+ //
+ if (!text_file)
+ {
+ if (type)
+ {
+ // Restore as bad_name() uses its line/column.
+ //
+ nv = move (*type);
+
+ bad_name (string ("no ") + what + " for specified type");
+ }
+
+ return nullopt;
+ }
+
+ return parse_text_file (move (*text_file), move (type), what);
+ };
+
+ // Parse the project/package descriptions/types.
+ //
+ m.description = parse_text_file_opt (move (description),
+ move (description_type),
+ "project description");
+
+ m.package_description =
+ parse_text_file_opt (move (package_description),
+ move (package_description_type),
+ "package description");
+
+ // Parse the package changes/types.
+ //
+ // Note: at the end of the loop the changes_type variable may contain
+ // value in unspecified state but we can still check for the value
+ // presence.
+ //
+ for (name_value& c: changes)
+ {
+ // Move the changes_type value from for the last changes entry.
+ //
+ m.changes.push_back (
+ parse_text_file (move (c),
+ (&c != &changes.back ()
+ ? optional<name_value> (changes_type)
+ : move (changes_type)),
+ "changes"));
+ }
+
+ // If there are multiple changes and the changes type is not explicitly
+ // specified, then verify that all changes effective types are the same.
+ // Note that in the "ignore unknown" mode there can be unresolved
+ // effective types which we just skip.
+ //
+ if (changes.size () > 1 && !changes_type)
+ {
+ optional<text_type> type;
+
+ for (size_t i (0); i != m.changes.size (); ++i)
+ {
+ const typed_text_file& c (m.changes[i]);
+
+ if (optional<text_type> t = c.effective_type (iu))
+ {
+ if (!type)
+ {
+ type = *t;
+ }
+ else if (*t != *type)
+ {
+ // Restore as bad_value() uses its line/column.
+ //
+ nv = move (changes[i]);
+
+ bad_value ("changes type '" + to_string (*t) + "' differs from " +
+ " previous type '" + to_string (*type) + "'");
+ }
+ }
}
}
@@ -4336,10 +4496,32 @@ namespace bpkg
if (!m.sha256sum && flag (package_manifest_flags::require_sha256sum))
bad_name ("no package sha256sum specified");
- if (m.description &&
- !m.description_type &&
- flag (package_manifest_flags::require_description_type))
- bad_name ("no package description type specified");
+ if (flag (package_manifest_flags::require_text_type))
+ {
+ if (m.description && !m.description->type)
+ bad_name ("no project description type specified");
+
+ if (m.package_description && !m.package_description->type)
+ bad_name ("no package description type specified");
+
+ // Note that changes either all have the same explicitly specified type
+ // or have no type.
+ //
+ if (!m.changes.empty () && !m.changes.front ().type)
+ {
+ // @@ TMP To support older repositories allow absent changes type
+ // until toolchain 0.16.0 is released.
+ //
+ // Note that for such repositories the packages may not have
+ // changes values other than plan text. Thus, we can safely set
+ // this type, if they are absent, so that the caller can always
+ // be sure that these values are always present for package
+ // manifest lists.
+ //bad_name ("no package changes type specified");
+ for (typed_text_file& c: m.changes)
+ c.type = "text/plain";
+ }
+ }
if (!m.bootstrap_build &&
flag (package_manifest_flags::require_bootstrap_build))
@@ -4406,7 +4588,7 @@ namespace bpkg
package_manifest_flags::forbid_fragment |
package_manifest_flags::forbid_incomplete_values |
package_manifest_flags::require_location |
- package_manifest_flags::require_description_type |
+ package_manifest_flags::require_text_type |
package_manifest_flags::require_bootstrap_build);
}
@@ -4497,33 +4679,6 @@ namespace bpkg
return r;
}
- optional<text_type> package_manifest::
- effective_description_type (bool iu) const
- {
- if (!description)
- throw logic_error ("absent description");
-
- optional<text_type> r;
-
- if (description_type)
- r = to_text_type (*description_type);
- else if (description->file)
- {
- string ext (description->path.extension ());
- if (ext.empty () || icasecmp (ext, "txt") == 0)
- r = text_type::plain;
- else if (icasecmp (ext, "md") == 0 || icasecmp (ext, "markdown") == 0)
- r = text_type::github_mark;
- }
- else
- r = text_type::plain;
-
- if (!r && !iu)
- throw invalid_argument ("unknown text type");
-
- return r;
- }
-
// If validate_only is true, then the package manifest is assumed to be
// default constructed and is used as a storage for convenience of the
// validation implementation.
@@ -4846,66 +5001,87 @@ namespace bpkg
bpkg::override (nvs, name, p, true /* validate_only */);
}
- static const string description_file ("description-file");
- static const string changes_file ("changes-file");
- static const string build_file ("build-file");
+ static const string description_file ("description-file");
+ static const string package_description_file ("package-description-file");
+ static const string changes_file ("changes-file");
+ static const string build_file ("build-file");
void package_manifest::
load_files (const function<load_function>& loader, bool iu)
{
- // Load a file and verify that its content is not empty, if the loader
- // returns the content.
+ // If required, load a file and verify that its content is not empty, if
+ // the loader returns the content. Make the text type explicit.
//
- auto load = [&loader] (const string& n, const path& p)
+ auto load = [iu, &loader] (typed_text_file& text,
+ const string& file_value_name)
{
- optional<string> r (loader (n, p));
+ // Make the type explicit.
+ //
+ optional<text_type> t;
- if (r && r->empty ())
- throw parsing ("package " + n + " references empty file");
+ // Convert the potential invalid_argument exception to the
+ // manifest_parsing exception similar to what we do in the manifest
+ // parser.
+ //
+ try
+ {
+ t = text.effective_type (iu);
+ }
+ catch (const invalid_argument& e)
+ {
+ if (text.type)
+ {
+ // Strip trailing "-file".
+ //
+ string prefix (file_value_name, 0, file_value_name.size () - 5);
- return r;
- };
+ throw parsing ("invalid " + prefix + "-type package manifest " +
+ "value: " + e.what ());
+ }
+ else
+ {
+ throw parsing ("invalid " + file_value_name + " package " +
+ "manifest value: " + e.what ());
+ }
+ }
- // Load the description-file manifest value.
- //
- if (description)
- {
- // Make the description type explicit.
- //
- optional<text_type> t (effective_description_type (iu)); // Can throw.
assert (t || iu); // Can only be absent if we ignore unknown.
- if (!description_type && t)
- description_type = to_string (*t);
+ if (!text.type && t)
+ text.type = to_string (*t);
- // At this point the description type can only be absent if the
- // description comes from a file. Otherwise, we would end up with the
- // plain text.
+ // At this point the type can only be absent if the text comes from a
+ // file. Otherwise, we would end up with the plain text.
//
- assert (description_type || description->file);
+ assert (text.type || text.file);
- if (description->file)
+ if (text.file)
{
- if (!description_type)
- description_type = "text/unknown; extension=" +
- description->path.extension ();
+ if (!text.type)
+ text.type = "text/unknown; extension=" + text.path.extension ();
+
+ if (optional<string> fc = loader (file_value_name, text.path))
+ {
+ if (fc->empty ())
+ throw parsing ("package manifest value " + file_value_name +
+ " references empty file");
- if (optional<string> fc = load (description_file, description->path))
- description = text_file (move (*fc));
+ text = typed_text_file (move (*fc), move (text.type));
+ }
}
- }
+ };
- // Load the changes-file manifest values.
+ // Load the descriptions and changes, if present.
//
- for (text_file& c: changes)
- {
- if (c.file)
- {
- if (optional<string> fc = load (changes_file, c.path))
- c = text_file (move (*fc));
- }
- }
+ if (description)
+ load (*description, description_file);
+
+ if (package_description)
+ load (*package_description, package_description_file);
+
+ for (typed_text_file& c: changes)
+ load (c, changes_file);
// Load the build-file manifest values.
//
@@ -4996,26 +5172,46 @@ namespace bpkg
if (!m.keywords.empty ())
s.next ("keywords", concatenate (m.keywords, " "));
- if (m.description)
+ auto serialize_text_file = [&s] (const text_file& v, const string& n)
{
- if (m.description->file)
- s.next ("description-file",
- serializer::merge_comment (m.description->path.string (),
- m.description->comment));
+ if (v.file)
+ s.next (n + "-file",
+ serializer::merge_comment (v.path.string (), v.comment));
else
- s.next ("description", m.description->text);
+ s.next (n, v.text);
+ };
- if (m.description_type)
- s.next ("description-type", *m.description_type);
- }
+ auto serialize_description = [&s, &serialize_text_file]
+ (const optional<typed_text_file>& desc,
+ const char* prefix)
+ {
+ if (desc)
+ {
+ string p (prefix);
+ serialize_text_file (*desc, p + "description");
+
+ if (desc->type)
+ s.next (p + "description-type", *desc->type);
+ }
+ };
+
+ serialize_description (m.description, "" /* prefix */);
+ serialize_description (m.package_description, "package-");
for (const auto& c: m.changes)
+ serialize_text_file (c, "changes");
+
+ // If there are any changes, then serialize the type of the first
+ // changes entry, if present. Note that if it is present, then we assume
+ // that the type was specified explicitly and so it is the same for all
+ // entries.
+ //
+ if (!m.changes.empty ())
{
- if (c.file)
- s.next ("changes-file",
- serializer::merge_comment (c.path.string (), c.comment));
- else
- s.next ("changes", c.text);
+ const typed_text_file& c (m.changes.front ());
+
+ if (c.type)
+ s.next ("changes-type", *c.type);
}
if (m.url)
@@ -5393,20 +5589,26 @@ namespace bpkg
d + " for " + p.name.string () + '-' + p.version.string ());
};
- if (p.description)
+ // Throw manifest_serialization if the text is in a file or untyped.
+ //
+ auto verify_text_file = [&bad_value] (const typed_text_file& v,
+ const string& n)
{
- if (p.description->file)
- bad_value ("forbidden description-file");
+ if (v.file)
+ bad_value ("forbidden " + n + "-file");
- if (!p.description_type)
- bad_value ("no valid description-type");
- }
+ if (!v.type)
+ bad_value ("no valid " + n + "-type");
+ };
+
+ if (p.description)
+ verify_text_file (*p.description, "description");
+
+ if (p.package_description)
+ verify_text_file (*p.package_description, "package-description");
for (const auto& c: p.changes)
- {
- if (c.file)
- bad_value ("forbidden changes-file");
- }
+ verify_text_file (c, "changes");
if (!p.buildfile_paths.empty ())
bad_value ("forbidden build-file");
diff --git a/libbpkg/manifest.hxx b/libbpkg/manifest.hxx
index c922cc4..dce0870 100644
--- a/libbpkg/manifest.hxx
+++ b/libbpkg/manifest.hxx
@@ -206,11 +206,6 @@ namespace bpkg
language (std::string n, bool i): name (std::move (n)), impl (i) {}
};
- // description
- // description-file
- // change
- // change-file
- //
class LIBBPKG_EXPORT text_file
{
public:
@@ -244,6 +239,72 @@ namespace bpkg
~text_file ();
};
+ enum class text_type
+ {
+ plain,
+ common_mark,
+ github_mark
+ };
+
+ LIBBPKG_EXPORT std::string
+ to_string (text_type);
+
+ // Throw std::invalid_argument if the argument is not a well-formed text
+ // type. Otherwise, return nullopt for an unknown text variant.
+ //
+ LIBBPKG_EXPORT butl::optional<text_type>
+ to_text_type (const std::string&);
+
+ inline std::ostream&
+ operator<< (std::ostream& os, text_type t)
+ {
+ return os << to_string (t);
+ }
+
+ // description
+ // description-file
+ // description-type
+ // package-description
+ // package-description-file
+ // package-description-type
+ // change
+ // change-file
+ // change-type
+ //
+ class LIBBPKG_EXPORT typed_text_file: public text_file
+ {
+ public:
+ butl::optional<std::string> type;
+
+ // File text constructor.
+ //
+ explicit
+ typed_text_file (std::string s = "",
+ butl::optional<std::string> t = butl::nullopt)
+ : text_file (std::move (s)), type (std::move (t)) {}
+
+ // File reference constructor.
+ //
+ typed_text_file (path_type p,
+ std::string c,
+ butl::optional<std::string> t = butl::nullopt)
+ : text_file (std::move (p), std::move (c)), type (std::move (t)) {}
+
+ // Return the type value if present, text_type::github_mark if it refers
+ // to a file with the .md or .markdown extension and text_type::plain if
+ // it refers to a file with the .txt extension or no extension or the text
+ // does not come from a file. Depending on the ignore_unknown value either
+ // throw std::invalid_argument or return nullopt if the type value or the
+ // file extension is unknown.
+ //
+ // Note: also throws std::invalid_argument if the type is not well-formed.
+ // This, however, may not happen for an object created by the package
+ // manifest parser since it has already verified that.
+ //
+ butl::optional<text_type>
+ effective_type (bool ignore_unknown = false) const;
+ };
+
// license
//
class licenses: public butl::small_vector<std::string, 1>
@@ -774,7 +835,7 @@ namespace bpkg
require_location = 0x020,
require_sha256sum = 0x040,
- require_description_type = 0x080,
+ require_text_type = 0x080, // description-type, changes-type, etc.
require_bootstrap_build = 0x100
};
@@ -965,28 +1026,6 @@ namespace bpkg
}
};
- enum class text_type
- {
- plain,
- common_mark,
- github_mark
- };
-
- LIBBPKG_EXPORT std::string
- to_string (text_type);
-
- // Throw std::invalid_argument if the argument is not a well-formed text
- // type. Otherwise, return nullopt for an unknown text variant.
- //
- LIBBPKG_EXPORT butl::optional<text_type>
- to_text_type (const std::string&); // May throw std::invalid_argument.
-
- inline std::ostream&
- operator<< (std::ostream& os, text_type t)
- {
- return os << to_string (t);
- }
-
enum class test_dependency_type
{
tests,
@@ -1115,9 +1154,9 @@ namespace bpkg
butl::small_vector<std::string, 5> topics;
butl::small_vector<std::string, 5> keywords;
- butl::optional<text_file> description;
- butl::optional<std::string> description_type;
- butl::small_vector<text_file, 1> changes;
+ butl::optional<typed_text_file> description;
+ butl::optional<typed_text_file> package_description;
+ butl::small_vector<typed_text_file, 1> changes;
butl::optional<manifest_url> url;
butl::optional<manifest_url> doc_url;
butl::optional<manifest_url> src_url;
@@ -1208,17 +1247,6 @@ namespace bpkg
const package_name&
effective_project () const noexcept {return project ? *project : name;}
- // Return the description type value if present, text_type::github_mark if
- // the description refers to a file with the .md or .markdown extension
- // and text_type::plain if it refers to a file with the .txt extension or
- // no extension or the description does not come from a file. Depending on
- // the ignore_unknown value either throw std::invalid_argument or return
- // nullopt if the description value or the file extension is unknown.
- // Throw std::logic_error if the description value is nullopt.
- //
- butl::optional<text_type>
- effective_description_type (bool ignore_unknown = false) const;
-
public:
package_manifest () = default;
@@ -1368,9 +1396,10 @@ namespace bpkg
// Load the *-file manifest values using the specified load function that
// returns the file contents passing through any exception it may throw.
// If nullopt is returned, then the respective *-file value is left
- // unexpanded. Set the potentially absent description type value to the
- // effective description type. If the effective type is nullopt then
- // assign a synthetic unknown type.
+ // unexpanded. Set the potentially absent project description, package
+ // description, and changes type values to their effective types. If an
+ // effective type is nullopt then assign a synthetic unknown type if the
+ // ignore_unknown argument is true and throw manifest_parsing otherwise.
//
// Note that if the returned file contents is empty, load_files() makes
// sure that this is allowed by the value's semantics throwing
diff --git a/tests/manifest/testscript b/tests/manifest/testscript
index 222d262..4ad8f0e 100644
--- a/tests/manifest/testscript
+++ b/tests/manifest/testscript
@@ -427,9 +427,9 @@
description-file: /README
EOI
%(
- stdin:6:19: error: package description-file path is absolute
+ stdin:6:19: error: project description file path is absolute
%|
- stdin:6:19: error: invalid package description file: invalid filesystem path
+ stdin:6:19: error: invalid project description file: invalid filesystem path
%)
EOE
}
@@ -459,7 +459,20 @@
description: libfoo is a very modern C++ XML parser.
description-type: image/gif
EOI
- stdin:7:19: error: invalid package description type: text type expected
+ stdin:7:19: error: invalid project description type: text type expected
+ EOE
+
+ : no-description
+ :
+ $* <<EOI 2>>EOE != 0
+ : 1
+ name: libfoo
+ version: 2.0.0
+ summary: Modern C++ parser
+ license: LGPLv2
+ description-type: image/gif
+ EOI
+ stdin:6:1: error: no project description for specified type
EOE
: deducing
@@ -475,7 +488,7 @@
license: LGPLv2
description-file: README.rtf
EOI
- stdin:6:19: error: invalid package description file: unknown text type
+ stdin:6:19: error: invalid project description file: unknown text type
EOE
: ignore-unknown
@@ -504,7 +517,7 @@
description: libfoo is a very modern C++ XML parser.
description-type: text/markdowns
EOI
- stdin:7:19: error: invalid package description type: unknown text type
+ stdin:7:19: error: invalid project description type: unknown text type
EOE
: ignore
@@ -546,7 +559,7 @@
description: libfoo is a very modern C++ XML parser.
description-type: text/plain;
EOI
- stdin:7:19: error: invalid package description type: missing '='
+ stdin:7:19: error: invalid project description type: missing '='
EOE
}
@@ -603,7 +616,7 @@
description: libfoo is a very modern C++ XML parser.
description-type: text/markdown; variant=Original
EOI
- stdin:7:19: error: invalid package description type: unknown text type
+ stdin:7:19: error: invalid project description type: unknown text type
EOE
: ignore
@@ -633,7 +646,7 @@
description: libfoo is a very modern C++ XML parser.
description-type: text/markdown; variants=GFM
EOI
- stdin:7:19: error: invalid package description type: unknown text type
+ stdin:7:19: error: invalid project description type: unknown text type
EOE
: ignore
@@ -651,6 +664,317 @@
}
}
+ : package-description-file
+ :
+ {
+ : absolute-path
+ :
+ $* <<EOI 2>>~%EOE% != 0
+ : 1
+ name: libfoo
+ version: 2.0.0
+ summary: Modern C++ parser
+ license: LGPLv2
+ package-description-file: /README
+ EOI
+ %(
+ stdin:6:27: error: package description file path is absolute
+ %|
+ stdin:6:27: error: invalid package description file: invalid filesystem path
+ %)
+ EOE
+ }
+
+ : package-description-type
+ :
+ {
+ : absent
+ :
+ $* <<EOF >>EOF
+ : 1
+ name: libfoo
+ version: 2.0.0
+ summary: Modern C++ parser
+ license: LGPLv2
+ package-description: libfoo is a very modern C++ XML parser.
+ EOF
+
+ : not-text
+ :
+ $* <<EOI 2>>EOE != 0
+ : 1
+ name: libfoo
+ version: 2.0.0
+ summary: Modern C++ parser
+ license: LGPLv2
+ package-description: libfoo is a very modern C++ XML parser.
+ package-description-type: image/gif
+ EOI
+ stdin:7:27: error: invalid package description type: text type expected
+ EOE
+
+ : no-description
+ :
+ $* <<EOI 2>>EOE != 0
+ : 1
+ name: libfoo
+ version: 2.0.0
+ summary: Modern C++ parser
+ license: LGPLv2
+ package-description-type: image/gif
+ EOI
+ stdin:6:1: error: no package description for specified type
+ EOE
+
+ : deducing
+ :
+ {
+ : fail
+ :
+ $* <<EOI 2>>EOE != 0
+ : 1
+ name: libfoo
+ version: 2.0.0
+ summary: Modern C++ parser
+ license: LGPLv2
+ package-description-file: README.rtf
+ EOI
+ stdin:6:27: error: invalid package description file: unknown text type
+ EOE
+
+ : ignore-unknown
+ :
+ $* -i <<EOF >>EOF
+ : 1
+ name: libfoo
+ version: 2.0.0
+ summary: Modern C++ parser
+ license: LGPLv2
+ package-description-file: README.rtf
+ EOF
+ }
+
+ : unknown
+ :
+ {
+ : fail
+ :
+ $* <<EOI 2>>EOE != 0
+ : 1
+ name: libfoo
+ version: 2.0.0
+ summary: Modern C++ parser
+ license: LGPLv2
+ package-description: libfoo is a very modern C++ XML parser.
+ package-description-type: text/markdowns
+ EOI
+ stdin:7:27: error: invalid package description type: unknown text type
+ EOE
+
+ : ignore
+ :
+ $* -i <<EOF >>EOF
+ : 1
+ name: libfoo
+ version: 2.0.0
+ summary: Modern C++ parser
+ license: LGPLv2
+ package-description: libfoo is a very modern C++ XML parser.
+ package-description-type: text/markdowns
+ EOF
+ }
+
+ : plain
+ :
+ {
+ : valid
+ :
+ $* <<EOF >>EOF
+ : 1
+ name: libfoo
+ version: 2.0.0
+ summary: Modern C++ parser
+ license: LGPLv2
+ package-description: libfoo is a very modern C++ XML parser.
+ package-description-type: text/plain
+ EOF
+
+ : invalid
+ :
+ $* <<EOI 2>>EOE != 0
+ : 1
+ name: libfoo
+ version: 2.0.0
+ summary: Modern C++ parser
+ license: LGPLv2
+ package-description: libfoo is a very modern C++ XML parser.
+ package-description-type: text/plain;
+ EOI
+ stdin:7:27: error: invalid package description type: missing '='
+ EOE
+ }
+
+ : markdown
+ :
+ {
+ : default
+ :
+ $* <<EOF >>EOF
+ : 1
+ name: libfoo
+ version: 2.0.0
+ summary: Modern C++ parser
+ license: LGPLv2
+ package-description: libfoo is a very modern C++ XML parser.
+ package-description-type: text/markdown
+ EOF
+
+ : gfm
+ :
+ $* <<EOF >>EOF
+ : 1
+ name: libfoo
+ version: 2.0.0
+ summary: Modern C++ parser
+ license: LGPLv2
+ package-description: libfoo is a very modern C++ XML parser.
+ package-description-type: text/markdown; variant=GFM
+ EOF
+
+ : common-mark
+ :
+ $* <<EOF >>EOF
+ : 1
+ name: libfoo
+ version: 2.0.0
+ summary: Modern C++ parser
+ license: LGPLv2
+ package-description: libfoo is a very modern C++ XML parser.
+ package-description-type: text/markdown; variant=CommonMark
+ EOF
+
+ : invalid-variant
+ :
+ {
+ : fail
+ :
+ $* <<EOI 2>>EOE != 0
+ : 1
+ name: libfoo
+ version: 2.0.0
+ summary: Modern C++ parser
+ license: LGPLv2
+ package-description: libfoo is a very modern C++ XML parser.
+ package-description-type: text/markdown; variant=Original
+ EOI
+ stdin:7:27: error: invalid package description type: unknown text type
+ EOE
+
+ : ignore
+ :
+ $* -i <<EOF >>EOF
+ : 1
+ name: libfoo
+ version: 2.0.0
+ summary: Modern C++ parser
+ license: LGPLv2
+ package-description: libfoo is a very modern C++ XML parser.
+ package-description-type: text/markdown; variant=Original
+ EOF
+ }
+
+ : invalid-parameter
+ :
+ {
+ : fail
+ :
+ $* <<EOI 2>>EOE != 0
+ : 1
+ name: libfoo
+ version: 2.0.0
+ summary: Modern C++ parser
+ license: LGPLv2
+ package-description: libfoo is a very modern C++ XML parser.
+ package-description-type: text/markdown; variants=GFM
+ EOI
+ stdin:7:27: error: invalid package description type: unknown text type
+ EOE
+
+ : ignore
+ :
+ $* -i <<EOF >>EOF
+ : 1
+ name: libfoo
+ version: 2.0.0
+ summary: Modern C++ parser
+ license: LGPLv2
+ package-description: libfoo is a very modern C++ XML parser.
+ package-description-type: text/markdown; variants=GFM
+ EOF
+ }
+ }
+ }
+
+ : changes-file
+ :
+ {
+ : absolute-path
+ :
+ $* <<EOI 2>>~%EOE% != 0
+ : 1
+ name: libfoo
+ version: 2.0.0
+ summary: Modern C++ parser
+ license: LGPLv2
+ changes-file: /CHANGES
+ EOI
+ %(
+ stdin:6:15: error: changes file path is absolute
+ %|
+ stdin:6:15: error: invalid changes file: invalid filesystem path
+ %)
+ EOE
+
+ : different-type
+ :
+ $* <<EOI 2>>EOE != 0
+ : 1
+ name: libfoo
+ version: 2.0.0
+ summary: Modern C++ parser
+ license: LGPLv2
+ changes-file: CHANGES1
+ changes-file: CHANGES2.md
+ EOI
+ stdin:7:15: error: changes type 'text/markdown;variant=GFM' differs from previous type 'text/plain'
+ EOE
+
+ : same-type
+ :
+ $* <<EOF >>EOF
+ : 1
+ name: libfoo
+ version: 2.0.0
+ summary: Modern C++ parser
+ license: LGPLv2
+ changes-file: CHANGES1.markdown
+ changes-file: CHANGES2.md
+ EOF
+
+ : explicit-type
+ :
+ $* <<EOF >>EOF
+ : 1
+ name: libfoo
+ version: 2.0.0
+ summary: Modern C++ parser
+ license: LGPLv2
+ changes-file: CHANGES1
+ changes-file: CHANGES2.md
+ changes-type: text/plain
+ EOF
+ }
+
: src-url
:
{
@@ -4077,8 +4401,11 @@
keywords: c++ xml parser serializer pull
description: libfoo is a very modern C++ XML parser.
description-type: text/plain
+ package-description: packaged for build2.
+ package-description-type: text/plain
changes: 1.2.3+2: applied upstream patch for critical bug bar
changes: 1.2.3+1: applied upstream patch for critical bug foo
+ changes-type: text/plain
url: http://www.example.org/projects/libfoo/; libfoo project page url
doc-url: http://www.example.org/projects/libfoo/man.xhtml; documentation page
src-url: http://scm.example.com/?p=odb/libodb.git\;a=tree; source tree