slack_message 2.1.0 → 2.3.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 +4 -4
- data/.github/workflows/main.yml +27 -0
- data/CHANGELOG.md +21 -1
- data/Gemfile.lock +1 -1
- data/README.md +74 -25
- data/lib/slack_message/api.rb +43 -26
- data/lib/slack_message/dsl.rb +54 -23
- data/lib/slack_message/rspec.rb +7 -1
- data/lib/slack_message.rb +23 -10
- data/slack_message.gemspec +3 -1
- data/spec/slack_message_spec.rb +36 -26
- metadata +4 -3
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 4b476b987543e7a38fd316de55186ddee859dfc1bd71817b14ddd13b2cb79f59
         | 
| 4 | 
            +
              data.tar.gz: 80932a2d8e1bcac51239ea7724cd42f419dadfb7061fd939f359c2f4a5743777
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: c6273ff440e4aa2fd9c4dba40c7e45e4c3840d02d7619dafb11d516f9a3541172499e0e81f1966103a89332bb3911fb96818984fadc0195faa27c9b5e1692fcb
         | 
| 7 | 
            +
              data.tar.gz: fc1a7df622d0cfd0c10311a70478cba43b893d7cde189d4f55dd997196e544145f8ce855a4b82af920f4320314907a6fe91bb39fddf3ba5e9460d4b8a4cbb33a
         | 
| @@ -0,0 +1,27 @@ | |
| 1 | 
            +
            name: CI
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            on:
         | 
| 4 | 
            +
              push:
         | 
| 5 | 
            +
                branches: [ master ]
         | 
| 6 | 
            +
              pull_request:
         | 
| 7 | 
            +
                branches: [ master ]
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            jobs:
         | 
| 10 | 
            +
              test:
         | 
| 11 | 
            +
                strategy:
         | 
| 12 | 
            +
                  matrix:
         | 
| 13 | 
            +
                    os: [ubuntu-latest, macos-latest]
         | 
| 14 | 
            +
                    ruby-version: [3.0, 2.7, 2.6]
         | 
| 15 | 
            +
                runs-on: ${{ matrix.os }}
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                steps:
         | 
| 18 | 
            +
                  - uses: actions/checkout@v2
         | 
| 19 | 
            +
                  - name: Set up Ruby ${{ matrix.ruby-version }}
         | 
| 20 | 
            +
                    uses: ruby/setup-ruby@v1
         | 
| 21 | 
            +
                    with:
         | 
| 22 | 
            +
                      ruby-version: ${{ matrix.ruby-version }}
         | 
| 23 | 
            +
                      bundler-cache: true
         | 
| 24 | 
            +
                  - name: Install dependencies
         | 
| 25 | 
            +
                    run: bundle install
         | 
| 26 | 
            +
                  - name: Run tests
         | 
| 27 | 
            +
                    run: bundle exec rspec
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -1,6 +1,26 @@ | |
| 1 1 | 
             
            # Changelog
         | 
| 2 2 |  | 
| 3 | 
            -
            ## [ | 
| 3 | 
            +
            ## [2.3.0] - 2021-11-30
         | 
| 4 | 
            +
            - Formally require minimum version of ruby. It wouldn't have worked anyway,
         | 
| 5 | 
            +
              but worth actually specifying.
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            ## [2.2.2] - 2021-11-30
         | 
| 8 | 
            +
            - Add github workflow for automatic CI runs. Stolen from another project.
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            ## [2.2.1] - 2021-11-20
         | 
| 11 | 
            +
            - Trying to fetch user ID for a string that isn't email-like raises an error.
         | 
| 12 | 
            +
            - In tests, fetching user IDs is mocked out to prevent network requests.
         | 
| 13 | 
            +
            - Tightened up and clarified README.
         | 
| 14 | 
            +
            - Some internal cleanup and restructuring of modules.
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            ## [2.2.0] - 2021-11-20
         | 
| 17 | 
            +
            - When sending text, it is now possible to mention users and have their user
         | 
| 18 | 
            +
              IDs automatically converted using `<email@email.com>` within text nodes.
         | 
| 19 | 
            +
            - It's now possible to override notification text.
         | 
| 20 | 
            +
            - Errors received from the Slack API now raise `SlackMessage::ApiError`.
         | 
| 21 | 
            +
            - Re-exposed a top-level method for getting user IDs, `SlackMessage.user_id`.
         | 
| 22 | 
            +
            - Raising some better errors when no message payload is present.
         | 
| 23 | 
            +
            - Using `build` now requires a profile, so configuration must exist.
         | 
| 4 24 |  | 
| 5 25 | 
             
            ## [2.1.0] - 2021-11-01
         | 
| 6 26 | 
             
            - Change to use Slack Apps for all profiles. This should allow growth toward
         | 
    
        data/Gemfile.lock
    CHANGED
    
    
    
        data/README.md
    CHANGED
    
    | @@ -15,23 +15,25 @@ end | |
| 15 15 |  | 
| 16 16 | 
             
            To install, just add `slack_message` to your bundle and you're ready to go.
         | 
| 17 17 |  | 
| 18 | 
            -
            Opinionated Stances
         | 
| 19 | 
            -
            ------------
         | 
| 18 | 
            +
            #### Opinionated Stances
         | 
| 20 19 |  | 
| 21 20 | 
             
            Slack's API has a lot of options available to you! But this gem takes some
         | 
| 22 | 
            -
            opinionated stances  | 
| 21 | 
            +
            opinionated stances about usage to try to minimize the pain of integrating
         | 
| 22 | 
            +
            with it. For example:
         | 
| 23 23 |  | 
| 24 | 
            -
            *  | 
| 24 | 
            +
            * SlackMessage has no dependencies. Your lockfile is enough of a mess already.
         | 
| 25 | 
            +
            * The code to build a message should look a lot like the message itself. Code
         | 
| 26 | 
            +
              that is simple to read and understand is a priority.
         | 
| 25 27 | 
             
            * Webhooks are passé. Only Slack Apps are supported now.
         | 
| 26 28 | 
             
            * Unless you request otherwise, text is always rendered using `mrkdwn`. If you
         | 
| 27 29 | 
             
              want plaintext, you'll need to ask for it. Same for the `emoji` flag.
         | 
| 28 30 | 
             
            * As many API semantics as possible are hidden. For instance, if you post to
         | 
| 29 | 
            -
              something that looks like an email address, `slack_message` is going to try | 
| 30 | 
            -
              look it up as an email address.
         | 
| 31 | 
            +
              something that looks like an email address, `slack_message` is going to try
         | 
| 32 | 
            +
              to look it up as an email address.
         | 
| 31 33 | 
             
            * A few little hacks on the block syntax, such as adding a `blank_line` (which
         | 
| 32 34 | 
             
              doesn't exist in the API), or leading spaces.
         | 
| 33 | 
            -
            * Configuration  | 
| 34 | 
            -
              should  | 
| 35 | 
            +
            * Configuration is kept as simple as possible. But, as much heavy lifting as
         | 
| 36 | 
            +
              possible should occur just once via configuration and not on every call.
         | 
| 35 37 |  | 
| 36 38 | 
             
            Usage
         | 
| 37 39 | 
             
            ------------
         | 
| @@ -173,9 +175,7 @@ It's just as easy to send messages directly to users. SlackMessage will look for | |
| 173 175 | 
             
            targets that are email-addressish, and look them up for you automatically:
         | 
| 174 176 |  | 
| 175 177 | 
             
            ```ruby
         | 
| 176 | 
            -
             | 
| 177 | 
            -
             | 
| 178 | 
            -
            SlackMessage.post_to(user_email) do
         | 
| 178 | 
            +
            SlackMessage.post_to('hello@joemastey.com') do
         | 
| 179 179 | 
             
              text "You specifically did it! :thumbsup:"
         | 
| 180 180 | 
             
            end
         | 
| 181 181 | 
             
            ```
         | 
| @@ -207,15 +207,13 @@ SlackMessage.post_to('#general') do | |
| 207 207 | 
             
                text "See more here: #{link('result', 'https://google.com')}"
         | 
| 208 208 | 
             
              end
         | 
| 209 209 |  | 
| 210 | 
            -
              text ":rocketship: hello@joemastey.com"
         | 
| 210 | 
            +
              text ":rocketship: <hello@joemastey.com>"
         | 
| 211 211 |  | 
| 212 212 | 
             
              context ":custom_slack_emoji: An example footer *with some markdown*."
         | 
| 213 213 | 
             
            end
         | 
| 214 214 | 
             
            ```
         | 
| 215 215 |  | 
| 216 216 | 
             
            SlackMessage will compose this into Block Kit syntax and send it on its way!
         | 
| 217 | 
            -
            For now you'll need to read a bit of the source code to get the entire API. Sorry,
         | 
| 218 | 
            -
            working on it.
         | 
| 219 217 |  | 
| 220 218 | 
             
            If you've defined multiple profiles in configuration, you can specify which to
         | 
| 221 219 | 
             
            use for your message by specifying its name:
         | 
| @@ -223,6 +221,7 @@ use for your message by specifying its name: | |
| 223 221 | 
             
            ```ruby
         | 
| 224 222 | 
             
            SlackMessage.post_to('#general', as: :sidekiq_bot) do
         | 
| 225 223 | 
             
              text ":octagonal_sign: A job has failed permanently and needs to be rescued."
         | 
| 224 | 
            +
             | 
| 226 225 | 
             
              link_button "Sidekiq Dashboard", sidekiq_dashboard_url, style: :danger
         | 
| 227 226 | 
             
            end
         | 
| 228 227 | 
             
            ```
         | 
| @@ -238,6 +237,55 @@ SlackMessage.post_to('#general') do | |
| 238 237 | 
             
            end
         | 
| 239 238 | 
             
            ```
         | 
| 240 239 |  | 
| 240 | 
            +
            #### Notifying Users
         | 
| 241 | 
            +
             | 
| 242 | 
            +
            There are several supported ways to tag and notify users. Mentioned above, it's
         | 
| 243 | 
            +
            possible to DM a user by email:
         | 
| 244 | 
            +
             | 
| 245 | 
            +
            ```ruby
         | 
| 246 | 
            +
            SlackMessage.post_to('hello@joemastey.com') do
         | 
| 247 | 
            +
              text "Hi there!"
         | 
| 248 | 
            +
            end
         | 
| 249 | 
            +
            ```
         | 
| 250 | 
            +
             | 
| 251 | 
            +
            You can also mention a user by email within a channel by wrapping their name
         | 
| 252 | 
            +
            in tags:
         | 
| 253 | 
            +
             | 
| 254 | 
            +
            ```ruby
         | 
| 255 | 
            +
            SlackMessage.post_to('#general') do
         | 
| 256 | 
            +
              bot_name "CoffeeBot"
         | 
| 257 | 
            +
              bot_icon ":coffee:"
         | 
| 258 | 
            +
             | 
| 259 | 
            +
              text ":coffee: It's your turn to make coffee <hello@joemastey.com>."
         | 
| 260 | 
            +
            end
         | 
| 261 | 
            +
            ```
         | 
| 262 | 
            +
             | 
| 263 | 
            +
            Emails that are not wrapped in tags will be rendered as normal email addresses.
         | 
| 264 | 
            +
            Additionally, Slack will automatically convert a number of channel names and
         | 
| 265 | 
            +
            tags you're probably already used to:
         | 
| 266 | 
            +
             | 
| 267 | 
            +
            ```ruby
         | 
| 268 | 
            +
            SlackMessage.post_to('#general') do
         | 
| 269 | 
            +
              bot_name "CoffeeBot"
         | 
| 270 | 
            +
              bot_icon ":coffee:"
         | 
| 271 | 
            +
             | 
| 272 | 
            +
              text "@here There's no coffee left! Let #general know when you fix it."
         | 
| 273 | 
            +
            end
         | 
| 274 | 
            +
            ```
         | 
| 275 | 
            +
             | 
| 276 | 
            +
            By default, the desktop notification for a message will be the text of the 
         | 
| 277 | 
            +
            message itself. However, you can customize desktop notifications if you prefer:
         | 
| 278 | 
            +
             | 
| 279 | 
            +
            ```ruby
         | 
| 280 | 
            +
            SlackMessage.post_to('hello@joemastey.com') do
         | 
| 281 | 
            +
              bot_name "CoffeeBot"
         | 
| 282 | 
            +
              bot_icon ":coffee:"
         | 
| 283 | 
            +
             | 
| 284 | 
            +
              notification_text "It's a coffee emergency!"
         | 
| 285 | 
            +
              text "There's no coffee left!"
         | 
| 286 | 
            +
            end
         | 
| 287 | 
            +
            ```
         | 
| 288 | 
            +
             | 
| 241 289 | 
             
            ### Testing
         | 
| 242 290 |  | 
| 243 291 | 
             
            You can do some basic testing against SlackMessage, at least if you use RSpec!
         | 
| @@ -254,8 +302,8 @@ RSpec.configure do |config| | |
| 254 302 | 
             
            end
         | 
| 255 303 | 
             
            ```
         | 
| 256 304 |  | 
| 257 | 
            -
            This will  | 
| 258 | 
            -
            some custom matchers:
         | 
| 305 | 
            +
            This will prevent API calls from leaking in your tests, and will allow you
         | 
| 306 | 
            +
            access to some custom matchers:
         | 
| 259 307 |  | 
| 260 308 | 
             
            ```ruby
         | 
| 261 309 | 
             
            expect {
         | 
| @@ -291,23 +339,24 @@ of very complicated regexes. | |
| 291 339 | 
             
            What it Doesn't Do
         | 
| 292 340 | 
             
            ------------
         | 
| 293 341 |  | 
| 294 | 
            -
            This gem is intended to stay  | 
| 295 | 
            -
            options and abilities, which  | 
| 296 | 
            -
            you want to add a feature, open an issue on Github first to see if it's | 
| 297 | 
            -
            to be merged. This gem was built out of an existing need that _didn't_ | 
| 298 | 
            -
             | 
| 299 | 
            -
            expand the DSL to include more useful features.
         | 
| 342 | 
            +
            This gem is intended to stay simple. Other Slack gems have lots of config
         | 
| 343 | 
            +
            options and abilities, which makes them powerful, but makes them a pain to use.
         | 
| 344 | 
            +
            If you want to add a feature, open an issue on Github first to see if it's
         | 
| 345 | 
            +
            likely to be merged. This gem was built out of an existing need that _didn't_
         | 
| 346 | 
            +
            include all of the block API, but I'd be inclined to merge features that
         | 
| 347 | 
            +
            sustainably expand the DSL to include more useful features.
         | 
| 300 348 |  | 
| 301 349 | 
             
            Some behaviors that are still planned but not yet added:
         | 
| 302 350 |  | 
| 303 351 | 
             
            * some API documentation amirite?
         | 
| 304 | 
            -
            *  | 
| 352 | 
            +
            * custom http_options in configuration
         | 
| 305 353 | 
             
            * more of BlockKit's options
         | 
| 306 | 
            -
            * any interactive elements at all | 
| 354 | 
            +
            * any interactive elements at all
         | 
| 307 355 | 
             
            * editing / updating messages
         | 
| 308 356 | 
             
            * multiple recipients
         | 
| 309 | 
            -
            * more interesting return types for your message | 
| 357 | 
            +
            * more interesting return types for your message
         | 
| 310 358 | 
             
            * richer text formatting (for instance, `ul` is currently a hack)
         | 
| 359 | 
            +
            * more and better organized testing capability
         | 
| 311 360 |  | 
| 312 361 | 
             
            Contributing
         | 
| 313 362 | 
             
            ------------
         | 
    
        data/lib/slack_message/api.rb
    CHANGED
    
    | @@ -2,47 +2,51 @@ require 'net/http' | |
| 2 2 | 
             
            require 'net/https'
         | 
| 3 3 | 
             
            require 'json'
         | 
| 4 4 |  | 
| 5 | 
            -
             | 
| 6 | 
            -
               | 
| 7 | 
            -
                uri = URI("https://slack.com/api/users.lookupByEmail?email=#{email}")
         | 
| 8 | 
            -
                request = Net::HTTP::Get.new(uri).tap do |req|
         | 
| 9 | 
            -
                  req['Authorization']  = "Bearer #{profile[:api_token]}"
         | 
| 10 | 
            -
                  req['Content-type']   = "application/json; charset=utf-8"
         | 
| 11 | 
            -
                end
         | 
| 5 | 
            +
            module SlackMessage::Api
         | 
| 6 | 
            +
              extend self
         | 
| 12 7 |  | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 8 | 
            +
              def user_id_for(email, profile)
         | 
| 9 | 
            +
                unless email =~ SlackMessage::EMAIL_PATTERN
         | 
| 10 | 
            +
                  raise ArgumentError, "Tried to find profile by invalid email address '#{email}'"
         | 
| 15 11 | 
             
                end
         | 
| 16 12 |  | 
| 13 | 
            +
                response = look_up_user_by_email(email, profile)
         | 
| 14 | 
            +
             | 
| 17 15 | 
             
                if response.code != "200"
         | 
| 18 | 
            -
                  raise "Got an error back from the Slack API (HTTP #{response.code}):\n#{response.body}"
         | 
| 16 | 
            +
                  raise SlackMessage::ApiError, "Got an error back from the Slack API (HTTP #{response.code}):\n#{response.body}"
         | 
| 19 17 | 
             
                elsif response.body == ""
         | 
| 20 | 
            -
                  raise "Received empty 200 response from Slack when looking up user info. Check your API key."
         | 
| 18 | 
            +
                  raise SlackMessage::ApiError, "Received empty 200 response from Slack when looking up user info. Check your API key."
         | 
| 21 19 | 
             
                end
         | 
| 22 20 |  | 
| 23 21 | 
             
                begin
         | 
| 24 22 | 
             
                  payload = JSON.parse(response.body)
         | 
| 25 23 | 
             
                rescue
         | 
| 26 | 
            -
                  raise "Unable to parse JSON response from Slack API\n#{response.body}"
         | 
| 24 | 
            +
                  raise SlackMessage::ApiError, "Unable to parse JSON response from Slack API\n#{response.body}"
         | 
| 27 25 | 
             
                end
         | 
| 28 26 |  | 
| 29 27 | 
             
                if payload.include?("error") && payload["error"] == "invalid_auth"
         | 
| 30 | 
            -
                  raise "Received an error because your authentication token isn't properly configured | 
| 28 | 
            +
                  raise SlackMessage::ApiError, "Received an error because your authentication token isn't properly configured."
         | 
| 29 | 
            +
                elsif payload.include?("error") && payload["error"] == "users_not_found"
         | 
| 30 | 
            +
                  raise SlackMessage::ApiError, "Couldn't find a user with the email '#{email}'."
         | 
| 31 31 | 
             
                elsif payload.include?("error")
         | 
| 32 | 
            -
                  raise "Received error response from Slack during user lookup:\n#{response.body}"
         | 
| 32 | 
            +
                  raise SlackMessage::ApiError, "Received error response from Slack during user lookup:\n#{response.body}"
         | 
| 33 33 | 
             
                end
         | 
| 34 34 |  | 
| 35 35 | 
             
                payload["user"]["id"]
         | 
| 36 36 | 
             
              end
         | 
| 37 37 |  | 
| 38 | 
            -
              def  | 
| 38 | 
            +
              def post(payload, target, profile)
         | 
| 39 39 | 
             
                params  = {
         | 
| 40 40 | 
             
                  channel: target,
         | 
| 41 41 | 
             
                  username: payload.custom_bot_name || profile[:name],
         | 
| 42 42 | 
             
                  blocks: payload.render,
         | 
| 43 | 
            -
                  text: payload. | 
| 43 | 
            +
                  text: payload.custom_notification,
         | 
| 44 44 | 
             
                }
         | 
| 45 45 |  | 
| 46 | 
            +
                if params[:blocks].length == 0
         | 
| 47 | 
            +
                  raise ArgumentError, "Tried to send an entirely empty message."
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
             | 
| 46 50 | 
             
                icon = payload.custom_bot_icon || profile[:icon]
         | 
| 47 51 | 
             
                if icon =~ /^:\w+:$/
         | 
| 48 52 | 
             
                  params[:icon_emoji] = icon
         | 
| @@ -58,24 +62,39 @@ class SlackMessage::Api | |
| 58 62 |  | 
| 59 63 | 
             
                # let's try to be helpful about error messages
         | 
| 60 64 | 
             
                if ["token_revoked", "token_expired", "invalid_auth", "not_authed"].include?(error)
         | 
| 61 | 
            -
                  raise "Couldn't send slack message because the API key for profile '#{profile[:handle]}' is wrong."
         | 
| 65 | 
            +
                  raise SlackMessage::ApiError, "Couldn't send slack message because the API key for profile '#{profile[:handle]}' is wrong."
         | 
| 62 66 | 
             
                elsif ["no_permission", "ekm_access_denied"].include?(error)
         | 
| 63 | 
            -
                  raise "Couldn't send slack message because the API key for profile '#{profile[:handle]}' isn't allowed to post messages."
         | 
| 67 | 
            +
                  raise SlackMessage::ApiError, "Couldn't send slack message because the API key for profile '#{profile[:handle]}' isn't allowed to post messages."
         | 
| 64 68 | 
             
                elsif error == "channel_not_found"
         | 
| 65 | 
            -
                  raise "Tried to send Slack message to non-existent channel or user '#{target}'"
         | 
| 69 | 
            +
                  raise SlackMessage::ApiError, "Tried to send Slack message to non-existent channel or user '#{target}'"
         | 
| 66 70 | 
             
                elsif error == "invalid_arguments"
         | 
| 67 | 
            -
                  raise "Tried to send Slack message with invalid payload."
         | 
| 71 | 
            +
                  raise SlackMessage::ApiError, "Tried to send Slack message with invalid payload."
         | 
| 68 72 | 
             
                elsif response.code == "302"
         | 
| 69 | 
            -
                  raise "Got 302 response while posting to Slack. Check your API key for profile '#{profile[:handle]}'."
         | 
| 73 | 
            +
                  raise SlackMessage::ApiError, "Got 302 response while posting to Slack. Check your API key for profile '#{profile[:handle]}'."
         | 
| 70 74 | 
             
                elsif response.code != "200"
         | 
| 71 | 
            -
                  raise "Got an error back from the Slack API (HTTP #{response.code}):\n#{response.body}"
         | 
| 75 | 
            +
                  raise SlackMessage::ApiError, "Got an error back from the Slack API (HTTP #{response.code}):\n#{response.body}"
         | 
| 72 76 | 
             
                end
         | 
| 73 77 |  | 
| 74 78 | 
             
                response
         | 
| 75 79 | 
             
              end
         | 
| 76 80 |  | 
| 77 | 
            -
               | 
| 78 | 
            -
             | 
| 81 | 
            +
              private
         | 
| 82 | 
            +
             | 
| 83 | 
            +
              # mostly for test harnesses
         | 
| 84 | 
            +
             | 
| 85 | 
            +
              def look_up_user_by_email(email, profile)
         | 
| 86 | 
            +
                uri = URI("https://slack.com/api/users.lookupByEmail?email=#{email}")
         | 
| 87 | 
            +
                request = Net::HTTP::Get.new(uri).tap do |req|
         | 
| 88 | 
            +
                  req['Authorization']  = "Bearer #{profile[:api_token]}"
         | 
| 89 | 
            +
                  req['Content-type']   = "application/json; charset=utf-8"
         | 
| 90 | 
            +
                end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
         | 
| 93 | 
            +
                  http.request(request)
         | 
| 94 | 
            +
                end
         | 
| 95 | 
            +
              end
         | 
| 96 | 
            +
             | 
| 97 | 
            +
              def post_message(profile, params)
         | 
| 79 98 | 
             
                uri = URI("https://slack.com/api/chat.postMessage")
         | 
| 80 99 | 
             
                request = Net::HTTP::Post.new(uri).tap do |req|
         | 
| 81 100 | 
             
                  req['Authorization']  = "Bearer #{profile[:api_token]}"
         | 
| @@ -87,6 +106,4 @@ class SlackMessage::Api | |
| 87 106 | 
             
                  http.request(request)
         | 
| 88 107 | 
             
                end
         | 
| 89 108 | 
             
              end
         | 
| 90 | 
            -
             | 
| 91 | 
            -
              private_class_method :post_message
         | 
| 92 109 | 
             
            end
         | 
    
        data/lib/slack_message/dsl.rb
    CHANGED
    
    | @@ -1,19 +1,21 @@ | |
| 1 1 | 
             
            class SlackMessage::Dsl
         | 
| 2 | 
            -
              attr_reader :body, :default_section, :custom_bot_name, :custom_bot_icon
         | 
| 3 | 
            -
              attr_accessor : | 
| 2 | 
            +
              attr_reader :body, :default_section, :custom_bot_name, :custom_bot_icon, :profile
         | 
| 3 | 
            +
              attr_accessor :custom_notification
         | 
| 4 4 |  | 
| 5 | 
            -
              EMSPACE = " " # unicode emspace
         | 
| 5 | 
            +
              EMSPACE = " " # unicode emspace, Slack won't compress this
         | 
| 6 6 |  | 
| 7 | 
            -
              def initialize(block)
         | 
| 7 | 
            +
              def initialize(block, profile)
         | 
| 8 8 | 
             
                # Delegate missing methods to caller scope. Thanks 2008:
         | 
| 9 9 | 
             
                # https://www.dan-manges.com/blog/ruby-dsls-instance-eval-with-delegation
         | 
| 10 10 | 
             
                @caller_self = eval("self", block.binding)
         | 
| 11 11 |  | 
| 12 | 
            -
                @body | 
| 13 | 
            -
                @ | 
| 14 | 
            -
                @ | 
| 15 | 
            -
             | 
| 16 | 
            -
                @ | 
| 12 | 
            +
                @body             = []
         | 
| 13 | 
            +
                @profile          = profile
         | 
| 14 | 
            +
                @default_section  = Section.new(self)
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                @custom_bot_name      = nil
         | 
| 17 | 
            +
                @custom_bot_icon      = nil
         | 
| 18 | 
            +
                @custom_notification  = nil
         | 
| 17 19 | 
             
              end
         | 
| 18 20 |  | 
| 19 21 | 
             
              # allowable top-level entities within a block
         | 
| @@ -56,9 +58,11 @@ class SlackMessage::Dsl | |
| 56 58 | 
             
                finalize_default_section
         | 
| 57 59 |  | 
| 58 60 | 
             
                if text == "" || text.nil?
         | 
| 59 | 
            -
                  raise ArgumentError, " | 
| 61 | 
            +
                  raise ArgumentError, "Cannot create a context block without a value."
         | 
| 60 62 | 
             
                end
         | 
| 61 63 |  | 
| 64 | 
            +
                text = self.enrich_text(text)
         | 
| 65 | 
            +
             | 
| 62 66 | 
             
                @body.push({ type: "context", elements: [{
         | 
| 63 67 | 
             
                  type: "mrkdwn", text: text
         | 
| 64 68 | 
             
                }]})
         | 
| @@ -79,7 +83,7 @@ class SlackMessage::Dsl | |
| 79 83 |  | 
| 80 84 | 
             
              # end delegation
         | 
| 81 85 |  | 
| 82 | 
            -
              #  | 
| 86 | 
            +
              # bot / notification overrides
         | 
| 83 87 |  | 
| 84 88 | 
             
              def bot_name(name)
         | 
| 85 89 | 
             
                @custom_bot_name = name
         | 
| @@ -89,6 +93,10 @@ class SlackMessage::Dsl | |
| 89 93 | 
             
                @custom_bot_icon = icon
         | 
| 90 94 | 
             
              end
         | 
| 91 95 |  | 
| 96 | 
            +
              def notification_text(msg)
         | 
| 97 | 
            +
                @custom_notification = msg
         | 
| 98 | 
            +
              end
         | 
| 99 | 
            +
             | 
| 92 100 | 
             
              # end bot name
         | 
| 93 101 |  | 
| 94 102 | 
             
              def render
         | 
| @@ -100,6 +108,20 @@ class SlackMessage::Dsl | |
| 100 108 | 
             
                @caller_self.send meth, *args, &blk
         | 
| 101 109 | 
             
              end
         | 
| 102 110 |  | 
| 111 | 
            +
              # replace emails w/ real user IDs
         | 
| 112 | 
            +
              def enrich_text(text_body)
         | 
| 113 | 
            +
                text_body.scan(SlackMessage::EMAIL_TAG_PATTERN).each do |email_tag|
         | 
| 114 | 
            +
                  raw_email = email_tag.gsub(/[><]/, '')
         | 
| 115 | 
            +
                  user_id   = SlackMessage::Api::user_id_for(raw_email, profile)
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                  text_body.gsub!(email_tag, "<@#{user_id}>") if user_id
         | 
| 118 | 
            +
                rescue SlackMessage::ApiError => e
         | 
| 119 | 
            +
                  # swallow errors for not-found users
         | 
| 120 | 
            +
                end
         | 
| 121 | 
            +
             | 
| 122 | 
            +
                text_body
         | 
| 123 | 
            +
              end
         | 
| 124 | 
            +
             | 
| 103 125 | 
             
              private
         | 
| 104 126 |  | 
| 105 127 | 
             
              # when doing things that would generate new top-levels, first try
         | 
| @@ -123,9 +145,11 @@ class SlackMessage::Dsl | |
| 123 145 |  | 
| 124 146 | 
             
                def text(msg)
         | 
| 125 147 | 
             
                  if msg == "" || msg.nil?
         | 
| 126 | 
            -
                    raise ArgumentError, " | 
| 148 | 
            +
                    raise ArgumentError, "Cannot create a text node without a value."
         | 
| 127 149 | 
             
                  end
         | 
| 128 150 |  | 
| 151 | 
            +
                  msg = @parent.enrich_text(msg)
         | 
| 152 | 
            +
             | 
| 129 153 | 
             
                  if @body.include?(:text)
         | 
| 130 154 | 
             
                    @body[:text][:text] << "\n#{msg}"
         | 
| 131 155 |  | 
| @@ -135,19 +159,21 @@ class SlackMessage::Dsl | |
| 135 159 | 
             
                end
         | 
| 136 160 |  | 
| 137 161 | 
             
                def ul(elements)
         | 
| 138 | 
            -
                  raise ArgumentError, " | 
| 162 | 
            +
                  raise ArgumentError, "Please pass an array when creating a ul." unless elements.respond_to?(:map)
         | 
| 163 | 
            +
             | 
| 164 | 
            +
                  msg = elements.map { |text| "#{EMSPACE}• #{text}" }.join("\n")
         | 
| 165 | 
            +
                  msg = @parent.enrich_text(msg)
         | 
| 139 166 |  | 
| 140 | 
            -
                  text(
         | 
| 141 | 
            -
                    elements.map { |text| "#{EMSPACE}• #{text}" }.join("\n")
         | 
| 142 | 
            -
                  )
         | 
| 167 | 
            +
                  text(msg)
         | 
| 143 168 | 
             
                end
         | 
| 144 169 |  | 
| 145 170 | 
             
                def ol(elements)
         | 
| 146 | 
            -
                  raise ArgumentError, " | 
| 171 | 
            +
                  raise ArgumentError, "Please pass an array when creating an ol." unless elements.respond_to?(:map)
         | 
| 147 172 |  | 
| 148 | 
            -
                  text(
         | 
| 149 | 
            -
             | 
| 150 | 
            -
             | 
| 173 | 
            +
                  msg = elements.map.with_index(1) { |text, idx| "#{EMSPACE}#{idx}. #{text}" }.join("\n")
         | 
| 174 | 
            +
                  msg = @parent.enrich_text(msg)
         | 
| 175 | 
            +
             | 
| 176 | 
            +
                  text(msg)
         | 
| 151 177 | 
             
                end
         | 
| 152 178 |  | 
| 153 179 | 
             
                # styles:  default, primary, danger
         | 
| @@ -205,9 +231,10 @@ class SlackMessage::Dsl | |
| 205 231 |  | 
| 206 232 | 
             
                def list_item(title, value)
         | 
| 207 233 | 
             
                  if value == "" || value.nil?
         | 
| 208 | 
            -
                    raise ArgumentError, " | 
| 234 | 
            +
                    raise ArgumentError, "Can't create a list item for '#{title}' without a value."
         | 
| 209 235 | 
             
                  end
         | 
| 210 236 |  | 
| 237 | 
            +
                  value = @parent.enrich_text(value)
         | 
| 211 238 | 
             
                  @list.add(title, value)
         | 
| 212 239 | 
             
                end
         | 
| 213 240 |  | 
| @@ -220,10 +247,14 @@ class SlackMessage::Dsl | |
| 220 247 | 
             
                end
         | 
| 221 248 |  | 
| 222 249 | 
             
                def render
         | 
| 250 | 
            +
                  unless has_content?
         | 
| 251 | 
            +
                    raise ArgumentError, "Can't create a section with no content."
         | 
| 252 | 
            +
                  end
         | 
| 253 | 
            +
             | 
| 223 254 | 
             
                  body[:fields] = @list.render if @list.any?
         | 
| 224 255 |  | 
| 225 | 
            -
                  if body[:text] && body[:text][:text] && !@parent. | 
| 226 | 
            -
                    @parent.notification_text | 
| 256 | 
            +
                  if body[:text] && body[:text][:text] && !@parent.custom_notification
         | 
| 257 | 
            +
                    @parent.notification_text(body[:text][:text])
         | 
| 227 258 | 
             
                  end
         | 
| 228 259 |  | 
| 229 260 | 
             
                  body
         | 
    
        data/lib/slack_message/rspec.rb
    CHANGED
    
    | @@ -30,7 +30,7 @@ module SlackMessage::RSpec | |
| 30 30 | 
             
              FauxResponse = Struct.new(:code, :body)
         | 
| 31 31 |  | 
| 32 32 | 
             
              def self.included(_)
         | 
| 33 | 
            -
                SlackMessage::Api. | 
| 33 | 
            +
                SlackMessage::Api.undef_method(:post_message)
         | 
| 34 34 | 
             
                SlackMessage::Api.define_singleton_method(:post_message) do |profile, params|
         | 
| 35 35 | 
             
                  @@listeners.each do |listener|
         | 
| 36 36 | 
             
                    listener.record_call(params.merge(profile: profile))
         | 
| @@ -53,6 +53,12 @@ module SlackMessage::RSpec | |
| 53 53 |  | 
| 54 54 | 
             
                  return FauxResponse.new('200', response.to_json)
         | 
| 55 55 | 
             
                end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                SlackMessage::Api.undef_method(:look_up_user_by_email)
         | 
| 58 | 
            +
                SlackMessage::Api.define_singleton_method(:look_up_user_by_email) do |email, profile|
         | 
| 59 | 
            +
                  response = {"ok"=>true, "user"=>{"id"=>"U5432CBA"}}
         | 
| 60 | 
            +
                  return FauxResponse.new('200', response.to_json)
         | 
| 61 | 
            +
                end
         | 
| 56 62 | 
             
              end
         | 
| 57 63 |  | 
| 58 64 | 
             
              # w/ channel
         | 
    
        data/lib/slack_message.rb
    CHANGED
    
    | @@ -3,6 +3,11 @@ module SlackMessage | |
| 3 3 | 
             
              require 'slack_message/api'
         | 
| 4 4 | 
             
              require 'slack_message/configuration'
         | 
| 5 5 |  | 
| 6 | 
            +
              EMAIL_TAG_PATTERN = /<[^@ \t\r\n\<]+@[^@ \t\r\n]+\.[^@ \t\r\n]+>/
         | 
| 7 | 
            +
              EMAIL_PATTERN = /^\S{1,}@\S{2,}\.\S{2,}$/
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              class ApiError < RuntimeError; end
         | 
| 10 | 
            +
             | 
| 6 11 | 
             
              def self.configuration
         | 
| 7 12 | 
             
                Configuration
         | 
| 8 13 | 
             
              end
         | 
| @@ -11,35 +16,43 @@ module SlackMessage | |
| 11 16 | 
             
                configuration.configure(&block)
         | 
| 12 17 | 
             
              end
         | 
| 13 18 |  | 
| 19 | 
            +
              def self.user_id(email, profile_name = :default)
         | 
| 20 | 
            +
                profile = Configuration.profile(profile_name)
         | 
| 21 | 
            +
                Api.user_id_for(email, profile)
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
             | 
| 14 24 | 
             
              def self.post_to(target, as: :default, &block)
         | 
| 15 | 
            -
                 | 
| 25 | 
            +
                profile = Configuration.profile(as)
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                payload = Dsl.new(block, profile).tap do |instance|
         | 
| 16 28 | 
             
                  instance.instance_eval(&block)
         | 
| 17 29 | 
             
                end
         | 
| 18 30 |  | 
| 19 | 
            -
                 | 
| 20 | 
            -
                target  = Api::user_id_for(target, profile) if target =~ /^\S{1,}@\S{2,}\.\S{2,}$/
         | 
| 31 | 
            +
                target  = Api::user_id_for(target, profile) if target =~ EMAIL_PATTERN
         | 
| 21 32 |  | 
| 22 33 | 
             
                Api.post(payload, target, profile)
         | 
| 23 34 | 
             
              end
         | 
| 24 35 |  | 
| 25 36 | 
             
              def self.post_as(profile_name, &block)
         | 
| 26 | 
            -
                payload = Dsl.new(block).tap do |instance|
         | 
| 27 | 
            -
                  instance.instance_eval(&block)
         | 
| 28 | 
            -
                end
         | 
| 29 | 
            -
             | 
| 30 37 | 
             
                profile = Configuration.profile(profile_name)
         | 
| 31 38 | 
             
                if profile[:default_channel].nil?
         | 
| 32 39 | 
             
                  raise ArgumentError, "Sorry, you need to specify a default_channel for profile #{profile_name} to use post_as"
         | 
| 33 40 | 
             
                end
         | 
| 34 41 |  | 
| 42 | 
            +
                payload = Dsl.new(block, profile).tap do |instance|
         | 
| 43 | 
            +
                  instance.instance_eval(&block)
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 35 46 | 
             
                target  = profile[:default_channel]
         | 
| 36 | 
            -
                target  = Api::user_id_for(target, profile) if target =~  | 
| 47 | 
            +
                target  = Api::user_id_for(target, profile) if target =~ EMAIL_PATTERN
         | 
| 37 48 |  | 
| 38 49 | 
             
                Api.post(payload, target, profile)
         | 
| 39 50 | 
             
              end
         | 
| 40 51 |  | 
| 41 | 
            -
              def self.build(&block)
         | 
| 42 | 
            -
                 | 
| 52 | 
            +
              def self.build(profile_name = :default, &block)
         | 
| 53 | 
            +
                profile = Configuration.profile(profile_name)
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                Dsl.new(block, profile).tap do |instance|
         | 
| 43 56 | 
             
                  instance.instance_eval(&block)
         | 
| 44 57 | 
             
                end.send(:render)
         | 
| 45 58 | 
             
              end
         | 
    
        data/slack_message.gemspec
    CHANGED
    
    | @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            Gem::Specification.new do |gem|
         | 
| 2 2 | 
             
              gem.name        = 'slack_message'
         | 
| 3 | 
            -
              gem.version     = "2. | 
| 3 | 
            +
              gem.version     = "2.3.0"
         | 
| 4 4 | 
             
              gem.summary     = "A nice DSL for composing rich messages in Slack"
         | 
| 5 5 | 
             
              gem.authors     = ["Joe Mastey"]
         | 
| 6 6 | 
             
              gem.email       = 'hello@joemastey.com'
         | 
| @@ -18,6 +18,8 @@ Gem::Specification.new do |gem| | |
| 18 18 | 
             
                "source_code_uri" => "http://github.com/jmmastey/slack_message",
         | 
| 19 19 | 
             
              }
         | 
| 20 20 |  | 
| 21 | 
            +
              gem.required_ruby_version = '>= 2.6.0'
         | 
| 22 | 
            +
             | 
| 21 23 | 
             
              gem.add_development_dependency "rspec", "3.10.0"
         | 
| 22 24 | 
             
              gem.add_development_dependency "pry", "0.14.1"
         | 
| 23 25 | 
             
              gem.add_development_dependency "rb-readline", "0.5.5"
         | 
    
        data/spec/slack_message_spec.rb
    CHANGED
    
    | @@ -1,31 +1,14 @@ | |
| 1 1 | 
             
            require 'spec_helper'
         | 
| 2 2 |  | 
| 3 3 | 
             
            RSpec.describe SlackMessage do
         | 
| 4 | 
            -
              describe "API convenience" do
         | 
| 5 | 
            -
                before do
         | 
| 6 | 
            -
                  SlackMessage.configure do |config|
         | 
| 7 | 
            -
                    config.add_profile(name: 'default profile', api_token: 'abc123')
         | 
| 8 | 
            -
                  end
         | 
| 9 | 
            -
                end
         | 
| 10 | 
            -
             | 
| 11 | 
            -
                after do
         | 
| 12 | 
            -
                  SlackMessage.configuration.reset
         | 
| 13 | 
            -
                end
         | 
| 14 | 
            -
             | 
| 15 | 
            -
                it "can grab user IDs" do
         | 
| 16 | 
            -
                  profile = SlackMessage::Configuration.profile(:default)
         | 
| 17 | 
            -
                  allow(Net::HTTP).to receive(:start).and_return(
         | 
| 18 | 
            -
                    double(code: "200", body: '{ "user": { "id": "ABC123" }}')
         | 
| 19 | 
            -
                  )
         | 
| 20 | 
            -
             | 
| 21 | 
            -
                  result = SlackMessage::Api.user_id_for("hello@joemastey.com", profile)
         | 
| 22 | 
            -
                  expect(result).to eq("ABC123")
         | 
| 23 | 
            -
                end
         | 
| 24 | 
            -
              end
         | 
| 25 | 
            -
             | 
| 26 4 | 
             
              describe "DSL" do
         | 
| 27 5 | 
             
                describe "#build" do
         | 
| 28 6 | 
             
                  it "renders some JSON" do
         | 
| 7 | 
            +
                    SlackMessage.configure do |config|
         | 
| 8 | 
            +
                      config.clear_profiles!
         | 
| 9 | 
            +
                      config.add_profile(name: 'default profile', api_token: 'abc123')
         | 
| 10 | 
            +
                    end
         | 
| 11 | 
            +
             | 
| 29 12 | 
             
                    expected_output = [
         | 
| 30 13 | 
             
                      { type: "section",
         | 
| 31 14 | 
             
                        text: { text: "foo", type: "mrkdwn" }
         | 
| @@ -42,12 +25,9 @@ RSpec.describe SlackMessage do | |
| 42 25 | 
             
              end
         | 
| 43 26 |  | 
| 44 27 | 
             
              describe "configuration" do
         | 
| 45 | 
            -
                after do
         | 
| 46 | 
            -
                  SlackMessage.configuration.reset
         | 
| 47 | 
            -
                end
         | 
| 48 | 
            -
             | 
| 49 28 | 
             
                it "lets you add and fetch profiles" do
         | 
| 50 29 | 
             
                  SlackMessage.configure do |config|
         | 
| 30 | 
            +
                    config.clear_profiles!
         | 
| 51 31 | 
             
                    config.add_profile(name: 'default profile', api_token: 'abc123')
         | 
| 52 32 | 
             
                    config.add_profile(:nonstandard, name: 'another profile', api_token: 'abc123')
         | 
| 53 33 | 
             
                  end
         | 
| @@ -141,4 +121,34 @@ RSpec.describe SlackMessage do | |
| 141 121 | 
             
                  }.to post_to_slack.with_content_matching(/foo/)
         | 
| 142 122 | 
             
                end
         | 
| 143 123 | 
             
              end
         | 
| 124 | 
            +
             | 
| 125 | 
            +
              describe "API convenience" do
         | 
| 126 | 
            +
                let(:profile) { SlackMessage::Configuration.profile(:default) }
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                before do
         | 
| 129 | 
            +
                  SlackMessage.configure do |config|
         | 
| 130 | 
            +
                    config.clear_profiles!
         | 
| 131 | 
            +
                    config.add_profile(name: 'default profile', api_token: 'abc123')
         | 
| 132 | 
            +
                  end
         | 
| 133 | 
            +
                end
         | 
| 134 | 
            +
             | 
| 135 | 
            +
                it "converts user IDs within text when tagged properly" do
         | 
| 136 | 
            +
                  allow(SlackMessage::Api).to receive(:user_id_for).and_return('ABC123')
         | 
| 137 | 
            +
             | 
| 138 | 
            +
                  expect {
         | 
| 139 | 
            +
                    SlackMessage.post_to('#general') { text("Working: <hello@joemastey.com> ") }
         | 
| 140 | 
            +
                  }.to post_to_slack.with_content_matching(/ABC123/)
         | 
| 141 | 
            +
             | 
| 142 | 
            +
                  expect {
         | 
| 143 | 
            +
                    SlackMessage.post_to('#general') { text("Not Tagged: hello@joemastey.com ") }
         | 
| 144 | 
            +
                  }.to post_to_slack.with_content_matching(/hello@joemastey.com/)
         | 
| 145 | 
            +
             | 
| 146 | 
            +
             | 
| 147 | 
            +
                  allow(SlackMessage::Api).to receive(:user_id_for).and_raise(SlackMessage::ApiError)
         | 
| 148 | 
            +
             | 
| 149 | 
            +
                  expect {
         | 
| 150 | 
            +
                    SlackMessage.post_to('#general') { text("Not User: <nuffin@nuffin.nuffin>") }
         | 
| 151 | 
            +
                  }.to post_to_slack.with_content_matching(/\<nuffin@nuffin.nuffin\>/)
         | 
| 152 | 
            +
                end
         | 
| 153 | 
            +
              end
         | 
| 144 154 | 
             
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: slack_message
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 2. | 
| 4 | 
            +
              version: 2.3.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Joe Mastey
         | 
| 8 8 | 
             
            autorequire:
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2021-11- | 
| 11 | 
            +
            date: 2021-11-30 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: rspec
         | 
| @@ -58,6 +58,7 @@ executables: [] | |
| 58 58 | 
             
            extensions: []
         | 
| 59 59 | 
             
            extra_rdoc_files: []
         | 
| 60 60 | 
             
            files:
         | 
| 61 | 
            +
            - ".github/workflows/main.yml"
         | 
| 61 62 | 
             
            - ".gitignore"
         | 
| 62 63 | 
             
            - ".ruby-version"
         | 
| 63 64 | 
             
            - CHANGELOG.md
         | 
| @@ -89,7 +90,7 @@ required_ruby_version: !ruby/object:Gem::Requirement | |
| 89 90 | 
             
              requirements:
         | 
| 90 91 | 
             
              - - ">="
         | 
| 91 92 | 
             
                - !ruby/object:Gem::Version
         | 
| 92 | 
            -
                  version:  | 
| 93 | 
            +
                  version: 2.6.0
         | 
| 93 94 | 
             
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 94 95 | 
             
              requirements:
         | 
| 95 96 | 
             
              - - ">="
         |