Keyword.pm 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. # ------------------------------------------------------------------------------
  2. # (C) Crown copyright Met Office. All rights reserved.
  3. # For further details please refer to the file COPYRIGHT.txt
  4. # which you should have received as part of this distribution.
  5. # ------------------------------------------------------------------------------
  6. use strict;
  7. use warnings;
  8. package Fcm::Keyword;
  9. use Carp qw{croak};
  10. use Fcm::Config;
  11. use Fcm::Exception;
  12. use Fcm::Keyword::Config;
  13. use Fcm::Keyword::Exception;
  14. use URI;
  15. my $ENTRIES;
  16. my $PREFIX_OF_LOCATION_KEYWORD = 'fcm';
  17. my $PATTERN_OF_RESERVED_REVISION_KEYWORDS
  18. = qr{\A (?:\d+|HEAD|BASE|COMMITTED|PREV|\{[^\}]+\}) \z}ixms;
  19. ################################################################################
  20. # Returns the Fcm::Keyword::Entries object for storing the location entries
  21. sub get_entries {
  22. my ($reset) = @_;
  23. if ($reset || !$ENTRIES) {
  24. $ENTRIES = Fcm::Keyword::Config::get_entries('LOCATION_ENTRIES');
  25. }
  26. return $ENTRIES;
  27. }
  28. ################################################################################
  29. # Returns a list of Fcm::Keyword::Entry::Location objects matching $in_loc
  30. sub get_location_entries_for {
  31. my ($in_loc) = @_;
  32. my ($loc, $rev, @entry_trail_refs) = _parse_loc($in_loc);
  33. return (map {$_->[0]} @entry_trail_refs);
  34. }
  35. ################################################################################
  36. # Returns the prefix of location keyword (with or without the delimiter).
  37. sub get_prefix_of_location_keyword {
  38. my ($with_delimiter) = @_;
  39. return $PREFIX_OF_LOCATION_KEYWORD . ($with_delimiter ? ':' : '');
  40. }
  41. ################################################################################
  42. # Expands (the keywords in) the specfied location (and REV), and returns them
  43. sub expand {
  44. my ($in_loc, $in_rev) = @_;
  45. my ($loc, $rev) = _expand($in_loc, $in_rev);
  46. return _unparse_loc($loc, $rev, $in_rev);
  47. }
  48. ################################################################################
  49. # Returns the corresponding browser URL for the input VC location
  50. sub get_browser_url {
  51. my ($in_loc, $in_rev) = @_;
  52. my ($loc, $rev, @entry_trail_refs) = _expand($in_loc, $in_rev);
  53. if (!@entry_trail_refs) {
  54. croak(Fcm::Keyword::Exception->new({message => sprintf(
  55. "%s: cannot be mapped to a browser URL", $in_loc,
  56. )}));
  57. }
  58. my @entries = map {$_->[0]} @entry_trail_refs;
  59. my $location_component_pattern
  60. = _get_browser_url_setting(\@entries, 'location_component_pattern');
  61. my $browser_url_template
  62. = _get_browser_url_setting(\@entries, 'browser_url_template');
  63. my $browser_rev_template
  64. = _get_browser_url_setting(\@entries, 'browser_rev_template');
  65. if (
  66. $location_component_pattern
  67. && $browser_url_template
  68. && $browser_rev_template
  69. ) {
  70. my $uri = URI->new($loc);
  71. my $sps = $uri->opaque();
  72. my @matches = $sps =~ $location_component_pattern;
  73. if (@matches) {
  74. my $result = $browser_url_template;
  75. for my $field_number (1 .. @matches) {
  76. my $match = $matches[$field_number - 1];
  77. $result =~ s/\{ $field_number \}/$match/xms;
  78. }
  79. my $rev_field = scalar(@matches) + 1;
  80. if ($rev) {
  81. my $rev_string = $browser_rev_template;
  82. $rev_string =~ s/\{1\}/$rev/xms;
  83. $result =~ s/\{ $rev_field \}/$rev_string/xms;
  84. }
  85. else {
  86. $result =~ s/\{ $rev_field \}//xms;
  87. }
  88. return $result;
  89. }
  90. }
  91. else {
  92. croak(Fcm::Keyword::Exception->new({message => sprintf(
  93. "%s: mapping templates not defined correctly", $in_loc,
  94. )}));
  95. }
  96. }
  97. ################################################################################
  98. # Returns a browser URL setting, helper function for get_browser_url()
  99. sub _get_browser_url_setting {
  100. my ($entries_ref, $setting) = @_;
  101. my $getter = "get_$setting";
  102. for my $entry (@{$entries_ref}) {
  103. my $setting = $entry->$getter();
  104. if ($setting) {
  105. return $setting;
  106. }
  107. }
  108. my $config = Fcm::Config->instance();
  109. return $config->setting('URL_BROWSER_MAPPING_DEFAULT', uc($setting));
  110. }
  111. ################################################################################
  112. # Un-expands the specfied location (and REV) to keywords, and returns them
  113. sub unexpand {
  114. my ($in_loc, $in_rev) = @_;
  115. my ($loc, $rev, @entry_trail_refs) = _parse_loc($in_loc, $in_rev);
  116. if (@entry_trail_refs) {
  117. my ($entry, $trail) = @{$entry_trail_refs[0]};
  118. if ($rev) {
  119. GET_REV_KEY:
  120. for my $entry_trail_ref (@entry_trail_refs) {
  121. my ($e, $t) = @{$entry_trail_ref};
  122. my $rev_key
  123. = $e->get_revision_entries()->get_entry_by_value($rev);
  124. if ($rev_key) {
  125. $rev = $rev_key->get_key();
  126. last GET_REV_KEY;
  127. }
  128. }
  129. }
  130. $loc = get_prefix_of_location_keyword(1) . $entry->get_key() . $trail;
  131. return _unparse_loc($loc, $rev, $in_rev);
  132. }
  133. return _unparse_loc($in_loc, $in_rev, $in_rev);
  134. }
  135. ################################################################################
  136. # Expands (the keywords in) the specfied location (and REV), and returns them
  137. sub _expand {
  138. my ($in_loc, $in_rev) = @_;
  139. my ($loc, $rev, @entry_trail_refs) = _parse_loc($in_loc, $in_rev);
  140. if (@entry_trail_refs) {
  141. my ($entry, $trail) = @{$entry_trail_refs[0]};
  142. $loc = $entry->get_value() . $trail;
  143. if ($rev && $rev !~ $PATTERN_OF_RESERVED_REVISION_KEYWORDS) {
  144. my $r;
  145. GET_REV:
  146. for my $entry_trail_ref (@entry_trail_refs) {
  147. my ($e, $t) = @{$entry_trail_ref};
  148. $r = $e->get_revision_entries()->get_entry_by_key($rev);
  149. if ($r) {
  150. $rev = $r->get_value();
  151. last GET_REV;
  152. }
  153. }
  154. if (!$r) {
  155. croak(Fcm::Keyword::Exception->new({message => sprintf(
  156. "%s: %s: unknown revision keyword",
  157. $loc, $rev,
  158. )}));
  159. }
  160. }
  161. }
  162. return ($loc, $rev, @entry_trail_refs);
  163. }
  164. ################################################################################
  165. # Parses $in_loc (and $in_rev)
  166. sub _parse_loc {
  167. my ($in_loc, $in_rev) = @_;
  168. if (!$in_loc) {
  169. croak(Fcm::Exception->new({
  170. message => 'internal error: $in_loc not defined',
  171. }));
  172. }
  173. if ($in_loc) {
  174. if (!defined($in_rev)) {
  175. my ($loc, $rev) = $in_loc =~ qr{\A (.+) \@ ([^/\@]+) \z}xms;
  176. if ($loc && $rev) {
  177. return ($loc, $rev, _get_loc_entry($loc));
  178. }
  179. else {
  180. return ($in_loc, $in_rev, _get_loc_entry($in_loc));
  181. }
  182. }
  183. return ($in_loc, $in_rev, _get_loc_entry($in_loc));
  184. }
  185. return;
  186. }
  187. ################################################################################
  188. # Returns a list of keyword entries/trailing path pairs for the input location
  189. sub _get_loc_entry {
  190. my ($loc) = @_;
  191. if ($loc) {
  192. my $uri = URI->new($loc);
  193. if (
  194. $uri->scheme()
  195. && $uri->scheme() eq get_prefix_of_location_keyword()
  196. ) {
  197. my ($key, $trail) = $uri->opaque() =~ qr{\A ([^/\@]+) (.*) \z}xms;
  198. my $entry = get_entries()->get_entry_by_key($key);
  199. if (!$entry || !$entry->get_value()) {
  200. die(Fcm::Keyword::Exception->new({message => sprintf(
  201. "%s: unknown FCM location keyword", $loc,
  202. )}));
  203. }
  204. $loc = $entry->get_value() . ($trail ? $trail : q{});
  205. }
  206. my @entry_trail_pairs = ();
  207. my $lead = $loc;
  208. GET_ENTRY:
  209. while ($lead) {
  210. my $entry = get_entries()->get_entry_by_value($lead);
  211. if ($entry) {
  212. my $trail = substr($loc, length($lead));
  213. push @entry_trail_pairs, [$entry, $trail];
  214. }
  215. if (!($lead =~ s{/+ [^/]* \z}{}xms)) {
  216. last GET_ENTRY;
  217. }
  218. }
  219. if (@entry_trail_pairs) {
  220. return @entry_trail_pairs;
  221. }
  222. else {
  223. return;
  224. }
  225. }
  226. return;
  227. }
  228. ################################################################################
  229. # If $in_rev, returns (LOC, REV). Otherwise, returns LOC@REV
  230. sub _unparse_loc {
  231. my ($loc, $rev, $in_rev) = @_;
  232. if (!$loc) {
  233. return;
  234. }
  235. return ($in_rev ? ($loc, $rev) : join(q{@}, $loc, ($rev ? $rev : ())));
  236. }
  237. 1;
  238. __END__
  239. =head1 NAME
  240. Fcm::Keyword
  241. =head1 SYNOPSIS
  242. use Fcm::Keyword;
  243. $loc = Fcm::Keyword::expand('fcm:namespace/path@rev-keyword');
  244. $loc = Fcm::Keyword::unexpand('svn://host/namespace/path@1234');
  245. ($loc, $rev) = Fcm::Keyword::expand('fcm:namespace/path', 'rev-keyword');
  246. ($loc, $rev) = Fcm::Keyword::unexpand('svn://host/namespace/path', 1234);
  247. $loc = Fcm::Keyword::get_browser_url('fcm:namespace/path');
  248. $loc = Fcm::Keyword::get_browser_url('svn://host/namespace/path');
  249. $loc = Fcm::Keyword::get_browser_url('fcm:namespace/path@1234');
  250. $loc = Fcm::Keyword::get_browser_url('svn://host/namespace/path@1234');
  251. $loc = Fcm::Keyword::get_browser_url('fcm:namespace/path', 1234);
  252. $loc = Fcm::Keyword::get_browser_url('svn://host/namespace/path', 1234);
  253. $entries = Fcm::Keyword::get_entries();
  254. =head1 DESCRIPTION
  255. This module contains utilities to expand and unexpand FCM location and revision
  256. keywords.
  257. =head1 FUNCTIONS
  258. =over 4
  259. =item expand($loc)
  260. Expands FCM keywords in $loc and returns the result.
  261. If $loc is a I<fcm> scheme URI, the leading part (before any "/" or "@"
  262. characters) of the URI opaque is the namespace of a FCM location keyword. This
  263. is expanded into the actual value. Optionally, $loc can be suffixed with a peg
  264. revision (an "@" followed by any characters). If a peg revision is a FCM
  265. revision keyword, it is expanded into the actual revision.
  266. =item expand($loc,$rev)
  267. Same as C<expand($loc)>, but $loc should not contain a peg revision. Returns a
  268. list containing the expanded version of $loc and $rev.
  269. =item get_browser_url($loc)
  270. Given a repository $loc in a known keyword namespace, returns the corresponding
  271. URL for the code browser.
  272. Optionally, $loc can be suffixed with a peg revision (an "@" followed by any
  273. characters).
  274. =item get_browser_url($loc,$rev)
  275. Same as get_browser_url($loc), but the revision should be specified using $rev
  276. but not pegged with $loc.
  277. =item get_entries([$reset])
  278. Returns the L<Fcm::Keyword::Entries|Fcm::Keyword::Entries> object for storing
  279. location keyword entries. If $reset if true, reloads the entries.
  280. =item get_location_entries_for($loc)
  281. Returns a list of L<Fcm::Keyword::Entry::Location|Fcm::Keyword::Entry::Location>
  282. objects matching $loc.
  283. =item get_prefix_of_location_keyword($with_delimiter)
  284. Returns the prefix of a FCM location keyword, (currently "fcm"). If
  285. $with_delimiter is specified and is true, returns the prefix with the delimiter,
  286. (currently "fcm:").
  287. =item unexpand($loc)
  288. Does the opposite of expand($loc). Returns the FCM location keyword equivalence
  289. of $loc. If the $loc can be mapped using 2 or more namespaces, the namespace
  290. that results in the longest substitution is used. Optionally, $loc can be
  291. suffixed with a peg revision (an "@" followed by any characters). If a peg
  292. revision is a known revision, it is turned into its corresponding revision
  293. keyword.
  294. =item unexpand($loc,$rev)
  295. Same as unexpand($loc), but $loc should not contain a peg revision. Returns a
  296. list containing the unexpanded version of $loc and $rev
  297. =back
  298. =head1 DIAGNOSTICS
  299. =over 4
  300. =item L<Fcm::Keyword::Exception|Fcm::Keyword::Exception>
  301. Functions in this module may die() with this exception when it fails to expand
  302. a keyword.
  303. =back
  304. =head1 SEE ALSO
  305. L<Fcm::Keyword::Config|Fcm::Keyword::Config>,
  306. L<Fcm::Keyword::Entries|Fcm::Keyword::Entries>,
  307. L<Fcm::Keyword::Entry|Fcm::Keyword::Entry>,
  308. L<Fcm::Keyword::Entry::Location|Fcm::Keyword::Entry::Location>,
  309. L<Fcm::Keyword::Exception|Fcm::Keyword::Exception>
  310. =head1 COPYRIGHT
  311. E<169> Crown copyright Met Office. All rights reserved.
  312. =cut