team_api 0.0.0
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 +7 -0
 - data/CONTRIBUTING.md +15 -0
 - data/LICENSE.md +31 -0
 - data/README.md +53 -0
 - data/lib/team_api/README.md +33 -0
 - data/lib/team_api/api.rb +217 -0
 - data/lib/team_api/canonicalizer.rb +125 -0
 - data/lib/team_api/config.rb +18 -0
 - data/lib/team_api/cross_referencer.rb +202 -0
 - data/lib/team_api/endpoints.yml +58 -0
 - data/lib/team_api/front_matter.rb +33 -0
 - data/lib/team_api/generator.rb +28 -0
 - data/lib/team_api/joiner.rb +151 -0
 - data/lib/team_api/snippets.rb +24 -0
 - data/lib/team_api/tag_categories.yml +9 -0
 - data/lib/team_api/version.rb +5 -0
 - data/lib/team_api.rb +11 -0
 - metadata +229 -0
 
    
        checksums.yaml
    ADDED
    
    | 
         @@ -0,0 +1,7 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            ---
         
     | 
| 
      
 2 
     | 
    
         
            +
            SHA1:
         
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 6740624d81a90a97d8d0f86e4495d74fd26d1b54
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 3fc55eee6ee7d09912249b15962525fe9add3da7
         
     | 
| 
      
 5 
     | 
    
         
            +
            SHA512:
         
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 474780225962c7350a00ea29b8e1fa80dc6b062918b4746a8e6984f0bcd7577319febf787cbf29dd70c8c3aabf34e9d993c3dd6ec3c7851c3c6880c4fe97e905
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 7c53f0fcb51b54a3e30d62f93d48068e5c3e5b497c715d8c5f9d66f4b9eed4b02608dce56463eb3949973c23b7fbadf6e6aa6475f2b435d71f161e722b5748af
         
     | 
    
        data/CONTRIBUTING.md
    ADDED
    
    | 
         @@ -0,0 +1,15 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            ## Welcome!
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            We're so glad you're thinking about contributing to an 18F open source project! If you're unsure or afraid of anything, just ask or submit the issue or pull request anyways. The worst that can happen is that you'll be politely asked to change something. We appreciate any sort of contribution, and don't want a wall of rules to get in the way of that.
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            Before contributing, we encourage you to read our CONTRIBUTING policy (you are here), our LICENSE, and our README, all of which should be in this repository. If you have any questions, or want to read more about our underlying policies, you can consult the 18F Open Source Policy GitHub repository at https://github.com/18f/open-source-policy, or just shoot us an email/official government letterhead note to [18f@gsa.gov](mailto:18f@gsa.gov).
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            ## Public domain
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            This project is in the public domain within the United States, and
         
     | 
| 
      
 10 
     | 
    
         
            +
            copyright and related rights in the work worldwide are waived through
         
     | 
| 
      
 11 
     | 
    
         
            +
            the [CC0 1.0 Universal public domain dedication](https://creativecommons.org/publicdomain/zero/1.0/).
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            All contributions to this project will be released under the CC0
         
     | 
| 
      
 14 
     | 
    
         
            +
            dedication. By submitting a pull request, you are agreeing to comply
         
     | 
| 
      
 15 
     | 
    
         
            +
            with this waiver of copyright interest.
         
     | 
    
        data/LICENSE.md
    ADDED
    
    | 
         @@ -0,0 +1,31 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            As a work of the United States Government, this project is in the
         
     | 
| 
      
 2 
     | 
    
         
            +
            public domain within the United States.
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            Additionally, we waive copyright and related rights in the work
         
     | 
| 
      
 5 
     | 
    
         
            +
            worldwide through the CC0 1.0 Universal public domain dedication.
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            ## CC0 1.0 Universal Summary
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            This is a human-readable summary of the [Legal Code (read the full text)](https://creativecommons.org/publicdomain/zero/1.0/legalcode).
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            ### No Copyright
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            The person who associated a work with this deed has dedicated the work to
         
     | 
| 
      
 14 
     | 
    
         
            +
            the public domain by waiving all of his or her rights to the work worldwide
         
     | 
| 
      
 15 
     | 
    
         
            +
            under copyright law, including all related and neighboring rights, to the
         
     | 
| 
      
 16 
     | 
    
         
            +
            extent allowed by law.
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
            You can copy, modify, distribute and perform the work, even for commercial
         
     | 
| 
      
 19 
     | 
    
         
            +
            purposes, all without asking permission.
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
            ### Other Information
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
            In no way are the patent or trademark rights of any person affected by CC0,
         
     | 
| 
      
 24 
     | 
    
         
            +
            nor are the rights that other persons may have in the work or in how the
         
     | 
| 
      
 25 
     | 
    
         
            +
            work is used, such as publicity or privacy rights.
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
            Unless expressly stated otherwise, the person who associated a work with
         
     | 
| 
      
 28 
     | 
    
         
            +
            this deed makes no warranties about the work, and disclaims liability for
         
     | 
| 
      
 29 
     | 
    
         
            +
            all uses of the work, to the fullest extent permitted by applicable law.
         
     | 
| 
      
 30 
     | 
    
         
            +
            When using or citing the work, you should not imply endorsement by the
         
     | 
| 
      
 31 
     | 
    
         
            +
            author or the affirmer.
         
     | 
    
        data/README.md
    ADDED
    
    | 
         @@ -0,0 +1,53 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # 18F Team API
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            Compiles information about team members, projects, etc. and exposes it via a
         
     | 
| 
      
 4 
     | 
    
         
            +
            JSON API.
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            Targeted consumers of this API include:
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            - [18F Hub](https://github.com/18F/hub)
         
     | 
| 
      
 9 
     | 
    
         
            +
            - [18F Dashboard](https://github.com/18F/dashboard)
         
     | 
| 
      
 10 
     | 
    
         
            +
            - [18F.gsa.gov](https://github.com/18F/18f.gsa.gov)
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            ## Installation
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            This gem currently serves as a [Jekyll](https://jekyllrb.com/) plugin, though
         
     | 
| 
      
 15 
     | 
    
         
            +
            it may become decoupled in the future. Presuming you're using
         
     | 
| 
      
 16 
     | 
    
         
            +
            [Bundler](http://bundler.io) in your Jekyll project, add the following to your
         
     | 
| 
      
 17 
     | 
    
         
            +
            `Gemfile`:
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 20 
     | 
    
         
            +
            group :jekyll_plugins do
         
     | 
| 
      
 21 
     | 
    
         
            +
              gem 'team_api'
         
     | 
| 
      
 22 
     | 
    
         
            +
            end
         
     | 
| 
      
 23 
     | 
    
         
            +
            ```
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
            Then, make sure to add an entry for `api_index_layout:` to your `_config.yml`
         
     | 
| 
      
 26 
     | 
    
         
            +
            file. The index page will have an `endpoints` collection with one entry per
         
     | 
| 
      
 27 
     | 
    
         
            +
            data collection, where each element has:
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
            * `endpoint`: the URL of the collection's JSON endpoint
         
     | 
| 
      
 30 
     | 
    
         
            +
            * `title`: title of the collection
         
     | 
| 
      
 31 
     | 
    
         
            +
            * `description`: description of the collection
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
            Here's a sample bare-bones template you can drop into your prefered layout:
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
            ```html
         
     | 
| 
      
 36 
     | 
    
         
            +
            <h1>API Endpoint Index</h1>
         
     | 
| 
      
 37 
     | 
    
         
            +
            <br/>{% for i in page.endpoints %}
         
     | 
| 
      
 38 
     | 
    
         
            +
            <div class="api_endpoint_desc">
         
     | 
| 
      
 39 
     | 
    
         
            +
            <h2><a href="{{ i.endpoint }}"<code>{{ i.endpoint }}</code></a> - {{ i.title }}</h2>
         
     | 
| 
      
 40 
     | 
    
         
            +
            <p>{{ i.description }}</p>
         
     | 
| 
      
 41 
     | 
    
         
            +
            </div>
         
     | 
| 
      
 42 
     | 
    
         
            +
            {% endfor %}
         
     | 
| 
      
 43 
     | 
    
         
            +
            ```
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
            ## Public domain
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
            This project is in the worldwide [public domain](LICENSE.md). As stated in [CONTRIBUTING](CONTRIBUTING.md):
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
            > This project is in the public domain within the United States, and copyright and related rights in the work worldwide are waived through the [CC0 1.0 Universal public domain dedication](https://creativecommons.org/publicdomain/zero/1.0/).
         
     | 
| 
      
 50 
     | 
    
         
            +
            >
         
     | 
| 
      
 51 
     | 
    
         
            +
            > All contributions to this project will be released under the CC0
         
     | 
| 
      
 52 
     | 
    
         
            +
            >dedication. By submitting a pull request, you are agreeing to comply
         
     | 
| 
      
 53 
     | 
    
         
            +
            >with this waiver of copyright interest.
         
     | 
| 
         @@ -0,0 +1,33 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            ## 18F Team API Plugins
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            Plugins are used to create data joins and cross-references needed to produce
         
     | 
| 
      
 4 
     | 
    
         
            +
            the API. The basic flow is:
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            * Join private data with public data
         
     | 
| 
      
 7 
     | 
    
         
            +
            * Process snippet data
         
     | 
| 
      
 8 
     | 
    
         
            +
            * Build cross-references between data elements
         
     | 
| 
      
 9 
     | 
    
         
            +
            * Perform canonicalization of names and their ordering
         
     | 
| 
      
 10 
     | 
    
         
            +
            * Generate API endpoints based on the joined, cross-referenced data
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            [generator.rb](generator.rb) is the entry point for this entire process. It
         
     | 
| 
      
 13 
     | 
    
         
            +
            contains `TeamApi::Generator`, which performs all of the above steps in order.
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
            ### Data Joining
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
            [joiner.rb](joiner.rb) contains the plugins that join public, private, and
         
     | 
| 
      
 18 
     | 
    
         
            +
            local data into the `site.data` object.
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
            ### Cross-Referencing
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
            [cross_referencer.rb](cross_referencer.rb) builds links between `site.data`
         
     | 
| 
      
 23 
     | 
    
         
            +
            data collections which are used to generate cross-referenced pages.
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
            ### Canonicalization
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
            [canonicalizer.rb](canonicalizer.rb) contains functions used to canonicalize
         
     | 
| 
      
 28 
     | 
    
         
            +
            names and the sort order of collections in `site.data`.
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
            ### API Endpoint Generation
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
            [api.rb](api.rb) generates all API endpoints and provides an index under
         
     | 
| 
      
 33 
     | 
    
         
            +
            `/api`.
         
     | 
    
        data/lib/team_api/api.rb
    ADDED
    
    | 
         @@ -0,0 +1,217 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # @author Mike Bland (michael.bland@gsa.gov)
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require_relative 'canonicalizer'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require_relative 'config'
         
     | 
| 
      
 5 
     | 
    
         
            +
            require 'jekyll'
         
     | 
| 
      
 6 
     | 
    
         
            +
            require 'json'
         
     | 
| 
      
 7 
     | 
    
         
            +
            require 'safe_yaml'
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            module TeamApi
         
     | 
| 
      
 10 
     | 
    
         
            +
              class IndexPage < ::Jekyll::Page
         
     | 
| 
      
 11 
     | 
    
         
            +
                private_class_method :new
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                def initialize(site)
         
     | 
| 
      
 14 
     | 
    
         
            +
                  @site = site
         
     | 
| 
      
 15 
     | 
    
         
            +
                  @base = site.source
         
     | 
| 
      
 16 
     | 
    
         
            +
                  @dir = File.join site.config['baseurl'], Api::BASEURL
         
     | 
| 
      
 17 
     | 
    
         
            +
                  @name = 'index.html'
         
     | 
| 
      
 18 
     | 
    
         
            +
                  @data = {}
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                def self.create(site, index_endpoints)
         
     | 
| 
      
 22 
     | 
    
         
            +
                  index_page = new site
         
     | 
| 
      
 23 
     | 
    
         
            +
                  index_page.process index_page.name
         
     | 
| 
      
 24 
     | 
    
         
            +
                  layout = site.config['api_index_layout']
         
     | 
| 
      
 25 
     | 
    
         
            +
                  fail '`api_index_layout:` not defined in _config.yml' unless layout
         
     | 
| 
      
 26 
     | 
    
         
            +
                  index_page.read_yaml File.join(site.source, '_layouts'), layout
         
     | 
| 
      
 27 
     | 
    
         
            +
                  index_page.data['endpoints'] = index_endpoints
         
     | 
| 
      
 28 
     | 
    
         
            +
                  site.pages << index_page
         
     | 
| 
      
 29 
     | 
    
         
            +
                end
         
     | 
| 
      
 30 
     | 
    
         
            +
              end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
              class Endpoint < ::Jekyll::Page
         
     | 
| 
      
 33 
     | 
    
         
            +
                private_class_method :new
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                def initialize(site, endpoint_path)
         
     | 
| 
      
 36 
     | 
    
         
            +
                  @site = site
         
     | 
| 
      
 37 
     | 
    
         
            +
                  @base = site.source
         
     | 
| 
      
 38 
     | 
    
         
            +
                  @dir = endpoint_path
         
     | 
| 
      
 39 
     | 
    
         
            +
                  @name = 'api.json'
         
     | 
| 
      
 40 
     | 
    
         
            +
                  @data = {}
         
     | 
| 
      
 41 
     | 
    
         
            +
                end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                def self.create(site, endpoint_path, data)
         
     | 
| 
      
 44 
     | 
    
         
            +
                  endpoint = new site, endpoint_path
         
     | 
| 
      
 45 
     | 
    
         
            +
                  endpoint.process endpoint.name
         
     | 
| 
      
 46 
     | 
    
         
            +
                  endpoint.content = data.to_json
         
     | 
| 
      
 47 
     | 
    
         
            +
                  site.pages << endpoint
         
     | 
| 
      
 48 
     | 
    
         
            +
                end
         
     | 
| 
      
 49 
     | 
    
         
            +
              end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
              # Functions for generating JSON objects as part of an API
         
     | 
| 
      
 52 
     | 
    
         
            +
              class Api
         
     | 
| 
      
 53 
     | 
    
         
            +
                BASEURL = 'api'
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                # Generates all of the API endpoints.
         
     | 
| 
      
 56 
     | 
    
         
            +
                # +site+:: Jekyll site object
         
     | 
| 
      
 57 
     | 
    
         
            +
                def self.generate_api(site)
         
     | 
| 
      
 58 
     | 
    
         
            +
                  impl = ApiImpl.new site, BASEURL
         
     | 
| 
      
 59 
     | 
    
         
            +
                  generate_collection_endpoints impl
         
     | 
| 
      
 60 
     | 
    
         
            +
                  generate_tag_category_endpoints impl
         
     | 
| 
      
 61 
     | 
    
         
            +
                  impl.generate_snippets_endpoints
         
     | 
| 
      
 62 
     | 
    
         
            +
                  IndexPage.create site, impl.index_endpoints
         
     | 
| 
      
 63 
     | 
    
         
            +
                end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                # Calculates the full URL prefix for every API endpoint, used to generate
         
     | 
| 
      
 66 
     | 
    
         
            +
                # `self:` links. It is generated by concatenating the `url:` and
         
     | 
| 
      
 67 
     | 
    
         
            +
                # `baseurl:` values from _config.yml, and the Api::BASEURL value, e.g.
         
     | 
| 
      
 68 
     | 
    
         
            +
                # localhost:4001/api, https://team-api.18f.gov/public/api.
         
     | 
| 
      
 69 
     | 
    
         
            +
                def self.baseurl(site)
         
     | 
| 
      
 70 
     | 
    
         
            +
                  File.join site.config['url'], site.config['baseurl'], BASEURL
         
     | 
| 
      
 71 
     | 
    
         
            +
                end
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                def self.add_self_links(site)
         
     | 
| 
      
 74 
     | 
    
         
            +
                  baseurl = self.baseurl site
         
     | 
| 
      
 75 
     | 
    
         
            +
                  Config.endpoint_config.each do |endpoint_info|
         
     | 
| 
      
 76 
     | 
    
         
            +
                    collection_name = endpoint_info['collection']
         
     | 
| 
      
 77 
     | 
    
         
            +
                    (site.data[collection_name] || {}).values.each do |item|
         
     | 
| 
      
 78 
     | 
    
         
            +
                      slug = Canonicalizer.canonicalize item[endpoint_info['item_id']]
         
     | 
| 
      
 79 
     | 
    
         
            +
                      item['self'] = File.join baseurl, collection_name, slug
         
     | 
| 
      
 80 
     | 
    
         
            +
                    end
         
     | 
| 
      
 81 
     | 
    
         
            +
                  end
         
     | 
| 
      
 82 
     | 
    
         
            +
                end
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
                def self.generate_collection_endpoints(impl)
         
     | 
| 
      
 85 
     | 
    
         
            +
                  Config.endpoint_config.each do |endpoint_info|
         
     | 
| 
      
 86 
     | 
    
         
            +
                    impl.generate_index_endpoint_for_collection endpoint_info
         
     | 
| 
      
 87 
     | 
    
         
            +
                    impl.generate_item_endpoints endpoint_info['collection']
         
     | 
| 
      
 88 
     | 
    
         
            +
                  end
         
     | 
| 
      
 89 
     | 
    
         
            +
                end
         
     | 
| 
      
 90 
     | 
    
         
            +
                private_class_method :generate_collection_endpoints
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
                def self.generate_tag_category_endpoints(impl)
         
     | 
| 
      
 93 
     | 
    
         
            +
                  %w(Skills Interests).each do |tag_category|
         
     | 
| 
      
 94 
     | 
    
         
            +
                    impl.generate_tag_category_endpoint tag_category
         
     | 
| 
      
 95 
     | 
    
         
            +
                    impl.generate_item_endpoints Canonicalizer.canonicalize(tag_category)
         
     | 
| 
      
 96 
     | 
    
         
            +
                  end
         
     | 
| 
      
 97 
     | 
    
         
            +
                end
         
     | 
| 
      
 98 
     | 
    
         
            +
                private_class_method :generate_tag_category_endpoints
         
     | 
| 
      
 99 
     | 
    
         
            +
              end
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
      
 101 
     | 
    
         
            +
              module ApiImplSnippetHelpers
         
     | 
| 
      
 102 
     | 
    
         
            +
                private
         
     | 
| 
      
 103 
     | 
    
         
            +
             
     | 
| 
      
 104 
     | 
    
         
            +
                def snippet_dates
         
     | 
| 
      
 105 
     | 
    
         
            +
                  @snippet_dates ||= (data['snippets'] || {}).keys.sort.reverse
         
     | 
| 
      
 106 
     | 
    
         
            +
                end
         
     | 
| 
      
 107 
     | 
    
         
            +
             
     | 
| 
      
 108 
     | 
    
         
            +
                def snippets
         
     | 
| 
      
 109 
     | 
    
         
            +
                  @snippets ||= snippet_dates.map { |t| [t, data['snippets'][t]] }.to_h
         
     | 
| 
      
 110 
     | 
    
         
            +
                end
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
                def snippets_by_user
         
     | 
| 
      
 113 
     | 
    
         
            +
                  @snippets_by_user ||= snippets
         
     | 
| 
      
 114 
     | 
    
         
            +
                    .flat_map { |date, batch| batch.map { |snippet| [date, snippet] } }
         
     | 
| 
      
 115 
     | 
    
         
            +
                    .group_by { |_date, snippet| snippet['name'] }
         
     | 
| 
      
 116 
     | 
    
         
            +
                    .map { |name, mapping| [name, mapping.to_h] }
         
     | 
| 
      
 117 
     | 
    
         
            +
                    .to_h
         
     | 
| 
      
 118 
     | 
    
         
            +
                end
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
                def snippets_summary
         
     | 
| 
      
 121 
     | 
    
         
            +
                  @snippet_summary ||= {
         
     | 
| 
      
 122 
     | 
    
         
            +
                    'latest' => snippet_dates.first,
         
     | 
| 
      
 123 
     | 
    
         
            +
                    'all' => snippet_dates,
         
     | 
| 
      
 124 
     | 
    
         
            +
                    'users' => Canonicalizer.team_xrefs(
         
     | 
| 
      
 125 
     | 
    
         
            +
                      data['team'], snippets_by_user.keys),
         
     | 
| 
      
 126 
     | 
    
         
            +
                  }
         
     | 
| 
      
 127 
     | 
    
         
            +
                end
         
     | 
| 
      
 128 
     | 
    
         
            +
             
     | 
| 
      
 129 
     | 
    
         
            +
                def generate_latest_snippet_endpoint
         
     | 
| 
      
 130 
     | 
    
         
            +
                  return if snippets.empty?
         
     | 
| 
      
 131 
     | 
    
         
            +
                  latest = snippets.first
         
     | 
| 
      
 132 
     | 
    
         
            +
                  endpoint = 'snippets/latest'
         
     | 
| 
      
 133 
     | 
    
         
            +
                  Endpoint.create(site, "#{baseurl}/#{endpoint}",
         
     | 
| 
      
 134 
     | 
    
         
            +
                    { 'datestamp' => latest[0] }.merge(envelop(endpoint, latest[1])))
         
     | 
| 
      
 135 
     | 
    
         
            +
                end
         
     | 
| 
      
 136 
     | 
    
         
            +
             
     | 
| 
      
 137 
     | 
    
         
            +
                def generate_snippets_by_date_endpoints
         
     | 
| 
      
 138 
     | 
    
         
            +
                  snippets.each do |timestamp, batch|
         
     | 
| 
      
 139 
     | 
    
         
            +
                    endpoint = "snippets/#{timestamp}"
         
     | 
| 
      
 140 
     | 
    
         
            +
                    Endpoint.create site, "#{baseurl}/#{endpoint}", envelop(endpoint, batch)
         
     | 
| 
      
 141 
     | 
    
         
            +
                  end
         
     | 
| 
      
 142 
     | 
    
         
            +
                end
         
     | 
| 
      
 143 
     | 
    
         
            +
             
     | 
| 
      
 144 
     | 
    
         
            +
                def generate_snippets_by_user_endpoints
         
     | 
| 
      
 145 
     | 
    
         
            +
                  snippets_by_user.each do |name, batch|
         
     | 
| 
      
 146 
     | 
    
         
            +
                    Endpoint.create site, "#{baseurl}/snippets/#{name}", batch
         
     | 
| 
      
 147 
     | 
    
         
            +
                    Endpoint.create(
         
     | 
| 
      
 148 
     | 
    
         
            +
                      site, "#{baseurl}/snippets/#{name}/latest", [batch.first].to_h)
         
     | 
| 
      
 149 
     | 
    
         
            +
                  end
         
     | 
| 
      
 150 
     | 
    
         
            +
                end
         
     | 
| 
      
 151 
     | 
    
         
            +
             
     | 
| 
      
 152 
     | 
    
         
            +
                def generate_snippets_index_summary_endpoint
         
     | 
| 
      
 153 
     | 
    
         
            +
                  generate_index_endpoint(
         
     | 
| 
      
 154 
     | 
    
         
            +
                    'snippets', 'Snippets', 'Summary of all available snippets',
         
     | 
| 
      
 155 
     | 
    
         
            +
                    snippets_summary) unless snippets.empty?
         
     | 
| 
      
 156 
     | 
    
         
            +
                end
         
     | 
| 
      
 157 
     | 
    
         
            +
              end
         
     | 
| 
      
 158 
     | 
    
         
            +
             
     | 
| 
      
 159 
     | 
    
         
            +
              class ApiImpl
         
     | 
| 
      
 160 
     | 
    
         
            +
                attr_accessor :site, :data, :index_endpoints, :baseurl
         
     | 
| 
      
 161 
     | 
    
         
            +
                include ApiImplSnippetHelpers
         
     | 
| 
      
 162 
     | 
    
         
            +
             
     | 
| 
      
 163 
     | 
    
         
            +
                def initialize(site, baseurl)
         
     | 
| 
      
 164 
     | 
    
         
            +
                  @site = site
         
     | 
| 
      
 165 
     | 
    
         
            +
                  @data = site.data
         
     | 
| 
      
 166 
     | 
    
         
            +
                  @index_endpoints = []
         
     | 
| 
      
 167 
     | 
    
         
            +
                  @baseurl = baseurl
         
     | 
| 
      
 168 
     | 
    
         
            +
                end
         
     | 
| 
      
 169 
     | 
    
         
            +
             
     | 
| 
      
 170 
     | 
    
         
            +
                def self_link(endpoint)
         
     | 
| 
      
 171 
     | 
    
         
            +
                  File.join site.config['url'], baseurl, endpoint
         
     | 
| 
      
 172 
     | 
    
         
            +
                end
         
     | 
| 
      
 173 
     | 
    
         
            +
             
     | 
| 
      
 174 
     | 
    
         
            +
                def envelop(endpoint, items)
         
     | 
| 
      
 175 
     | 
    
         
            +
                  return if items.nil? || items.empty?
         
     | 
| 
      
 176 
     | 
    
         
            +
                  { 'self' => self_link(endpoint), 'results' => items }
         
     | 
| 
      
 177 
     | 
    
         
            +
                end
         
     | 
| 
      
 178 
     | 
    
         
            +
             
     | 
| 
      
 179 
     | 
    
         
            +
                def generate_index_endpoint(endpoint, title, description, items)
         
     | 
| 
      
 180 
     | 
    
         
            +
                  return if items.nil? || items.empty?
         
     | 
| 
      
 181 
     | 
    
         
            +
                  Endpoint.create site, "#{baseurl}/#{endpoint}", items
         
     | 
| 
      
 182 
     | 
    
         
            +
                  index_endpoints << {
         
     | 
| 
      
 183 
     | 
    
         
            +
                    'endpoint' => endpoint, 'title' => title, 'description' => description
         
     | 
| 
      
 184 
     | 
    
         
            +
                  }
         
     | 
| 
      
 185 
     | 
    
         
            +
                end
         
     | 
| 
      
 186 
     | 
    
         
            +
             
     | 
| 
      
 187 
     | 
    
         
            +
                def generate_tag_category_endpoint(category)
         
     | 
| 
      
 188 
     | 
    
         
            +
                  canonicalized = Canonicalizer.canonicalize(category)
         
     | 
| 
      
 189 
     | 
    
         
            +
                  generate_index_endpoint(canonicalized, category,
         
     | 
| 
      
 190 
     | 
    
         
            +
                    "Index of team members by #{category.downcase}",
         
     | 
| 
      
 191 
     | 
    
         
            +
                    envelop(canonicalized, (data[canonicalized] || {}).values))
         
     | 
| 
      
 192 
     | 
    
         
            +
                end
         
     | 
| 
      
 193 
     | 
    
         
            +
             
     | 
| 
      
 194 
     | 
    
         
            +
                def generate_index_endpoint_for_collection(endpoint_info)
         
     | 
| 
      
 195 
     | 
    
         
            +
                  collection = endpoint_info['collection']
         
     | 
| 
      
 196 
     | 
    
         
            +
                  generate_index_endpoint(
         
     | 
| 
      
 197 
     | 
    
         
            +
                    endpoint_info['collection'], endpoint_info['title'],
         
     | 
| 
      
 198 
     | 
    
         
            +
                    endpoint_info['description'],
         
     | 
| 
      
 199 
     | 
    
         
            +
                    envelop(collection, (data[collection] || {}).values))
         
     | 
| 
      
 200 
     | 
    
         
            +
                end
         
     | 
| 
      
 201 
     | 
    
         
            +
             
     | 
| 
      
 202 
     | 
    
         
            +
                def generate_item_endpoints(collection_name)
         
     | 
| 
      
 203 
     | 
    
         
            +
                  (data[collection_name] || {}).each do |identifier, value|
         
     | 
| 
      
 204 
     | 
    
         
            +
                    identifier = Canonicalizer.canonicalize(identifier)
         
     | 
| 
      
 205 
     | 
    
         
            +
                    url = "#{baseurl}/#{collection_name}/#{identifier}"
         
     | 
| 
      
 206 
     | 
    
         
            +
                    Endpoint.create site, url, value
         
     | 
| 
      
 207 
     | 
    
         
            +
                  end
         
     | 
| 
      
 208 
     | 
    
         
            +
                end
         
     | 
| 
      
 209 
     | 
    
         
            +
             
     | 
| 
      
 210 
     | 
    
         
            +
                def generate_snippets_endpoints
         
     | 
| 
      
 211 
     | 
    
         
            +
                  generate_latest_snippet_endpoint
         
     | 
| 
      
 212 
     | 
    
         
            +
                  generate_snippets_by_date_endpoints
         
     | 
| 
      
 213 
     | 
    
         
            +
                  generate_snippets_by_user_endpoints
         
     | 
| 
      
 214 
     | 
    
         
            +
                  generate_snippets_index_summary_endpoint
         
     | 
| 
      
 215 
     | 
    
         
            +
                end
         
     | 
| 
      
 216 
     | 
    
         
            +
              end
         
     | 
| 
      
 217 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,125 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # @author Mike Bland (michael.bland@gsa.gov)
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require_relative 'config'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require_relative 'cross_referencer'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            module TeamApi
         
     | 
| 
      
 7 
     | 
    
         
            +
              # Contains utility functions for canonicalizing names and the order of data.
         
     | 
| 
      
 8 
     | 
    
         
            +
              class Canonicalizer
         
     | 
| 
      
 9 
     | 
    
         
            +
                # Canonicalizes the order and names of certain fields within site_data.
         
     | 
| 
      
 10 
     | 
    
         
            +
                def self.canonicalize_data(site_data)
         
     | 
| 
      
 11 
     | 
    
         
            +
                  sort_collections site_data
         
     | 
| 
      
 12 
     | 
    
         
            +
                  canonicalize_tag_category site_data['skills']
         
     | 
| 
      
 13 
     | 
    
         
            +
                  canonicalize_tag_category site_data['interests']
         
     | 
| 
      
 14 
     | 
    
         
            +
                end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                def self.sort_collections(site_data)
         
     | 
| 
      
 17 
     | 
    
         
            +
                  Config.endpoint_config.each do |endpoint_info|
         
     | 
| 
      
 18 
     | 
    
         
            +
                    collection = endpoint_info['collection']
         
     | 
| 
      
 19 
     | 
    
         
            +
                    next unless site_data.member? collection
         
     | 
| 
      
 20 
     | 
    
         
            +
                    sorted = sort_collection_values(endpoint_info,
         
     | 
| 
      
 21 
     | 
    
         
            +
                      site_data[collection].values)
         
     | 
| 
      
 22 
     | 
    
         
            +
                    sort_item_xrefs endpoint_info, sorted
         
     | 
| 
      
 23 
     | 
    
         
            +
                    item_id_field = endpoint_info['item_id']
         
     | 
| 
      
 24 
     | 
    
         
            +
                    site_data[collection] = sorted.map { |i| [i[item_id_field], i] }.to_h
         
     | 
| 
      
 25 
     | 
    
         
            +
                  end
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                def self.sort_collection_values(endpoint_info, values)
         
     | 
| 
      
 29 
     | 
    
         
            +
                  sort_by_field = endpoint_info['sort_by']
         
     | 
| 
      
 30 
     | 
    
         
            +
                  if sort_by_field == 'last_name'
         
     | 
| 
      
 31 
     | 
    
         
            +
                    sort_by_last_name values
         
     | 
| 
      
 32 
     | 
    
         
            +
                  else
         
     | 
| 
      
 33 
     | 
    
         
            +
                    values.sort_by { |i| (i[sort_by_field] || '').downcase }
         
     | 
| 
      
 34 
     | 
    
         
            +
                  end
         
     | 
| 
      
 35 
     | 
    
         
            +
                end
         
     | 
| 
      
 36 
     | 
    
         
            +
                private_class_method :sort_collection_values
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                def self.sort_item_xrefs(endpoint_info, collection)
         
     | 
| 
      
 39 
     | 
    
         
            +
                  collection.each do |item|
         
     | 
| 
      
 40 
     | 
    
         
            +
                    sortable_item_fields(item, endpoint_info).each do |field, field_info|
         
     | 
| 
      
 41 
     | 
    
         
            +
                      item[field] = sort_collection_values field_info, item[field]
         
     | 
| 
      
 42 
     | 
    
         
            +
                    end
         
     | 
| 
      
 43 
     | 
    
         
            +
                  end
         
     | 
| 
      
 44 
     | 
    
         
            +
                end
         
     | 
| 
      
 45 
     | 
    
         
            +
                private_class_method :sort_item_xrefs
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                def self.sortable_item_fields(item, collection_endpoint_info)
         
     | 
| 
      
 48 
     | 
    
         
            +
                  collection_endpoint_info['item_collections'].map do |item_spec|
         
     | 
| 
      
 49 
     | 
    
         
            +
                    field, endpoint_info = parse_collection_spec item_spec
         
     | 
| 
      
 50 
     | 
    
         
            +
                    [field, endpoint_info] if item[field]
         
     | 
| 
      
 51 
     | 
    
         
            +
                  end.compact
         
     | 
| 
      
 52 
     | 
    
         
            +
                end
         
     | 
| 
      
 53 
     | 
    
         
            +
                private_class_method :sortable_item_fields
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                def self.parse_collection_spec(collection_spec)
         
     | 
| 
      
 56 
     | 
    
         
            +
                  if collection_spec.instance_of? Hash
         
     | 
| 
      
 57 
     | 
    
         
            +
                    [collection_spec['field'],
         
     | 
| 
      
 58 
     | 
    
         
            +
                     Config.endpoint_info_by_collection[collection_spec['collection']]]
         
     | 
| 
      
 59 
     | 
    
         
            +
                  else
         
     | 
| 
      
 60 
     | 
    
         
            +
                    [collection_spec, Config.endpoint_info_by_collection[collection_spec]]
         
     | 
| 
      
 61 
     | 
    
         
            +
                  end
         
     | 
| 
      
 62 
     | 
    
         
            +
                end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                # Returns a canonicalized, URL-friendly substitute for an arbitrary string.
         
     | 
| 
      
 65 
     | 
    
         
            +
                # +s+:: string to canonicalize
         
     | 
| 
      
 66 
     | 
    
         
            +
                def self.canonicalize(s)
         
     | 
| 
      
 67 
     | 
    
         
            +
                  s.downcase.gsub(/\s+/, '-')
         
     | 
| 
      
 68 
     | 
    
         
            +
                end
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                def self.comparable_name(person)
         
     | 
| 
      
 71 
     | 
    
         
            +
                  if person['last_name']
         
     | 
| 
      
 72 
     | 
    
         
            +
                    [person['last_name'].downcase, person['first_name'].downcase]
         
     | 
| 
      
 73 
     | 
    
         
            +
                  else
         
     | 
| 
      
 74 
     | 
    
         
            +
                    # Trim off title suffix, if any.
         
     | 
| 
      
 75 
     | 
    
         
            +
                    full_name = person['full_name'].downcase.split(',')[0]
         
     | 
| 
      
 76 
     | 
    
         
            +
                    last_name = full_name.split.last
         
     | 
| 
      
 77 
     | 
    
         
            +
                    [last_name, full_name]
         
     | 
| 
      
 78 
     | 
    
         
            +
                  end
         
     | 
| 
      
 79 
     | 
    
         
            +
                end
         
     | 
| 
      
 80 
     | 
    
         
            +
                private_class_method :comparable_name
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
                # Sorts an array of team member data hashes based on the team members'
         
     | 
| 
      
 83 
     | 
    
         
            +
                # last names.
         
     | 
| 
      
 84 
     | 
    
         
            +
                # +team+:: An array of team member data hashes
         
     | 
| 
      
 85 
     | 
    
         
            +
                def self.sort_by_last_name(team)
         
     | 
| 
      
 86 
     | 
    
         
            +
                  team.sort_by { |member| comparable_name member }
         
     | 
| 
      
 87 
     | 
    
         
            +
                end
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
                def self.team_xrefs(team, usernames)
         
     | 
| 
      
 90 
     | 
    
         
            +
                  fields = CrossReferencer::TEAM_FIELDS
         
     | 
| 
      
 91 
     | 
    
         
            +
                  usernames
         
     | 
| 
      
 92 
     | 
    
         
            +
                    .map { |username| team[username] }
         
     | 
| 
      
 93 
     | 
    
         
            +
                    .compact
         
     | 
| 
      
 94 
     | 
    
         
            +
                    .map { |member| member.select { |field, _| fields.include? field } }
         
     | 
| 
      
 95 
     | 
    
         
            +
                    .sort_by { |member| comparable_name member }
         
     | 
| 
      
 96 
     | 
    
         
            +
                end
         
     | 
| 
      
 97 
     | 
    
         
            +
             
     | 
| 
      
 98 
     | 
    
         
            +
                # Breaks a YYYYMMDD timestamp into a hyphenated version: YYYY-MM-DD
         
     | 
| 
      
 99 
     | 
    
         
            +
                # +timestamp+:: timestamp in the form YYYYMMDD
         
     | 
| 
      
 100 
     | 
    
         
            +
                def self.hyphenate_yyyymmdd(timestamp)
         
     | 
| 
      
 101 
     | 
    
         
            +
                  "#{timestamp[0..3]}-#{timestamp[4..5]}-#{timestamp[6..7]}"
         
     | 
| 
      
 102 
     | 
    
         
            +
                end
         
     | 
| 
      
 103 
     | 
    
         
            +
             
     | 
| 
      
 104 
     | 
    
         
            +
                # Consolidate tags entries that are not exactly the same. Selects the
         
     | 
| 
      
 105 
     | 
    
         
            +
                # lexicographically smaller version of the tag as a standard.
         
     | 
| 
      
 106 
     | 
    
         
            +
                #
         
     | 
| 
      
 107 
     | 
    
         
            +
                # In the future, we may just consider raising an error if there are two
         
     | 
| 
      
 108 
     | 
    
         
            +
                # different strings for the same thing.
         
     | 
| 
      
 109 
     | 
    
         
            +
                def self.canonicalize_tag_category(tags_xrefs)
         
     | 
| 
      
 110 
     | 
    
         
            +
                  return if tags_xrefs.nil? || tags_xrefs.empty?
         
     | 
| 
      
 111 
     | 
    
         
            +
                  tags_xrefs.replace(CrossReferencer.map_reduce(tags_xrefs.values,
         
     | 
| 
      
 112 
     | 
    
         
            +
                    ->(xref) { [[xref['slug'], xref]] }, method(:consolidate_xrefs)).to_h)
         
     | 
| 
      
 113 
     | 
    
         
            +
                end
         
     | 
| 
      
 114 
     | 
    
         
            +
             
     | 
| 
      
 115 
     | 
    
         
            +
                def self.consolidate_xrefs(slug, xrefs)
         
     | 
| 
      
 116 
     | 
    
         
            +
                  xrefs.sort_by! { |xref| xref['name'] }
         
     | 
| 
      
 117 
     | 
    
         
            +
                  result = xrefs.each_with_object(xrefs.shift) do |xref, consolidated|
         
     | 
| 
      
 118 
     | 
    
         
            +
                    consolidated['members'].concat xref['members']
         
     | 
| 
      
 119 
     | 
    
         
            +
                  end
         
     | 
| 
      
 120 
     | 
    
         
            +
                  result['members'].sort_by! { |member| comparable_name member }
         
     | 
| 
      
 121 
     | 
    
         
            +
                  [slug, result]
         
     | 
| 
      
 122 
     | 
    
         
            +
                end
         
     | 
| 
      
 123 
     | 
    
         
            +
                private_class_method :consolidate_xrefs
         
     | 
| 
      
 124 
     | 
    
         
            +
              end
         
     | 
| 
      
 125 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,18 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # @author Mike Bland (michael.bland@gsa.gov)
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module TeamApi
         
     | 
| 
      
 4 
     | 
    
         
            +
              class Config
         
     | 
| 
      
 5 
     | 
    
         
            +
                def self.endpoint_config
         
     | 
| 
      
 6 
     | 
    
         
            +
                  @endpoint_config ||= begin
         
     | 
| 
      
 7 
     | 
    
         
            +
                    endpoint_config_path = File.join File.dirname(__FILE__), 'endpoints.yml'
         
     | 
| 
      
 8 
     | 
    
         
            +
                    SafeYAML.load_file endpoint_config_path, safe: true
         
     | 
| 
      
 9 
     | 
    
         
            +
                  end
         
     | 
| 
      
 10 
     | 
    
         
            +
                end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                def self.endpoint_info_by_collection
         
     | 
| 
      
 13 
     | 
    
         
            +
                  @endpoint_info_by_collection ||= Config.endpoint_config.map do |item|
         
     | 
| 
      
 14 
     | 
    
         
            +
                    [item['collection'], item]
         
     | 
| 
      
 15 
     | 
    
         
            +
                  end.to_h
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
              end
         
     | 
| 
      
 18 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,202 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # @author Mike Bland (michael.bland@gsa.gov)
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require_relative 'api'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require_relative 'canonicalizer'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 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
         
     | 
| 
      
 109 
     | 
    
         
            +
             
     | 
| 
      
 110 
     | 
    
         
            +
              # Builds cross-references between data sets.
         
     | 
| 
      
 111 
     | 
    
         
            +
              class CrossReferencer
         
     | 
| 
      
 112 
     | 
    
         
            +
                TEAM_FIELDS = %w(name last_name first_name full_name self)
         
     | 
| 
      
 113 
     | 
    
         
            +
                PROJECT_FIELDS = %w(name project self)
         
     | 
| 
      
 114 
     | 
    
         
            +
                WORKING_GROUP_FIELDS = %w(name full_name self)
         
     | 
| 
      
 115 
     | 
    
         
            +
                GUILD_FIELDS = %w(name full_name self)
         
     | 
| 
      
 116 
     | 
    
         
            +
                TAG_CATEGORIES = %w(skills interests)
         
     | 
| 
      
 117 
     | 
    
         
            +
             
     | 
| 
      
 118 
     | 
    
         
            +
                # Build cross-references between data sets.
         
     | 
| 
      
 119 
     | 
    
         
            +
                # +site_data+:: Jekyll +site.data+ object
         
     | 
| 
      
 120 
     | 
    
         
            +
                def self.build_xrefs(site)
         
     | 
| 
      
 121 
     | 
    
         
            +
                  team, projects, working_groups, guilds = create_xref_data site
         
     | 
| 
      
 122 
     | 
    
         
            +
             
     | 
| 
      
 123 
     | 
    
         
            +
                  projects.create_xrefs team
         
     | 
| 
      
 124 
     | 
    
         
            +
                  [working_groups, guilds].each do |grouplet|
         
     | 
| 
      
 125 
     | 
    
         
            +
                    grouplet.create_xrefs team, source_to_target_field: 'leads'
         
     | 
| 
      
 126 
     | 
    
         
            +
                    grouplet.create_xrefs team, source_to_target_field: 'members'
         
     | 
| 
      
 127 
     | 
    
         
            +
                  end
         
     | 
| 
      
 128 
     | 
    
         
            +
             
     | 
| 
      
 129 
     | 
    
         
            +
                  xref_tags_and_team_members site, TAG_CATEGORIES, team
         
     | 
| 
      
 130 
     | 
    
         
            +
                  xref_locations site.data, team, [projects, working_groups, guilds]
         
     | 
| 
      
 131 
     | 
    
         
            +
                end
         
     | 
| 
      
 132 
     | 
    
         
            +
             
     | 
| 
      
 133 
     | 
    
         
            +
                def self.create_xref_data(site)
         
     | 
| 
      
 134 
     | 
    
         
            +
                  [CrossReferenceData.new(site, 'team', TEAM_FIELDS),
         
     | 
| 
      
 135 
     | 
    
         
            +
                   CrossReferenceData.new(site, 'projects', PROJECT_FIELDS),
         
     | 
| 
      
 136 
     | 
    
         
            +
                   CrossReferenceData.new(site, 'working-groups', WORKING_GROUP_FIELDS),
         
     | 
| 
      
 137 
     | 
    
         
            +
                   CrossReferenceData.new(site, 'guilds', GUILD_FIELDS),
         
     | 
| 
      
 138 
     | 
    
         
            +
                  ]
         
     | 
| 
      
 139 
     | 
    
         
            +
                end
         
     | 
| 
      
 140 
     | 
    
         
            +
                private_class_method :create_xref_data
         
     | 
| 
      
 141 
     | 
    
         
            +
             
     | 
| 
      
 142 
     | 
    
         
            +
                def self.xref_tags_and_team_members(site, tag_categories, team_xref)
         
     | 
| 
      
 143 
     | 
    
         
            +
                  tag_categories.each do |category|
         
     | 
| 
      
 144 
     | 
    
         
            +
                    xrefs = create_tag_xrefs(site, (site.data['team'] || {}).values,
         
     | 
| 
      
 145 
     | 
    
         
            +
                      category, team_xref)
         
     | 
| 
      
 146 
     | 
    
         
            +
                    site.data[category] = xrefs unless xrefs.empty?
         
     | 
| 
      
 147 
     | 
    
         
            +
                  end
         
     | 
| 
      
 148 
     | 
    
         
            +
                end
         
     | 
| 
      
 149 
     | 
    
         
            +
             
     | 
| 
      
 150 
     | 
    
         
            +
                def self.create_tag_xrefs(site, items, category, xref_data)
         
     | 
| 
      
 151 
     | 
    
         
            +
                  map_items_to_tags = lambda do |item|
         
     | 
| 
      
 152 
     | 
    
         
            +
                    item_xref = xref_data.item_to_xref item
         
     | 
| 
      
 153 
     | 
    
         
            +
                    item[category].map { |tag| [tag, item_xref] } unless item[category].nil?
         
     | 
| 
      
 154 
     | 
    
         
            +
                  end
         
     | 
| 
      
 155 
     | 
    
         
            +
                  create_tag_xrefs = lambda do |tag, item_xrefs|
         
     | 
| 
      
 156 
     | 
    
         
            +
                    [tag, tag_xref(site, category, tag, item_xrefs)]
         
     | 
| 
      
 157 
     | 
    
         
            +
                  end
         
     | 
| 
      
 158 
     | 
    
         
            +
                  map_reduce(items, map_items_to_tags, create_tag_xrefs).to_h
         
     | 
| 
      
 159 
     | 
    
         
            +
                end
         
     | 
| 
      
 160 
     | 
    
         
            +
             
     | 
| 
      
 161 
     | 
    
         
            +
                # Returns an Array of objects after mapping and reducing items.
         
     | 
| 
      
 162 
     | 
    
         
            +
                # mapper takes a single item and returns an Array of [key, value] pairs.
         
     | 
| 
      
 163 
     | 
    
         
            +
                # reducer takes a [key, Array of values] pair and returns a single item.
         
     | 
| 
      
 164 
     | 
    
         
            +
                def self.map_reduce(items, mapper, reducer)
         
     | 
| 
      
 165 
     | 
    
         
            +
                  items.flat_map { |item| mapper.call(item) }.compact
         
     | 
| 
      
 166 
     | 
    
         
            +
                    .each_with_object({}) { |kv, shuffle| (shuffle[kv[0]] ||= []) << kv[1] }
         
     | 
| 
      
 167 
     | 
    
         
            +
                    .map { |key, values| reducer.call(key, values) }.compact
         
     | 
| 
      
 168 
     | 
    
         
            +
                end
         
     | 
| 
      
 169 
     | 
    
         
            +
             
     | 
| 
      
 170 
     | 
    
         
            +
                def self.tag_xref(site, category, tag, members)
         
     | 
| 
      
 171 
     | 
    
         
            +
                  category_slug = Canonicalizer.canonicalize category
         
     | 
| 
      
 172 
     | 
    
         
            +
                  tag_slug = Canonicalizer.canonicalize tag
         
     | 
| 
      
 173 
     | 
    
         
            +
                  { 'name' => tag,
         
     | 
| 
      
 174 
     | 
    
         
            +
                    'slug' => tag_slug,
         
     | 
| 
      
 175 
     | 
    
         
            +
                    'self' => File.join(Api.baseurl(site), category_slug, tag_slug),
         
     | 
| 
      
 176 
     | 
    
         
            +
                    'members' => Canonicalizer.sort_by_last_name(members || []),
         
     | 
| 
      
 177 
     | 
    
         
            +
                  }
         
     | 
| 
      
 178 
     | 
    
         
            +
                end
         
     | 
| 
      
 179 
     | 
    
         
            +
             
     | 
| 
      
 180 
     | 
    
         
            +
                def self.group_names_to_team_xrefs(team, collection_xrefs)
         
     | 
| 
      
 181 
     | 
    
         
            +
                  collection_xrefs.map do |xref|
         
     | 
| 
      
 182 
     | 
    
         
            +
                    xrefs = team.flat_map { |i| i[xref.collection_name] }.compact.uniq
         
     | 
| 
      
 183 
     | 
    
         
            +
                    [xref.collection_name, xrefs] unless xrefs.empty?
         
     | 
| 
      
 184 
     | 
    
         
            +
                  end.compact.to_h
         
     | 
| 
      
 185 
     | 
    
         
            +
                end
         
     | 
| 
      
 186 
     | 
    
         
            +
             
     | 
| 
      
 187 
     | 
    
         
            +
                # Produces an array of locations containing cross references to team
         
     | 
| 
      
 188 
     | 
    
         
            +
                # members and all projects, working groups, guilds, etc. associated with
         
     | 
| 
      
 189 
     | 
    
         
            +
                # each team member. All team member cross-references must already exist.
         
     | 
| 
      
 190 
     | 
    
         
            +
                def self.xref_locations(site_data, team_xref, collection_xrefs)
         
     | 
| 
      
 191 
     | 
    
         
            +
                  location_xrefs = site_data['team'].values.group_by { |i| i['location'] }
         
     | 
| 
      
 192 
     | 
    
         
            +
                    .map do |location_code, team|
         
     | 
| 
      
 193 
     | 
    
         
            +
                      [location_code,
         
     | 
| 
      
 194 
     | 
    
         
            +
                       {
         
     | 
| 
      
 195 
     | 
    
         
            +
                         'team' => team.map { |member| team_xref.item_to_xref member },
         
     | 
| 
      
 196 
     | 
    
         
            +
                       }.merge(group_names_to_team_xrefs(team, collection_xrefs)),
         
     | 
| 
      
 197 
     | 
    
         
            +
                      ] unless location_code.nil?
         
     | 
| 
      
 198 
     | 
    
         
            +
                    end
         
     | 
| 
      
 199 
     | 
    
         
            +
                  HashJoiner.deep_merge site_data['locations'], location_xrefs.compact.to_h
         
     | 
| 
      
 200 
     | 
    
         
            +
                end
         
     | 
| 
      
 201 
     | 
    
         
            +
              end
         
     | 
| 
      
 202 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,58 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            - collection: team
         
     | 
| 
      
 2 
     | 
    
         
            +
              title: Team
         
     | 
| 
      
 3 
     | 
    
         
            +
              description: Team member info, indexed by username
         
     | 
| 
      
 4 
     | 
    
         
            +
              item_id: name
         
     | 
| 
      
 5 
     | 
    
         
            +
              sort_by: last_name
         
     | 
| 
      
 6 
     | 
    
         
            +
              item_collections:
         
     | 
| 
      
 7 
     | 
    
         
            +
              - projects
         
     | 
| 
      
 8 
     | 
    
         
            +
              - working-groups
         
     | 
| 
      
 9 
     | 
    
         
            +
              - guilds
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            - collection: locations
         
     | 
| 
      
 12 
     | 
    
         
            +
              title: Locations
         
     | 
| 
      
 13 
     | 
    
         
            +
              description: Index of team members by location code
         
     | 
| 
      
 14 
     | 
    
         
            +
              item_id: code
         
     | 
| 
      
 15 
     | 
    
         
            +
              sort_by: label
         
     | 
| 
      
 16 
     | 
    
         
            +
              item_collections:
         
     | 
| 
      
 17 
     | 
    
         
            +
              - team
         
     | 
| 
      
 18 
     | 
    
         
            +
              - projects
         
     | 
| 
      
 19 
     | 
    
         
            +
              - working-groups
         
     | 
| 
      
 20 
     | 
    
         
            +
              - guilds
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
            - collection: projects
         
     | 
| 
      
 23 
     | 
    
         
            +
              title: Projects
         
     | 
| 
      
 24 
     | 
    
         
            +
              description: Project info, indexed by short project name
         
     | 
| 
      
 25 
     | 
    
         
            +
              item_id: name
         
     | 
| 
      
 26 
     | 
    
         
            +
              sort_by: full_name
         
     | 
| 
      
 27 
     | 
    
         
            +
              item_collections:
         
     | 
| 
      
 28 
     | 
    
         
            +
              - team
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
            - collection: departments
         
     | 
| 
      
 31 
     | 
    
         
            +
              title: Departments
         
     | 
| 
      
 32 
     | 
    
         
            +
              description: Department info, indexed by department name
         
     | 
| 
      
 33 
     | 
    
         
            +
              item_id: name
         
     | 
| 
      
 34 
     | 
    
         
            +
              sort_by: name
         
     | 
| 
      
 35 
     | 
    
         
            +
              item_collections:
         
     | 
| 
      
 36 
     | 
    
         
            +
              - team
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
            - collection: working-groups
         
     | 
| 
      
 39 
     | 
    
         
            +
              title: Working Groups
         
     | 
| 
      
 40 
     | 
    
         
            +
              description: Working Groups info, indexed by name
         
     | 
| 
      
 41 
     | 
    
         
            +
              item_id: name
         
     | 
| 
      
 42 
     | 
    
         
            +
              sort_by: full_name
         
     | 
| 
      
 43 
     | 
    
         
            +
              item_collections:
         
     | 
| 
      
 44 
     | 
    
         
            +
              - field: leads
         
     | 
| 
      
 45 
     | 
    
         
            +
                collection: team
         
     | 
| 
      
 46 
     | 
    
         
            +
              - field: members
         
     | 
| 
      
 47 
     | 
    
         
            +
                collection: team
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
            - collection: guilds
         
     | 
| 
      
 50 
     | 
    
         
            +
              title: Guilds
         
     | 
| 
      
 51 
     | 
    
         
            +
              description: Guilds info, indexed by name
         
     | 
| 
      
 52 
     | 
    
         
            +
              item_id: name
         
     | 
| 
      
 53 
     | 
    
         
            +
              sort_by: full_name
         
     | 
| 
      
 54 
     | 
    
         
            +
              item_collections:
         
     | 
| 
      
 55 
     | 
    
         
            +
              - field: leads
         
     | 
| 
      
 56 
     | 
    
         
            +
                collection: team
         
     | 
| 
      
 57 
     | 
    
         
            +
              - field: members
         
     | 
| 
      
 58 
     | 
    
         
            +
                collection: team
         
     | 
| 
         @@ -0,0 +1,33 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # @author Mike Bland (michael.bland@gsa.gov)
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require 'safe_yaml'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module TeamApi
         
     | 
| 
      
 6 
     | 
    
         
            +
              class FrontMatter
         
     | 
| 
      
 7 
     | 
    
         
            +
                class Error < StandardError
         
     | 
| 
      
 8 
     | 
    
         
            +
                end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                MARKER = '---'
         
     | 
| 
      
 11 
     | 
    
         
            +
                START_MARKER = "#{MARKER}\n"
         
     | 
| 
      
 12 
     | 
    
         
            +
                END_MARKER = "\n#{MARKER}\n"
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                def self.update_front_matter(filename)
         
     | 
| 
      
 15 
     | 
    
         
            +
                  end_front_matter = front_matter_end_index filename, content
         
     | 
| 
      
 16 
     | 
    
         
            +
                  front_matter = content[0..end_front_matter]
         
     | 
| 
      
 17 
     | 
    
         
            +
                  content = content[end_front_matter..-1]
         
     | 
| 
      
 18 
     | 
    
         
            +
                  front_matter = SafeYAML.load front_matter, safe: true
         
     | 
| 
      
 19 
     | 
    
         
            +
                  yield front_matter
         
     | 
| 
      
 20 
     | 
    
         
            +
                  File.write filename, "#{front_matter.to_yaml}#{content}"
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                def self.front_matter_end_index(filename, content)
         
     | 
| 
      
 24 
     | 
    
         
            +
                  unless content.start_with? START_MARKER
         
     | 
| 
      
 25 
     | 
    
         
            +
                    fail Error, "#{filename}: contains no front matter"
         
     | 
| 
      
 26 
     | 
    
         
            +
                  end
         
     | 
| 
      
 27 
     | 
    
         
            +
                  end_front_matter = content.index END_MARKER, START_MARKER.size
         
     | 
| 
      
 28 
     | 
    
         
            +
                  return end_front_matter unless end_front_matter.nil?
         
     | 
| 
      
 29 
     | 
    
         
            +
                  fail Error, "#{filename}: front matter does not end with '#{MARKER}'"
         
     | 
| 
      
 30 
     | 
    
         
            +
                end
         
     | 
| 
      
 31 
     | 
    
         
            +
                private_class_method :front_matter_end_index
         
     | 
| 
      
 32 
     | 
    
         
            +
              end
         
     | 
| 
      
 33 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,28 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # @author Mike Bland (michael.bland@gsa.gov)
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require_relative 'api'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require_relative 'canonicalizer'
         
     | 
| 
      
 5 
     | 
    
         
            +
            require_relative 'cross_referencer'
         
     | 
| 
      
 6 
     | 
    
         
            +
            require_relative 'joiner'
         
     | 
| 
      
 7 
     | 
    
         
            +
            require_relative 'snippets'
         
     | 
| 
      
 8 
     | 
    
         
            +
            require 'hash-joiner'
         
     | 
| 
      
 9 
     | 
    
         
            +
            require 'jekyll'
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            module TeamApi
         
     | 
| 
      
 12 
     | 
    
         
            +
              # Processes site data, generates authorization artifacts, publishes an API,
         
     | 
| 
      
 13 
     | 
    
         
            +
              # and generates cross-linked Hub pages.
         
     | 
| 
      
 14 
     | 
    
         
            +
              class Generator < ::Jekyll::Generator
         
     | 
| 
      
 15 
     | 
    
         
            +
                safe true
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                # Executes all of the data processing and artifact/page generation phases
         
     | 
| 
      
 18 
     | 
    
         
            +
                # for the Hub.
         
     | 
| 
      
 19 
     | 
    
         
            +
                def generate(site)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  Joiner.join_data(site)
         
     | 
| 
      
 21 
     | 
    
         
            +
                  Snippets.publish(site)
         
     | 
| 
      
 22 
     | 
    
         
            +
                  CrossReferencer.build_xrefs(site)
         
     | 
| 
      
 23 
     | 
    
         
            +
                  Canonicalizer.canonicalize_data(site.data)
         
     | 
| 
      
 24 
     | 
    
         
            +
                  ::HashJoiner.prune_empty_properties(site.data)
         
     | 
| 
      
 25 
     | 
    
         
            +
                  Api.generate_api(site)
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
              end
         
     | 
| 
      
 28 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,151 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # @author Mike Bland (michael.bland@gsa.gov)
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require_relative 'api'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'hash-joiner'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            module TeamApi
         
     | 
| 
      
 7 
     | 
    
         
            +
              class UnknownTeamMemberReferenceError < StandardError
         
     | 
| 
      
 8 
     | 
    
         
            +
              end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
              class UnknownSnippetUsernameError < StandardError
         
     | 
| 
      
 11 
     | 
    
         
            +
              end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
              # Joins the data from collections into +site.data+. Also filters out private
         
     | 
| 
      
 14 
     | 
    
         
            +
              # data when +site.config[+'public'] is +true+ (aka "public mode").
         
     | 
| 
      
 15 
     | 
    
         
            +
              class Joiner
         
     | 
| 
      
 16 
     | 
    
         
            +
                # Executes all of the steps to join the different data sources into
         
     | 
| 
      
 17 
     | 
    
         
            +
                # +site.data+ and filters out private data when in public mode.
         
     | 
| 
      
 18 
     | 
    
         
            +
                #
         
     | 
| 
      
 19 
     | 
    
         
            +
                # +site+:: Jekyll site data object
         
     | 
| 
      
 20 
     | 
    
         
            +
                def self.join_data(site)
         
     | 
| 
      
 21 
     | 
    
         
            +
                  impl = JoinerImpl.new site
         
     | 
| 
      
 22 
     | 
    
         
            +
                  site.data.merge! impl.collection_data
         
     | 
| 
      
 23 
     | 
    
         
            +
                  impl.promote_or_remove_data
         
     | 
| 
      
 24 
     | 
    
         
            +
                  impl.join_project_data
         
     | 
| 
      
 25 
     | 
    
         
            +
                  Api.add_self_links site
         
     | 
| 
      
 26 
     | 
    
         
            +
                  impl.join_snippet_data
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
              end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
              # Implements Joiner operations.
         
     | 
| 
      
 31 
     | 
    
         
            +
              class JoinerImpl
         
     | 
| 
      
 32 
     | 
    
         
            +
                attr_reader :site, :data, :public_mode
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                # +site+:: Jekyll site data object
         
     | 
| 
      
 35 
     | 
    
         
            +
                def initialize(site)
         
     | 
| 
      
 36 
     | 
    
         
            +
                  @site = site
         
     | 
| 
      
 37 
     | 
    
         
            +
                  @data = site.data
         
     | 
| 
      
 38 
     | 
    
         
            +
                  @public_mode = site.config['public']
         
     | 
| 
      
 39 
     | 
    
         
            +
                end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                def collection_data
         
     | 
| 
      
 42 
     | 
    
         
            +
                  @collection_data ||= site.collections.map do |data_class, collection|
         
     | 
| 
      
 43 
     | 
    
         
            +
                    groups = groups collection
         
     | 
| 
      
 44 
     | 
    
         
            +
                    result = (groups[:public] || {})
         
     | 
| 
      
 45 
     | 
    
         
            +
                    result.merge!('private' => groups[:private]) if groups[:private]
         
     | 
| 
      
 46 
     | 
    
         
            +
                    [data_class, result] unless result.empty?
         
     | 
| 
      
 47 
     | 
    
         
            +
                  end.compact.to_h
         
     | 
| 
      
 48 
     | 
    
         
            +
                end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                def groups(collection)
         
     | 
| 
      
 51 
     | 
    
         
            +
                  collection.docs
         
     | 
| 
      
 52 
     | 
    
         
            +
                    .select { |doc| doc.data['published'] != false }
         
     | 
| 
      
 53 
     | 
    
         
            +
                    .group_by { |doc| doc_visibility doc }
         
     | 
| 
      
 54 
     | 
    
         
            +
                    .map { |group, docs| [group, docs_data(docs)] }
         
     | 
| 
      
 55 
     | 
    
         
            +
                    .to_h
         
     | 
| 
      
 56 
     | 
    
         
            +
                end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                def doc_visibility(doc)
         
     | 
| 
      
 59 
     | 
    
         
            +
                  parent = File.basename File.dirname(doc.cleaned_relative_path)
         
     | 
| 
      
 60 
     | 
    
         
            +
                  (parent == 'private') ? :private : :public
         
     | 
| 
      
 61 
     | 
    
         
            +
                end
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                def docs_data(docs)
         
     | 
| 
      
 64 
     | 
    
         
            +
                  docs.map { |doc| [doc.basename_without_ext, doc.data] }.to_h
         
     | 
| 
      
 65 
     | 
    
         
            +
                end
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                def promote_or_remove_data
         
     | 
| 
      
 68 
     | 
    
         
            +
                  private_data_method = public_mode ? :remove_data : :promote_data
         
     | 
| 
      
 69 
     | 
    
         
            +
                  HashJoiner.send private_data_method, data, 'private'
         
     | 
| 
      
 70 
     | 
    
         
            +
                end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                def join_project_data
         
     | 
| 
      
 73 
     | 
    
         
            +
                  # A little bit of project data munging. Can go away after the .about.yml
         
     | 
| 
      
 74 
     | 
    
         
            +
                  # convention takes hold, hopefully.
         
     | 
| 
      
 75 
     | 
    
         
            +
                  projects = (data['projects'] ||= {})
         
     | 
| 
      
 76 
     | 
    
         
            +
                  projects.delete_if { |_, p| p['status'] == 'Hold' } if @public_mode
         
     | 
| 
      
 77 
     | 
    
         
            +
                  projects.values.each { |p| join_team_list p['team'] }
         
     | 
| 
      
 78 
     | 
    
         
            +
                end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                def team
         
     | 
| 
      
 81 
     | 
    
         
            +
                  data['team'] ||= {}
         
     | 
| 
      
 82 
     | 
    
         
            +
                end
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
                # Returns an index of team member usernames keyed by email address.
         
     | 
| 
      
 85 
     | 
    
         
            +
                def team_by_email
         
     | 
| 
      
 86 
     | 
    
         
            +
                  @team_by_email ||= team_index_by_field 'email'
         
     | 
| 
      
 87 
     | 
    
         
            +
                end
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
                # Returns an index of team member usernames keyed by email address.
         
     | 
| 
      
 90 
     | 
    
         
            +
                def team_by_github
         
     | 
| 
      
 91 
     | 
    
         
            +
                  @team_by_github ||= team_index_by_field 'github'
         
     | 
| 
      
 92 
     | 
    
         
            +
                end
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
                # Returns an index of team member usernames keyed by a particular field.
         
     | 
| 
      
 95 
     | 
    
         
            +
                def team_index_by_field(field)
         
     | 
| 
      
 96 
     | 
    
         
            +
                  team.values.map do |member|
         
     | 
| 
      
 97 
     | 
    
         
            +
                    value = member[field]
         
     | 
| 
      
 98 
     | 
    
         
            +
                    [value, member['name']] unless value.nil?
         
     | 
| 
      
 99 
     | 
    
         
            +
                  end.compact.to_h
         
     | 
| 
      
 100 
     | 
    
         
            +
                end
         
     | 
| 
      
 101 
     | 
    
         
            +
             
     | 
| 
      
 102 
     | 
    
         
            +
                # Replaces each member of team_list with a key into the team hash.
         
     | 
| 
      
 103 
     | 
    
         
            +
                # Values can be:
         
     | 
| 
      
 104 
     | 
    
         
            +
                # - Strings that are already team hash keys
         
     | 
| 
      
 105 
     | 
    
         
            +
                # - Strings that are email addresses
         
     | 
| 
      
 106 
     | 
    
         
            +
                # - Strings that are GitHub usernames
         
     | 
| 
      
 107 
     | 
    
         
            +
                # - Hashes that contain an 'email' property
         
     | 
| 
      
 108 
     | 
    
         
            +
                # - Hashes that contain a 'github' property
         
     | 
| 
      
 109 
     | 
    
         
            +
                def join_team_list(team_list)
         
     | 
| 
      
 110 
     | 
    
         
            +
                  (team_list || []).map! do |reference|
         
     | 
| 
      
 111 
     | 
    
         
            +
                    member = team_member_from_reference reference
         
     | 
| 
      
 112 
     | 
    
         
            +
                    if member.nil?
         
     | 
| 
      
 113 
     | 
    
         
            +
                      fail UnknownTeamMemberReferenceError, member unless public_mode
         
     | 
| 
      
 114 
     | 
    
         
            +
                    else
         
     | 
| 
      
 115 
     | 
    
         
            +
                      member['name']
         
     | 
| 
      
 116 
     | 
    
         
            +
                    end
         
     | 
| 
      
 117 
     | 
    
         
            +
                  end.compact
         
     | 
| 
      
 118 
     | 
    
         
            +
                end
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
                def team_member_from_reference(reference)
         
     | 
| 
      
 121 
     | 
    
         
            +
                  key = (reference.instance_of? String) ? reference : (
         
     | 
| 
      
 122 
     | 
    
         
            +
                    reference['email'] || reference['github'])
         
     | 
| 
      
 123 
     | 
    
         
            +
                  team[key] || team[team_by_email[key] || team_by_github[key]]
         
     | 
| 
      
 124 
     | 
    
         
            +
                end
         
     | 
| 
      
 125 
     | 
    
         
            +
             
     | 
| 
      
 126 
     | 
    
         
            +
                SNIPPET_JOIN_FIELDS = %w(name full_name first_name last_name self)
         
     | 
| 
      
 127 
     | 
    
         
            +
             
     | 
| 
      
 128 
     | 
    
         
            +
                # Joins snippet data into +site.data[+'snippets'] and filters out snippets
         
     | 
| 
      
 129 
     | 
    
         
            +
                # from team members not appearing in +site.data[+'team'] or
         
     | 
| 
      
 130 
     | 
    
         
            +
                # +team_by_email+.
         
     | 
| 
      
 131 
     | 
    
         
            +
                def join_snippet_data
         
     | 
| 
      
 132 
     | 
    
         
            +
                  data['snippets'] = data['snippets'].map do |timestamp, snippets|
         
     | 
| 
      
 133 
     | 
    
         
            +
                    joined = snippets.map { |snippet| join_snippet snippet }
         
     | 
| 
      
 134 
     | 
    
         
            +
                      .compact.each { |i| i.delete 'username' }
         
     | 
| 
      
 135 
     | 
    
         
            +
                    [timestamp, joined] unless joined.empty?
         
     | 
| 
      
 136 
     | 
    
         
            +
                  end.compact.to_h
         
     | 
| 
      
 137 
     | 
    
         
            +
                end
         
     | 
| 
      
 138 
     | 
    
         
            +
             
     | 
| 
      
 139 
     | 
    
         
            +
                def join_snippet(snippet)
         
     | 
| 
      
 140 
     | 
    
         
            +
                  username = snippet['username']
         
     | 
| 
      
 141 
     | 
    
         
            +
                  member = team[username] || team[team_by_email[username]]
         
     | 
| 
      
 142 
     | 
    
         
            +
             
     | 
| 
      
 143 
     | 
    
         
            +
                  if member.nil?
         
     | 
| 
      
 144 
     | 
    
         
            +
                    fail UnknownSnippetUsernameError, username unless public_mode
         
     | 
| 
      
 145 
     | 
    
         
            +
                  else
         
     | 
| 
      
 146 
     | 
    
         
            +
                    member = member.select { |k, _| SNIPPET_JOIN_FIELDS.include? k }
         
     | 
| 
      
 147 
     | 
    
         
            +
                    snippet.merge member
         
     | 
| 
      
 148 
     | 
    
         
            +
                  end
         
     | 
| 
      
 149 
     | 
    
         
            +
                end
         
     | 
| 
      
 150 
     | 
    
         
            +
              end
         
     | 
| 
      
 151 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,24 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # @author Mike Bland (michael.bland@gsa.gov)
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require 'weekly_snippets/publisher'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module TeamApi
         
     | 
| 
      
 6 
     | 
    
         
            +
              class Snippets
         
     | 
| 
      
 7 
     | 
    
         
            +
                # Used to convert snippet headline markers to h4, since the layout uses
         
     | 
| 
      
 8 
     | 
    
         
            +
                # h3.
         
     | 
| 
      
 9 
     | 
    
         
            +
                HEADLINE = "\n####"
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                MARKDOWN_SNIPPET_MUNGER = proc do |text|
         
     | 
| 
      
 12 
     | 
    
         
            +
                  text.gsub!(/^::: (.*) :::$/, "#{HEADLINE} \\1") # For jtag. ;-)
         
     | 
| 
      
 13 
     | 
    
         
            +
                  text.gsub!(/^\*\*\*/, HEADLINE) # For elaine. ;-)
         
     | 
| 
      
 14 
     | 
    
         
            +
                end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                # TODO(mbland): Push this to the snippet import script.
         
     | 
| 
      
 17 
     | 
    
         
            +
                def self.publish(site)
         
     | 
| 
      
 18 
     | 
    
         
            +
                  publisher = ::WeeklySnippets::Publisher.new(
         
     | 
| 
      
 19 
     | 
    
         
            +
                    headline: HEADLINE, public_mode: site.config['public'],
         
     | 
| 
      
 20 
     | 
    
         
            +
                    markdown_snippet_munger: MARKDOWN_SNIPPET_MUNGER)
         
     | 
| 
      
 21 
     | 
    
         
            +
                  site.data['snippets'] = publisher.publish site.data['snippets']
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
              end
         
     | 
| 
      
 24 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/team_api.rb
    ADDED
    
    | 
         @@ -0,0 +1,11 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # @author Mike Bland (michael.bland@gsa.gov)
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require_relative 'team_api/api'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require_relative 'team_api/canonicalizer'
         
     | 
| 
      
 5 
     | 
    
         
            +
            require_relative 'team_api/config'
         
     | 
| 
      
 6 
     | 
    
         
            +
            require_relative 'team_api/cross_referencer'
         
     | 
| 
      
 7 
     | 
    
         
            +
            require_relative 'team_api/front_matter'
         
     | 
| 
      
 8 
     | 
    
         
            +
            require_relative 'team_api/generator'
         
     | 
| 
      
 9 
     | 
    
         
            +
            require_relative 'team_api/joiner'
         
     | 
| 
      
 10 
     | 
    
         
            +
            require_relative 'team_api/snippets'
         
     | 
| 
      
 11 
     | 
    
         
            +
            require_relative 'team_api/version'
         
     | 
    
        metadata
    ADDED
    
    | 
         @@ -0,0 +1,229 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            --- !ruby/object:Gem::Specification
         
     | 
| 
      
 2 
     | 
    
         
            +
            name: team_api
         
     | 
| 
      
 3 
     | 
    
         
            +
            version: !ruby/object:Gem::Version
         
     | 
| 
      
 4 
     | 
    
         
            +
              version: 0.0.0
         
     | 
| 
      
 5 
     | 
    
         
            +
            platform: ruby
         
     | 
| 
      
 6 
     | 
    
         
            +
            authors:
         
     | 
| 
      
 7 
     | 
    
         
            +
            - Mike Bland
         
     | 
| 
      
 8 
     | 
    
         
            +
            autorequire: 
         
     | 
| 
      
 9 
     | 
    
         
            +
            bindir: bin
         
     | 
| 
      
 10 
     | 
    
         
            +
            cert_chain: []
         
     | 
| 
      
 11 
     | 
    
         
            +
            date: 2015-08-30 00:00:00.000000000 Z
         
     | 
| 
      
 12 
     | 
    
         
            +
            dependencies:
         
     | 
| 
      
 13 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 14 
     | 
    
         
            +
              name: bundler
         
     | 
| 
      
 15 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 16 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 17 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 18 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 19 
     | 
    
         
            +
                    version: '1.10'
         
     | 
| 
      
 20 
     | 
    
         
            +
              type: :runtime
         
     | 
| 
      
 21 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 22 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 23 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 24 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 25 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 26 
     | 
    
         
            +
                    version: '1.10'
         
     | 
| 
      
 27 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 28 
     | 
    
         
            +
              name: safe_yaml
         
     | 
| 
      
 29 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 30 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 31 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 32 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 33 
     | 
    
         
            +
                    version: '1.0'
         
     | 
| 
      
 34 
     | 
    
         
            +
              type: :runtime
         
     | 
| 
      
 35 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 36 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 37 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 38 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 39 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 40 
     | 
    
         
            +
                    version: '1.0'
         
     | 
| 
      
 41 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 42 
     | 
    
         
            +
              name: jekyll
         
     | 
| 
      
 43 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 44 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 45 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 46 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 47 
     | 
    
         
            +
                    version: '0'
         
     | 
| 
      
 48 
     | 
    
         
            +
              type: :runtime
         
     | 
| 
      
 49 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 50 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 51 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 52 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 53 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 54 
     | 
    
         
            +
                    version: '0'
         
     | 
| 
      
 55 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 56 
     | 
    
         
            +
              name: weekly_snippets
         
     | 
| 
      
 57 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 58 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 59 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 60 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 61 
     | 
    
         
            +
                    version: '0'
         
     | 
| 
      
 62 
     | 
    
         
            +
              type: :runtime
         
     | 
| 
      
 63 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 64 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 65 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 66 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 67 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 68 
     | 
    
         
            +
                    version: '0'
         
     | 
| 
      
 69 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 70 
     | 
    
         
            +
              name: hash-joiner
         
     | 
| 
      
 71 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 72 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 73 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 74 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 75 
     | 
    
         
            +
                    version: '0'
         
     | 
| 
      
 76 
     | 
    
         
            +
              type: :runtime
         
     | 
| 
      
 77 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 78 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 79 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 80 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 81 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 82 
     | 
    
         
            +
                    version: '0'
         
     | 
| 
      
 83 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 84 
     | 
    
         
            +
              name: go_script
         
     | 
| 
      
 85 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 86 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 87 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 88 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 89 
     | 
    
         
            +
                    version: '0.1'
         
     | 
| 
      
 90 
     | 
    
         
            +
              type: :development
         
     | 
| 
      
 91 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 92 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 93 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 94 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 95 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 96 
     | 
    
         
            +
                    version: '0.1'
         
     | 
| 
      
 97 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 98 
     | 
    
         
            +
              name: rake
         
     | 
| 
      
 99 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 100 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 101 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 102 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 103 
     | 
    
         
            +
                    version: '10.4'
         
     | 
| 
      
 104 
     | 
    
         
            +
              type: :development
         
     | 
| 
      
 105 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 106 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 107 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 108 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 109 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 110 
     | 
    
         
            +
                    version: '10.4'
         
     | 
| 
      
 111 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 112 
     | 
    
         
            +
              name: minitest
         
     | 
| 
      
 113 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 114 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 115 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 116 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 117 
     | 
    
         
            +
                    version: '0'
         
     | 
| 
      
 118 
     | 
    
         
            +
              type: :development
         
     | 
| 
      
 119 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 120 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 121 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 122 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 123 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 124 
     | 
    
         
            +
                    version: '0'
         
     | 
| 
      
 125 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 126 
     | 
    
         
            +
              name: codeclimate-test-reporter
         
     | 
| 
      
 127 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 128 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 129 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 130 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 131 
     | 
    
         
            +
                    version: '0'
         
     | 
| 
      
 132 
     | 
    
         
            +
              type: :development
         
     | 
| 
      
 133 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 134 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 135 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 136 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 137 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 138 
     | 
    
         
            +
                    version: '0'
         
     | 
| 
      
 139 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 140 
     | 
    
         
            +
              name: coveralls
         
     | 
| 
      
 141 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 142 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 143 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 144 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 145 
     | 
    
         
            +
                    version: '0'
         
     | 
| 
      
 146 
     | 
    
         
            +
              type: :development
         
     | 
| 
      
 147 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 148 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 149 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 150 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 151 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 152 
     | 
    
         
            +
                    version: '0'
         
     | 
| 
      
 153 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 154 
     | 
    
         
            +
              name: rubocop
         
     | 
| 
      
 155 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 156 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 157 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 158 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 159 
     | 
    
         
            +
                    version: '0'
         
     | 
| 
      
 160 
     | 
    
         
            +
              type: :development
         
     | 
| 
      
 161 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 162 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 163 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 164 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 165 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 166 
     | 
    
         
            +
                    version: '0'
         
     | 
| 
      
 167 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 168 
     | 
    
         
            +
              name: about_yml
         
     | 
| 
      
 169 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 170 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 171 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 172 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 173 
     | 
    
         
            +
                    version: '0'
         
     | 
| 
      
 174 
     | 
    
         
            +
              type: :development
         
     | 
| 
      
 175 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 176 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 177 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 178 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 179 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 180 
     | 
    
         
            +
                    version: '0'
         
     | 
| 
      
 181 
     | 
    
         
            +
            description: Compiles information about team members, projects, etc. and exposes it
         
     | 
| 
      
 182 
     | 
    
         
            +
              via a JSON API.
         
     | 
| 
      
 183 
     | 
    
         
            +
            email:
         
     | 
| 
      
 184 
     | 
    
         
            +
            - michael.bland@gsa.gov
         
     | 
| 
      
 185 
     | 
    
         
            +
            executables: []
         
     | 
| 
      
 186 
     | 
    
         
            +
            extensions: []
         
     | 
| 
      
 187 
     | 
    
         
            +
            extra_rdoc_files: []
         
     | 
| 
      
 188 
     | 
    
         
            +
            files:
         
     | 
| 
      
 189 
     | 
    
         
            +
            - CONTRIBUTING.md
         
     | 
| 
      
 190 
     | 
    
         
            +
            - LICENSE.md
         
     | 
| 
      
 191 
     | 
    
         
            +
            - README.md
         
     | 
| 
      
 192 
     | 
    
         
            +
            - lib/team_api.rb
         
     | 
| 
      
 193 
     | 
    
         
            +
            - lib/team_api/README.md
         
     | 
| 
      
 194 
     | 
    
         
            +
            - lib/team_api/api.rb
         
     | 
| 
      
 195 
     | 
    
         
            +
            - lib/team_api/canonicalizer.rb
         
     | 
| 
      
 196 
     | 
    
         
            +
            - lib/team_api/config.rb
         
     | 
| 
      
 197 
     | 
    
         
            +
            - lib/team_api/cross_referencer.rb
         
     | 
| 
      
 198 
     | 
    
         
            +
            - lib/team_api/endpoints.yml
         
     | 
| 
      
 199 
     | 
    
         
            +
            - lib/team_api/front_matter.rb
         
     | 
| 
      
 200 
     | 
    
         
            +
            - lib/team_api/generator.rb
         
     | 
| 
      
 201 
     | 
    
         
            +
            - lib/team_api/joiner.rb
         
     | 
| 
      
 202 
     | 
    
         
            +
            - lib/team_api/snippets.rb
         
     | 
| 
      
 203 
     | 
    
         
            +
            - lib/team_api/tag_categories.yml
         
     | 
| 
      
 204 
     | 
    
         
            +
            - lib/team_api/version.rb
         
     | 
| 
      
 205 
     | 
    
         
            +
            homepage: https://github.com/18F/team_api
         
     | 
| 
      
 206 
     | 
    
         
            +
            licenses:
         
     | 
| 
      
 207 
     | 
    
         
            +
            - CC0
         
     | 
| 
      
 208 
     | 
    
         
            +
            metadata: {}
         
     | 
| 
      
 209 
     | 
    
         
            +
            post_install_message: 
         
     | 
| 
      
 210 
     | 
    
         
            +
            rdoc_options: []
         
     | 
| 
      
 211 
     | 
    
         
            +
            require_paths:
         
     | 
| 
      
 212 
     | 
    
         
            +
            - lib
         
     | 
| 
      
 213 
     | 
    
         
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         
     | 
| 
      
 214 
     | 
    
         
            +
              requirements:
         
     | 
| 
      
 215 
     | 
    
         
            +
              - - ">="
         
     | 
| 
      
 216 
     | 
    
         
            +
                - !ruby/object:Gem::Version
         
     | 
| 
      
 217 
     | 
    
         
            +
                  version: '0'
         
     | 
| 
      
 218 
     | 
    
         
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         
     | 
| 
      
 219 
     | 
    
         
            +
              requirements:
         
     | 
| 
      
 220 
     | 
    
         
            +
              - - ">="
         
     | 
| 
      
 221 
     | 
    
         
            +
                - !ruby/object:Gem::Version
         
     | 
| 
      
 222 
     | 
    
         
            +
                  version: '0'
         
     | 
| 
      
 223 
     | 
    
         
            +
            requirements: []
         
     | 
| 
      
 224 
     | 
    
         
            +
            rubyforge_project: 
         
     | 
| 
      
 225 
     | 
    
         
            +
            rubygems_version: 2.4.5.1
         
     | 
| 
      
 226 
     | 
    
         
            +
            signing_key: 
         
     | 
| 
      
 227 
     | 
    
         
            +
            specification_version: 4
         
     | 
| 
      
 228 
     | 
    
         
            +
            summary: Compiles team information and publishes it as a JSON API
         
     | 
| 
      
 229 
     | 
    
         
            +
            test_files: []
         
     |