team_api 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9dff193621885b732e86f4796e1392e51f1d9025
4
- data.tar.gz: b28833e5d8d114604ba3f2b76e0af14d132f842a
3
+ metadata.gz: 09cb397221c7524d2b837eb6d94b5a980b589688
4
+ data.tar.gz: 1310a085598f49a6ef07be1a6d126420745a471e
5
5
  SHA512:
6
- metadata.gz: 694d28f524bb623a665ea18b821d08966b0abcd4dcf8d23949bfc491c319e3ae0df785ba9dcb34e362813e23da0539a000d9a77ad4b44b168867028d430c1488
7
- data.tar.gz: 38e1984b4bc4158481a5be7bd588b6c275b75156eadd32f73b77a392eb4fff2e12790f5448080aaf82a030e63de6b6a193af27d59b464fa5581f9f090594074d
6
+ metadata.gz: 7a74743fc65e50316b66dba13f061d9eef35cae2985be85ce2a87109a0649f9ef0e3898689c7a694e0fbe8b6cedfdc1f22f65d60c65b440aa7a840172d116a39
7
+ data.tar.gz: 2bbe5b4ea8be6e3a7a2de7b89387e776a029d8bc08610dd06b78d2e03e5ff40c9eb955c7ccaec57996c9a29d5133b2be173324c56c4e43753315209c1c3f7c20
@@ -1,69 +1,19 @@
1
1
  # @author Mike Bland (michael.bland@gsa.gov)
2
2
 
3
- require_relative 'config'
3
+ require_relative 'collection_canonicalizer'
4
4
  require_relative 'cross_referencer'
5
+ require_relative 'name_canonicalizer'
6
+ require_relative 'tag_canonicalizer'
7
+
8
+ require 'lambda_map_reduce'
5
9
 
6
10
  module TeamApi
7
11
  # Contains utility functions for canonicalizing names and the order of data.
8
12
  class Canonicalizer
9
13
  # Canonicalizes the order and names of certain fields within site_data.
10
14
  def self.canonicalize_data(site_data)
11
- sort_collections site_data
12
- %w(skills interests).each do |category|
13
- xrefs = site_data[category]
14
- canonicalize_tag_category xrefs
15
- site_data['team'].values.each do |member|
16
- canonicalize_tags_for_item category, xrefs, member
17
- end
18
- end
19
- end
20
-
21
- def self.sort_collections(site_data)
22
- Config.endpoint_config.each do |endpoint_info|
23
- collection = endpoint_info['collection']
24
- next unless site_data.member? collection
25
- sorted = sort_collection_values(endpoint_info,
26
- site_data[collection].values)
27
- sort_item_xrefs endpoint_info, sorted
28
- item_id_field = endpoint_info['item_id']
29
- site_data[collection] = sorted.map { |i| [i[item_id_field], i] }.to_h
30
- end
31
- end
32
-
33
- def self.sort_collection_values(endpoint_info, values)
34
- sort_by_field = endpoint_info['sort_by']
35
- if sort_by_field == 'last_name'
36
- sort_by_last_name values
37
- else
38
- values.sort_by { |i| (i[sort_by_field] || '').downcase }
39
- end
40
- end
41
- private_class_method :sort_collection_values
42
-
43
- def self.sort_item_xrefs(endpoint_info, collection)
44
- collection.each do |item|
45
- sortable_item_fields(item, endpoint_info).each do |field, field_info|
46
- item[field] = sort_collection_values field_info, item[field]
47
- end
48
- end
49
- end
50
- private_class_method :sort_item_xrefs
51
-
52
- def self.sortable_item_fields(item, collection_endpoint_info)
53
- collection_endpoint_info['item_collections'].map do |item_spec|
54
- field, endpoint_info = parse_collection_spec item_spec
55
- [field, endpoint_info] if item[field]
56
- end.compact
57
- end
58
- private_class_method :sortable_item_fields
59
-
60
- def self.parse_collection_spec(collection_spec)
61
- if collection_spec.instance_of? Hash
62
- [collection_spec['field'],
63
- Config.endpoint_info_by_collection[collection_spec['collection']]]
64
- else
65
- [collection_spec, Config.endpoint_info_by_collection[collection_spec]]
66
- end
15
+ CollectionCanonicalizer.sort_collections site_data
16
+ TagCanonicalizer.canonicalize_categories site_data, %w(skills interests)
67
17
  end
68
18
 
69
19
  # Returns a canonicalized, URL-friendly substitute for an arbitrary string.
@@ -72,32 +22,13 @@ module TeamApi
72
22
  s.downcase.gsub(/\s+/, '-')
73
23
  end
74
24
 
75
- def self.comparable_name(person)
76
- if person['last_name']
77
- [person['last_name'].downcase, person['first_name'].downcase]
78
- else
79
- # Trim off title suffix, if any.
80
- full_name = person['full_name'].downcase.split(',')[0]
81
- last_name = full_name.split.last
82
- [last_name, full_name]
83
- end
84
- end
85
- private_class_method :comparable_name
86
-
87
- # Sorts an array of team member data hashes based on the team members'
88
- # last names.
89
- # +team+:: An array of team member data hashes
90
- def self.sort_by_last_name(team)
91
- team.sort_by { |member| comparable_name member }
92
- end
93
-
94
25
  def self.team_xrefs(team, usernames)
95
26
  fields = CrossReferencer::TEAM_FIELDS
96
- usernames
27
+ result = usernames
97
28
  .map { |username| team[username] }
98
29
  .compact
99
30
  .map { |member| member.select { |field, _| fields.include? field } }
100
- .sort_by { |member| comparable_name member }
31
+ NameCanonicalizer.sort_by_last_name result
101
32
  end
102
33
 
103
34
  # Breaks a YYYYMMDD timestamp into a hyphenated version: YYYY-MM-DD
@@ -105,32 +36,5 @@ module TeamApi
105
36
  def self.hyphenate_yyyymmdd(timestamp)
106
37
  "#{timestamp[0..3]}-#{timestamp[4..5]}-#{timestamp[6..7]}"
107
38
  end
108
-
109
- # Consolidate tags entries that are not exactly the same. Selects the
110
- # lexicographically smaller version of the tag as a standard.
111
- #
112
- # In the future, we may just consider raising an error if there are two
113
- # different strings for the same thing.
114
- def self.canonicalize_tag_category(tags_xrefs)
115
- return if tags_xrefs.nil? || tags_xrefs.empty?
116
- tags_xrefs.replace(CrossReferencer.map_reduce(tags_xrefs.values,
117
- ->(xref) { [[xref['slug'], xref]] }, method(:consolidate_xrefs)).to_h)
118
- end
119
-
120
- def self.consolidate_xrefs(slug, xrefs)
121
- xrefs.sort_by! { |xref| xref['name'] }
122
- result = xrefs.each_with_object(xrefs.shift) do |xref, consolidated|
123
- consolidated['members'].concat xref['members']
124
- end
125
- result['members'].sort_by! { |member| comparable_name member }
126
- [slug, result]
127
- end
128
- private_class_method :consolidate_xrefs
129
-
130
- def self.canonicalize_tags_for_item(category, xrefs, item)
131
- return if item[category].nil?
132
- item[category].each { |tag| tag['name'] = xrefs[tag['slug']]['name'] }
133
- .sort_by! { |tag| tag['name'] }
134
- end
135
39
  end
136
40
  end
@@ -0,0 +1,56 @@
1
+ # @author Mike Bland (michael.bland@gsa.gov)
2
+
3
+ require_relative 'config'
4
+ require_relative 'name_canonicalizer'
5
+
6
+ module TeamApi
7
+ class CollectionCanonicalizer
8
+ def self.sort_collections(site_data)
9
+ Config.endpoint_config.each do |endpoint_info|
10
+ collection = endpoint_info['collection']
11
+ next unless site_data.member? collection
12
+ sorted = sort_collection_values(endpoint_info,
13
+ site_data[collection].values)
14
+ sort_item_xrefs endpoint_info, sorted
15
+ item_id_field = endpoint_info['item_id']
16
+ site_data[collection] = sorted.map { |i| [i[item_id_field], i] }.to_h
17
+ end
18
+ end
19
+
20
+ def self.sort_collection_values(endpoint_info, values)
21
+ sort_by_field = endpoint_info['sort_by']
22
+ if sort_by_field == 'last_name'
23
+ NameCanonicalizer.sort_by_last_name values
24
+ else
25
+ values.sort_by { |i| (i[sort_by_field] || '').downcase }
26
+ end
27
+ end
28
+ private_class_method :sort_collection_values
29
+
30
+ def self.sort_item_xrefs(endpoint_info, collection)
31
+ collection.each do |item|
32
+ sortable_item_fields(item, endpoint_info).each do |field, field_info|
33
+ item[field] = sort_collection_values field_info, item[field]
34
+ end
35
+ end
36
+ end
37
+ private_class_method :sort_item_xrefs
38
+
39
+ def self.sortable_item_fields(item, collection_endpoint_info)
40
+ collection_endpoint_info['item_collections'].map do |item_spec|
41
+ field, endpoint_info = parse_collection_spec item_spec
42
+ [field, endpoint_info] if item[field]
43
+ end.compact
44
+ end
45
+ private_class_method :sortable_item_fields
46
+
47
+ def self.parse_collection_spec(collection_spec)
48
+ if collection_spec.instance_of? Hash
49
+ [collection_spec['field'],
50
+ Config.endpoint_info_by_collection[collection_spec['collection']]]
51
+ else
52
+ [collection_spec, Config.endpoint_info_by_collection[collection_spec]]
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,106 @@
1
+ # @author Mike Bland (michael.bland@gsa.gov)
2
+
3
+ module TeamApi
4
+ # Signals that a cross-reference ID value in one object is not present in
5
+ # the target collection. Only raised in "private" mode, since "public" mode
6
+ # may legitimately filter out data.
7
+ class UnknownCrossReferenceTargetId < StandardError
8
+ end
9
+
10
+ # Provides a collection with the ability to replace identifiers with more
11
+ # detailed cross-reference values from another collection, and with the
12
+ # ability to construct its own cross-reference values to assign to values
13
+ # from other collections.
14
+ #
15
+ # The intent is to provide enough cross-reference information to surface in
16
+ # an API without requiring the client to join the data necessary to produce
17
+ # cross-links. For example, instead of surfacing `['mbland']` in a list of
18
+ # team members, this class will produce `[{'name' => 'mbland', 'full_name'
19
+ # => 'Mike Bland', 'first_name' => 'Mike', 'last_name' => 'Bland'}]`, which
20
+ # the client can use to more easily sort multiple values and transform into:
21
+ # `<a href="https://hub.18f.gov/team/mbland/">Mike Bland</a>`.
22
+ class CrossReferenceData
23
+ attr_accessor :collection_name, :data, :item_xref_fields, :public_mode
24
+
25
+ # @param site [Jekyll::Site] site object
26
+ # @param collection_name [String] name of collection within site.data
27
+ # @param field_to_xref [String] name of the field to cross-reference
28
+ # @param item_xref_fields [Array<String>] list of fields from which to
29
+ # produce cross-references for this collection
30
+ def initialize(site, collection_name, item_xref_fields)
31
+ @collection_name = collection_name
32
+ @data = site.data[collection_name] || {}
33
+ @item_xref_fields = item_xref_fields
34
+ @public_mode = site.config['public']
35
+ end
36
+
37
+ # Selects fields from `item` to produce a smaller hash as a
38
+ # cross-reference.
39
+ def item_to_xref(item)
40
+ item.select { |field, _| item_xref_fields.include? field }
41
+ end
42
+
43
+ # Translates identifiers into cross-reference values in both this object's
44
+ # collection and the `target` collection.
45
+ #
46
+ # This object's collection is considered the "source", and references to
47
+ # its values will be injected into "target". For each "source" object,
48
+ # `source[target.collection_name]` should be an existing field containing
49
+ # identifiers that are keys into `target.data`. The `target` collection
50
+ # values should not contain a `target[source.collection_name]` field; that
51
+ # field will be created by this method.
52
+ #
53
+ # @param target [CrossReferenceData] contains data to cross-reference with
54
+ # items from this object's collection
55
+ # @param source_to_target_field [String] if specified, the field from this
56
+ # collection's objects that contain identifiers of objects stored within
57
+ # target; if not specified, target.collection_name will be used instead
58
+ def create_xrefs(target, source_to_target_field: nil)
59
+ target_collection_field = source_to_target_field || target.collection_name
60
+ data.values.each do |source|
61
+ create_xrefs_for_source source, target_collection_field, target
62
+ end
63
+ target.data.values.each { |item| (item[collection_name] || []).uniq! }
64
+ end
65
+
66
+ private
67
+
68
+ def create_xrefs_for_source(source, target_collection_field, target)
69
+ source_xref = item_to_xref source
70
+ target_ids = filter_target_ids target, source, target_collection_field
71
+ link_source_to_targets source_xref, target_ids, target
72
+ source[target_collection_field] = target_xrefs target, target_ids
73
+ end
74
+
75
+ def filter_target_ids(target_xref, source_item, target_collection_field)
76
+ (source_item[target_collection_field] || []).map do |target_id|
77
+ if target_xref.data.member? target_id
78
+ target_id
79
+ elsif !public_mode
80
+ fail UnknownCrossReferenceTargetId, unknown_cross_reference_msg(
81
+ collection_name, source_item, target_collection_field,
82
+ target_xref, target_id)
83
+ end
84
+ end.compact
85
+ end
86
+
87
+ def unknown_cross_reference_msg(collection_name,
88
+ source_item, target_collection_field, target_xref, target_id)
89
+ "source collection: \"#{collection_name}\" " \
90
+ "source xref: #{item_to_xref source_item} " \
91
+ "target collection field: \"#{target_collection_field}\" " \
92
+ "target collection: \"#{target_xref.collection_name}\" " \
93
+ "target ID: \"#{target_id}\""
94
+ end
95
+
96
+ def link_source_to_targets(source_xref, target_ids, target_xref)
97
+ target_ids.each do |target_id|
98
+ (target_xref.data[target_id][collection_name] ||= []) << source_xref
99
+ end
100
+ end
101
+
102
+ def target_xrefs(target_xref, target_ids)
103
+ target_ids.map { |id| target_xref.item_to_xref target_xref.data[id] }
104
+ end
105
+ end
106
+ end
@@ -2,111 +2,12 @@
2
2
 
3
3
  require_relative 'api'
4
4
  require_relative 'canonicalizer'
5
+ require_relative 'cross_reference_data'
6
+ require_relative 'name_canonicalizer'
5
7
 
6
- module TeamApi
7
- # Signals that a cross-reference ID value in one object is not present in
8
- # the target collection. Only raised in "private" mode, since "public" mode
9
- # may legitimately filter out data.
10
- class UnknownCrossReferenceTargetId < StandardError
11
- end
12
-
13
- # Provides a collection with the ability to replace identifiers with more
14
- # detailed cross-reference values from another collection, and with the
15
- # ability to construct its own cross-reference values to assign to values
16
- # from other collections.
17
- #
18
- # The intent is to provide enough cross-reference information to surface in
19
- # an API without requiring the client to join the data necessary to produce
20
- # cross-links. For example, instead of surfacing `['mbland']` in a list of
21
- # team members, this class will produce `[{'name' => 'mbland', 'full_name'
22
- # => 'Mike Bland', 'first_name' => 'Mike', 'last_name' => 'Bland'}]`, which
23
- # the client can use to more easily sort multiple values and transform into:
24
- # `<a href="https://hub.18f.gov/team/mbland/">Mike Bland</a>`.
25
- class CrossReferenceData
26
- attr_accessor :collection_name, :data, :item_xref_fields, :public_mode
27
-
28
- # @param site [Jekyll::Site] site object
29
- # @param collection_name [String] name of collection within site.data
30
- # @param field_to_xref [String] name of the field to cross-reference
31
- # @param item_xref_fields [Array<String>] list of fields from which to
32
- # produce cross-references for this collection
33
- def initialize(site, collection_name, item_xref_fields)
34
- @collection_name = collection_name
35
- @data = site.data[collection_name] || {}
36
- @item_xref_fields = item_xref_fields
37
- @public_mode = site.config['public']
38
- end
39
-
40
- # Selects fields from `item` to produce a smaller hash as a
41
- # cross-reference.
42
- def item_to_xref(item)
43
- item.select { |field, _| item_xref_fields.include? field }
44
- end
45
-
46
- # Translates identifiers into cross-reference values in both this object's
47
- # collection and the `target` collection.
48
- #
49
- # This object's collection is considered the "source", and references to
50
- # its values will be injected into "target". For each "source" object,
51
- # `source[target.collection_name]` should be an existing field containing
52
- # identifiers that are keys into `target.data`. The `target` collection
53
- # values should not contain a `target[source.collection_name]` field; that
54
- # field will be created by this method.
55
- #
56
- # @param target [CrossReferenceData] contains data to cross-reference with
57
- # items from this object's collection
58
- # @param source_to_target_field [String] if specified, the field from this
59
- # collection's objects that contain identifiers of objects stored within
60
- # target; if not specified, target.collection_name will be used instead
61
- def create_xrefs(target, source_to_target_field: nil)
62
- target_collection_field = source_to_target_field || target.collection_name
63
- data.values.each do |source|
64
- create_xrefs_for_source source, target_collection_field, target
65
- end
66
- target.data.values.each { |item| (item[collection_name] || []).uniq! }
67
- end
68
-
69
- private
70
-
71
- def create_xrefs_for_source(source, target_collection_field, target)
72
- source_xref = item_to_xref source
73
- target_ids = filter_target_ids target, source, target_collection_field
74
- link_source_to_targets source_xref, target_ids, target
75
- source[target_collection_field] = target_xrefs target, target_ids
76
- end
77
-
78
- def filter_target_ids(target_xref, source_item, target_collection_field)
79
- (source_item[target_collection_field] || []).map do |target_id|
80
- if target_xref.data.member? target_id
81
- target_id
82
- elsif !public_mode
83
- fail UnknownCrossReferenceTargetId, unknown_cross_reference_msg(
84
- collection_name, source_item, target_collection_field,
85
- target_xref, target_id)
86
- end
87
- end.compact
88
- end
89
-
90
- def unknown_cross_reference_msg(collection_name,
91
- source_item, target_collection_field, target_xref, target_id)
92
- "source collection: \"#{collection_name}\" " \
93
- "source xref: #{item_to_xref source_item} " \
94
- "target collection field: \"#{target_collection_field}\" " \
95
- "target collection: \"#{target_xref.collection_name}\" " \
96
- "target ID: \"#{target_id}\""
97
- end
98
-
99
- def link_source_to_targets(source_xref, target_ids, target_xref)
100
- target_ids.each do |target_id|
101
- (target_xref.data[target_id][collection_name] ||= []) << source_xref
102
- end
103
- end
104
-
105
- def target_xrefs(target_xref, target_ids)
106
- target_ids.map { |id| target_xref.item_to_xref target_xref.data[id] }
107
- end
108
- end
8
+ require 'lambda_map_reduce'
109
9
 
10
+ module TeamApi
110
11
  # Builds cross-references between data sets.
111
12
  class CrossReferencer
112
13
  TEAM_FIELDS = %w(name last_name first_name full_name self)
@@ -158,24 +59,48 @@ module TeamApi
158
59
  end
159
60
  end
160
61
 
62
+ # Generates a Hash of { tag => cross-reference } generated from the tag
63
+ # `category` Arrays from each element of `items`.
64
+ #
65
+ # For example:
66
+ # TEAM = {
67
+ # 'mbland' => {
68
+ # 'name' => 'mbland', 'full_name' => 'Mike Bland',
69
+ # 'skills' => ['C++', 'Python'] },
70
+ # 'arowla' => {
71
+ # 'name' => 'arowla', 'full_name' => 'Alison Rowland',
72
+ # 'skills' => ['Python'] },
73
+ # }
74
+ # TEAM_XREF = CrossReferenceData.new site, 'team', ['name', 'full_name']
75
+ # create_tag_xrefs site, TEAM, 'skills', TEAM_XREF
76
+ #
77
+ # will produce:
78
+ # {'C++' => {
79
+ # 'name' => 'C++',
80
+ # 'slug' => 'c++',
81
+ # 'self' => 'https://.../skills/c++',
82
+ # 'members' => [{ 'name' => 'mbland', 'full_name' => 'Mike Bland' }],
83
+ # },
84
+ #
85
+ # 'Python' => {
86
+ # 'name' => 'Python',
87
+ # 'slug' => 'python',
88
+ # 'self' => 'https://.../skills/python',
89
+ # 'members' => [
90
+ # { 'name' => 'mbland', 'full_name' => 'Mike Bland' },
91
+ # { 'name' => 'arowla', 'full_name' => 'Alison Rowland' },
92
+ # ],
93
+ # },
94
+ # }
161
95
  def self.create_tag_xrefs(site, items, category, xref_data)
162
- map_items_to_tags = lambda do |item|
96
+ items_to_tags = lambda do |item|
163
97
  item_xref = xref_data.item_to_xref item
164
98
  item[category].map { |tag| [tag, item_xref] } unless item[category].nil?
165
99
  end
166
100
  create_tag_xrefs = lambda do |tag, item_xrefs|
167
101
  [tag, tag_xref(site, category, tag, item_xrefs)]
168
102
  end
169
- map_reduce(items, map_items_to_tags, create_tag_xrefs).to_h
170
- end
171
-
172
- # Returns an Array of objects after mapping and reducing items.
173
- # mapper takes a single item and returns an Array of [key, value] pairs.
174
- # reducer takes a [key, Array of values] pair and returns a single item.
175
- def self.map_reduce(items, mapper, reducer)
176
- items.flat_map { |item| mapper.call(item) }.compact
177
- .each_with_object({}) { |kv, shuffle| (shuffle[kv[0]] ||= []) << kv[1] }
178
- .map { |key, values| reducer.call(key, values) }.compact
103
+ LambdaMapReduce.map_reduce(items, items_to_tags, create_tag_xrefs).to_h
179
104
  end
180
105
 
181
106
  def self.tag_xref(site, category, tag, members)
@@ -184,7 +109,7 @@ module TeamApi
184
109
  { 'name' => tag,
185
110
  'slug' => tag_slug,
186
111
  'self' => File.join(Api.baseurl(site), category_slug, tag_slug),
187
- 'members' => Canonicalizer.sort_by_last_name(members || []),
112
+ 'members' => NameCanonicalizer.sort_by_last_name(members || []),
188
113
  }
189
114
  end
190
115
 
@@ -119,7 +119,7 @@ module TeamApi
119
119
 
120
120
  def team_member_from_reference(reference)
121
121
  key = (reference.instance_of? String) ? reference : (
122
- reference['email'] || reference['github'])
122
+ reference['id'] || reference['email'] || reference['github'])
123
123
  team[key] || team[team_by_email[key] || team_by_github[key]]
124
124
  end
125
125
 
@@ -0,0 +1,28 @@
1
+ # @author Mike Bland (michael.bland@gsa.gov)
2
+
3
+ module TeamApi
4
+ class NameCanonicalizer
5
+ # Sorts an array of team member data hashes based on the team members'
6
+ # last names.
7
+ # +team+:: An array of team member data hashes
8
+ def self.sort_by_last_name(team)
9
+ team.sort_by { |member| comparable_name member }
10
+ end
11
+
12
+ def self.sort_by_last_name!(team)
13
+ team.sort_by! { |member| comparable_name member }
14
+ end
15
+
16
+ def self.comparable_name(person)
17
+ if person['last_name']
18
+ [person['last_name'].downcase, person['first_name'].downcase]
19
+ else
20
+ # Trim off title suffix, if any.
21
+ full_name = person['full_name'].downcase.split(',')[0]
22
+ last_name = full_name.split.last
23
+ [last_name, full_name]
24
+ end
25
+ end
26
+ private_class_method :comparable_name
27
+ end
28
+ end
@@ -0,0 +1,47 @@
1
+ # @author Mike Bland (michael.bland@gsa.gov)
2
+
3
+ require_relative 'name_canonicalizer'
4
+
5
+ module TeamApi
6
+ # Contains utility functions for canonicalizing names and the order of data.
7
+ class TagCanonicalizer
8
+ def self.canonicalize_categories(site_data, tag_categories)
9
+ tag_categories.each do |category|
10
+ xrefs = site_data[category]
11
+ canonicalize_tag_category xrefs
12
+ site_data['team'].values.each do |member|
13
+ canonicalize_tags_for_item category, xrefs, member
14
+ end
15
+ end
16
+ end
17
+
18
+ # Consolidate tags entries that are not exactly the same. Selects the
19
+ # lexicographically smaller version of the tag as a standard.
20
+ #
21
+ # In the future, we may just consider raising an error if there are two
22
+ # different strings for the same thing.
23
+ def self.canonicalize_tag_category(tags_xrefs)
24
+ return if tags_xrefs.nil? || tags_xrefs.empty?
25
+ tags_xrefs.replace(LambdaMapReduce.map_reduce(
26
+ tags_xrefs.values,
27
+ ->(xref) { [[xref['slug'], xref]] },
28
+ method(:consolidate_xrefs)).to_h)
29
+ end
30
+
31
+ def self.consolidate_xrefs(slug, xrefs)
32
+ xrefs.sort_by! { |xref| xref['name'] }
33
+ result = xrefs.each_with_object(xrefs.shift) do |xref, consolidated|
34
+ consolidated['members'].concat xref['members']
35
+ end
36
+ NameCanonicalizer.sort_by_last_name! result['members']
37
+ [slug, result]
38
+ end
39
+ private_class_method :consolidate_xrefs
40
+
41
+ def self.canonicalize_tags_for_item(category, xrefs, item)
42
+ return if item[category].nil?
43
+ item[category].each { |tag| tag['name'] = xrefs[tag['slug']]['name'] }
44
+ .sort_by! { |tag| tag['name'] }
45
+ end
46
+ end
47
+ end
@@ -1,5 +1,5 @@
1
1
  # @author Mike Bland (michael.bland@gsa.gov)
2
2
 
3
3
  module TeamApi
4
- VERSION = '0.0.2'
4
+ VERSION = '0.0.3'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: team_api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Bland
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-09-15 00:00:00.000000000 Z
11
+ date: 2015-09-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: lambda_map_reduce
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: go_script
85
99
  requirement: !ruby/object:Gem::Requirement
@@ -193,13 +207,17 @@ files:
193
207
  - lib/team_api/README.md
194
208
  - lib/team_api/api.rb
195
209
  - lib/team_api/canonicalizer.rb
210
+ - lib/team_api/collection_canonicalizer.rb
196
211
  - lib/team_api/config.rb
212
+ - lib/team_api/cross_reference_data.rb
197
213
  - lib/team_api/cross_referencer.rb
198
214
  - lib/team_api/endpoints.yml
199
215
  - lib/team_api/front_matter.rb
200
216
  - lib/team_api/generator.rb
201
217
  - lib/team_api/joiner.rb
218
+ - lib/team_api/name_canonicalizer.rb
202
219
  - lib/team_api/snippets.rb
220
+ - lib/team_api/tag_canonicalizer.rb
203
221
  - lib/team_api/tag_categories.yml
204
222
  - lib/team_api/version.rb
205
223
  homepage: https://github.com/18F/team_api