Newer
Older
dub_jkp / source / dub / internal / dyaml / resolver.d
  1.  
  2. // Copyright Ferdinand Majerech 2011.
  3. // Distributed under the Boost Software License, Version 1.0.
  4. // (See accompanying file LICENSE_1_0.txt or copy at
  5. // http://www.boost.org/LICENSE_1_0.txt)
  6.  
  7. /**
  8. * Implements a class that resolves YAML tags. This can be used to implicitly
  9. * resolve tags for custom data types, removing the need to explicitly
  10. * specify tags in YAML. A tutorial can be found
  11. * $(LINK2 ../tutorials/custom_types.html, here).
  12. *
  13. * Code based on $(LINK2 http://www.pyyaml.org, PyYAML).
  14. */
  15. module dub.internal.dyaml.resolver;
  16.  
  17.  
  18. import std.conv;
  19. import std.regex;
  20. import std.typecons;
  21. import std.utf;
  22.  
  23. import dub.internal.dyaml.node;
  24. import dub.internal.dyaml.exception;
  25.  
  26.  
  27. /// Type of `regexes`
  28. private alias RegexType = Tuple!(string, "tag", const Regex!char, "regexp", string, "chars");
  29.  
  30. private immutable RegexType[] regexes = [
  31. RegexType("tag:yaml.org,2002:bool",
  32. regex(r"^(?:yes|Yes|YES|no|No|NO|true|True|TRUE" ~
  33. "|false|False|FALSE|on|On|ON|off|Off|OFF)$"),
  34. "yYnNtTfFoO"),
  35. RegexType("tag:yaml.org,2002:float",
  36. regex(r"^(?:[-+]?([0-9][0-9_]*)\\.[0-9_]*" ~
  37. "(?:[eE][-+][0-9]+)?|[-+]?(?:[0-9][0-9_]" ~
  38. "*)?\\.[0-9_]+(?:[eE][-+][0-9]+)?|[-+]?" ~
  39. "[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]" ~
  40. "*|[-+]?\\.(?:inf|Inf|INF)|\\." ~
  41. "(?:nan|NaN|NAN))$"),
  42. "-+0123456789."),
  43. RegexType("tag:yaml.org,2002:int",
  44. regex(r"^(?:[-+]?0b[0-1_]+" ~
  45. "|[-+]?0[0-7_]+" ~
  46. "|[-+]?(?:0|[1-9][0-9_]*)" ~
  47. "|[-+]?0x[0-9a-fA-F_]+" ~
  48. "|[-+]?[1-9][0-9_]*(?::[0-5]?[0-9])+)$"),
  49. "-+0123456789"),
  50. RegexType("tag:yaml.org,2002:merge", regex(r"^<<$"), "<"),
  51. RegexType("tag:yaml.org,2002:null",
  52. regex(r"^$|^(?:~|null|Null|NULL)$"), "~nN\0"),
  53. RegexType("tag:yaml.org,2002:timestamp",
  54. regex(r"^[0-9][0-9][0-9][0-9]-[0-9][0-9]-" ~
  55. "[0-9][0-9]|[0-9][0-9][0-9][0-9]-[0-9]" ~
  56. "[0-9]?-[0-9][0-9]?[Tt]|[ \t]+[0-9]" ~
  57. "[0-9]?:[0-9][0-9]:[0-9][0-9]" ~
  58. "(?:\\.[0-9]*)?(?:[ \t]*Z|[-+][0-9]" ~
  59. "[0-9]?(?::[0-9][0-9])?)?$"),
  60. "0123456789"),
  61. RegexType("tag:yaml.org,2002:value", regex(r"^=$"), "="),
  62.  
  63. //The following resolver is only for documentation purposes. It cannot work
  64. //because plain scalars cannot start with '!', '&', or '*'.
  65. RegexType("tag:yaml.org,2002:yaml", regex(r"^(?:!|&|\*)$"), "!&*"),
  66. ];
  67.  
  68. /**
  69. * Resolves YAML tags (data types).
  70. *
  71. * Can be used to implicitly resolve custom data types of scalar values.
  72. */
  73. struct Resolver
  74. {
  75. private:
  76. // Default tag to use for scalars.
  77. string defaultScalarTag_ = "tag:yaml.org,2002:str";
  78. // Default tag to use for sequences.
  79. string defaultSequenceTag_ = "tag:yaml.org,2002:seq";
  80. // Default tag to use for mappings.
  81. string defaultMappingTag_ = "tag:yaml.org,2002:map";
  82.  
  83. /*
  84. * Arrays of scalar resolver tuples indexed by starting character of a scalar.
  85. *
  86. * Each tuple stores regular expression the scalar must match,
  87. * and tag to assign to it if it matches.
  88. */
  89. Tuple!(string, const Regex!char)[][dchar] yamlImplicitResolvers_;
  90.  
  91. package:
  92. static auto withDefaultResolvers() @safe
  93. {
  94. Resolver resolver;
  95. foreach(pair; regexes)
  96. {
  97. resolver.addImplicitResolver(pair.tag, pair.regexp, pair.chars);
  98. }
  99. return resolver;
  100. }
  101.  
  102. public:
  103. @disable bool opEquals(ref Resolver);
  104. @disable int opCmp(ref Resolver);
  105.  
  106. /**
  107. * Add an implicit scalar resolver.
  108. *
  109. * If a scalar matches regexp and starts with any character in first,
  110. * its _tag is set to tag. If it matches more than one resolver _regexp
  111. * resolvers added _first override ones added later. Default resolvers
  112. * override any user specified resolvers, but they can be disabled in
  113. * Resolver constructor.
  114. *
  115. * If a scalar is not resolved to anything, it is assigned the default
  116. * YAML _tag for strings.
  117. *
  118. * Params: tag = Tag to resolve to.
  119. * regexp = Regular expression the scalar must match to have this _tag.
  120. * first = String of possible starting characters of the scalar.
  121. *
  122. */
  123. void addImplicitResolver(string tag, const Regex!char regexp, string first)
  124. pure @safe
  125. {
  126. foreach(const dchar c; first)
  127. {
  128. if((c in yamlImplicitResolvers_) is null)
  129. {
  130. yamlImplicitResolvers_[c] = [];
  131. }
  132. yamlImplicitResolvers_[c] ~= tuple(tag, regexp);
  133. }
  134. }
  135. /// Resolve scalars starting with 'A' to !_tag
  136. @safe unittest
  137. {
  138. import std.file : write;
  139. import std.regex : regex;
  140. import dub.internal.dyaml.loader : Loader;
  141. import dub.internal.dyaml.resolver : Resolver;
  142.  
  143. write("example.yaml", "A");
  144.  
  145. auto loader = Loader.fromFile("example.yaml");
  146. loader.resolver.addImplicitResolver("!tag", regex("A.*"), "A");
  147.  
  148. auto node = loader.load();
  149. assert(node.tag == "!tag");
  150. }
  151.  
  152. package:
  153. /**
  154. * Resolve tag of a node.
  155. *
  156. * Params: kind = Type of the node.
  157. * tag = Explicit tag of the node, if any.
  158. * value = Value of the node, if any.
  159. * implicit = Should the node be implicitly resolved?
  160. *
  161. * If the tag is already specified and not non-specific, that tag will
  162. * be returned.
  163. *
  164. * Returns: Resolved tag.
  165. */
  166. string resolve(const NodeID kind, const string tag, scope string value,
  167. const bool implicit) @safe
  168. {
  169. import std.array : empty, front;
  170. if((tag !is null) && (tag != "!"))
  171. {
  172. return tag;
  173. }
  174.  
  175. final switch (kind)
  176. {
  177. case NodeID.scalar:
  178. if(!implicit)
  179. {
  180. return defaultScalarTag_;
  181. }
  182.  
  183. //Get the first char of the value.
  184. const dchar first = value.empty ? '\0' : value.front;
  185.  
  186. auto resolvers = (first in yamlImplicitResolvers_) is null ?
  187. [] : yamlImplicitResolvers_[first];
  188.  
  189. //If regexp matches, return tag.
  190. foreach(resolver; resolvers)
  191. {
  192. // source/dyaml/resolver.d(192,35): Error: scope variable `__tmpfordtorXXX`
  193. // assigned to non-scope parameter `this` calling
  194. // `std.regex.RegexMatch!string.RegexMatch.~this`
  195. bool isEmpty = () @trusted {
  196. return match(value, resolver[1]).empty;
  197. }();
  198. if(!isEmpty)
  199. {
  200. return resolver[0];
  201. }
  202. }
  203. return defaultScalarTag_;
  204. case NodeID.sequence:
  205. return defaultSequenceTag_;
  206. case NodeID.mapping:
  207. return defaultMappingTag_;
  208. case NodeID.invalid:
  209. assert(false, "Cannot resolve an invalid node");
  210. }
  211. }
  212. @safe unittest
  213. {
  214. auto resolver = Resolver.withDefaultResolvers;
  215.  
  216. bool tagMatch(string tag, string[] values) @safe
  217. {
  218. const string expected = tag;
  219. foreach(value; values)
  220. {
  221. const string resolved = resolver.resolve(NodeID.scalar, null, value, true);
  222. if(expected != resolved)
  223. {
  224. return false;
  225. }
  226. }
  227. return true;
  228. }
  229.  
  230. assert(tagMatch("tag:yaml.org,2002:bool",
  231. ["yes", "NO", "True", "on"]));
  232. assert(tagMatch("tag:yaml.org,2002:float",
  233. ["6.8523015e+5", "685.230_15e+03", "685_230.15",
  234. "190:20:30.15", "-.inf", ".NaN"]));
  235. assert(tagMatch("tag:yaml.org,2002:int",
  236. ["685230", "+685_230", "02472256", "0x_0A_74_AE",
  237. "0b1010_0111_0100_1010_1110", "190:20:30"]));
  238. assert(tagMatch("tag:yaml.org,2002:merge", ["<<"]));
  239. assert(tagMatch("tag:yaml.org,2002:null", ["~", "null", ""]));
  240. assert(tagMatch("tag:yaml.org,2002:str",
  241. ["abcd", "9a8b", "9.1adsf"]));
  242. assert(tagMatch("tag:yaml.org,2002:timestamp",
  243. ["2001-12-15T02:59:43.1Z",
  244. "2001-12-14t21:59:43.10-05:00",
  245. "2001-12-14 21:59:43.10 -5",
  246. "2001-12-15 2:59:43.10",
  247. "2002-12-14"]));
  248. assert(tagMatch("tag:yaml.org,2002:value", ["="]));
  249. assert(tagMatch("tag:yaml.org,2002:yaml", ["!", "&", "*"]));
  250. }
  251.  
  252. ///Returns: Default scalar tag.
  253. @property string defaultScalarTag() const pure @safe nothrow {return defaultScalarTag_;}
  254.  
  255. ///Returns: Default sequence tag.
  256. @property string defaultSequenceTag() const pure @safe nothrow {return defaultSequenceTag_;}
  257.  
  258. ///Returns: Default mapping tag.
  259. @property string defaultMappingTag() const pure @safe nothrow {return defaultMappingTag_;}
  260. }