├── .rspec ├── .gitignore ├── lib ├── grape-raketasks │ ├── version.rb │ ├── tasks.rb │ ├── railtie.rb │ ├── route.rb │ ├── processor.rb │ └── console_formatter.rb ├── grape-raketasks.rb └── tasks │ └── grape-raketasks.rake ├── Gemfile ├── spec ├── spec_helper.rb ├── support │ └── test_objects.rb └── grape-raketasks │ ├── route_spec.rb │ ├── console_formatter_spec.rb │ └── processor_spec.rb ├── .travis.yml ├── .rubocop.yml ├── Rakefile ├── grape-raketasks.gemspec ├── LICENSE └── README.md /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format=documentation 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ruby 2 | Gemfile.lock 3 | *.gem 4 | 5 | # Vim 6 | *.sw[a-z] 7 | -------------------------------------------------------------------------------- /lib/grape-raketasks/version.rb: -------------------------------------------------------------------------------- 1 | module GrapeRakeTasks 2 | VERSION = '0.0.3' 3 | end 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | gem 'rubocop', '0.22.0' 6 | -------------------------------------------------------------------------------- /lib/grape-raketasks/tasks.rb: -------------------------------------------------------------------------------- 1 | load File.expand_path('../../tasks/grape-raketasks.rake', __FILE__) 2 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'grape-raketasks' 2 | 3 | # load support files 4 | Dir.glob(ENV['PWD'] + '/spec/support/*.rb').each { |f| require f } 5 | -------------------------------------------------------------------------------- /lib/grape-raketasks/railtie.rb: -------------------------------------------------------------------------------- 1 | module GrapeRakeTasks 2 | class Railtie < Rails::Railtie 3 | rake_tasks do 4 | Dir[File.join(File.dirname(__FILE__), '../tasks/*.rake')].each { |f| load f } 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/grape-raketasks.rb: -------------------------------------------------------------------------------- 1 | require 'grape' 2 | require 'grape-raketasks/route' 3 | require 'grape-raketasks/processor' 4 | require 'grape-raketasks/console_formatter' 5 | 6 | module GrapeTasks 7 | require 'grape-raketasks/railtie' if defined?(Rails) 8 | end 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | 3 | cache: bundler 4 | 5 | rvm: 6 | - ruby-head 7 | - 2.1.1 8 | - 2.1.0 9 | - 2.0.0 10 | - 1.9.3 11 | - jruby-19mode 12 | - jruby-head 13 | - rbx-2 14 | 15 | matrix: 16 | allow_failures: 17 | - rvm: ruby-head 18 | - rvm: jruby-head 19 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | Exclude: 3 | - vendor/**/* 4 | - bin/**/* 5 | 6 | Documentation: 7 | Enabled: false 8 | 9 | LineLength: 10 | Enabled: false 11 | 12 | MethodLength: 13 | Enabled: true 14 | 15 | ClassLength: 16 | Enabled: false 17 | 18 | FileName: 19 | Enabled: false 20 | 21 | Encoding: 22 | Enabled: false 23 | -------------------------------------------------------------------------------- /lib/tasks/grape-raketasks.rake: -------------------------------------------------------------------------------- 1 | namespace :grape_raketasks do 2 | desc 'Print routes provided by Grape APIs to the terminal. Target a specific API with API=x.' 3 | task routes: :environment do 4 | all_routes = GrapeRakeTasks::Route.all_routes(Grape::API) 5 | processor = GrapeRakeTasks::Processor.new(all_routes) 6 | puts processor.format(GrapeRakeTasks::ConsoleFormatter.new, ENV['API']) 7 | end 8 | end 9 | 10 | -------------------------------------------------------------------------------- /spec/support/test_objects.rb: -------------------------------------------------------------------------------- 1 | # test objects 2 | 3 | def grape_route_object 4 | g = Grape::Route.new 5 | g.options = { path: '/', method: 'GET' } 6 | g 7 | end 8 | 9 | module SampleApiOne 10 | class API < Grape::API 11 | end 12 | end 13 | 14 | module SampleApiTwo 15 | class API < Grape::API 16 | end 17 | end 18 | 19 | class SampleApiThree < Grape::API 20 | end 21 | 22 | Grape::API.subclasses.each do |api| 23 | api.routes << grape_route_object 24 | end 25 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'bundler' 3 | 4 | Bundler.setup :default, :test, :development 5 | 6 | Bundler::GemHelper.install_tasks 7 | 8 | require 'rspec/core/rake_task' 9 | RSpec::Core::RakeTask.new(:spec) do |spec| 10 | spec.pattern = 'spec/**/*_spec.rb' 11 | end 12 | 13 | task :spec 14 | 15 | require 'rainbow/ext/string' unless String.respond_to?(:color) 16 | require 'rubocop/rake_task' 17 | Rubocop::RakeTask.new(:rubocop) 18 | 19 | task default: [:rubocop, :spec] 20 | -------------------------------------------------------------------------------- /lib/grape-raketasks/route.rb: -------------------------------------------------------------------------------- 1 | require 'active_support/core_ext/class' 2 | 3 | # enable adding the API name to a route's options 4 | module Grape 5 | class Route 6 | attr_accessor :options 7 | end 8 | end 9 | 10 | module GrapeRakeTasks 11 | class Route 12 | def self.all_routes(api) 13 | api.subclasses.flat_map do |klass| 14 | api_routes(klass) 15 | end 16 | end 17 | 18 | def self.api_routes(api) 19 | api.routes.map do |grape_route| 20 | route_with_api_name(grape_route, api) 21 | end 22 | end 23 | 24 | def self.route_with_api_name(grape_route, api_name) 25 | route = grape_route.dup 26 | new_opts = route.options.merge(api: api_name) 27 | route.options = new_opts 28 | route 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /grape-raketasks.gemspec: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/lib/grape-raketasks/version' 2 | 3 | Gem::Specification.new do |gem| 4 | gem.name = 'grape-raketasks' 5 | gem.version = GrapeRakeTasks::VERSION 6 | 7 | gem.homepage = 'https://github.com/reprah/grape-raketasks' 8 | gem.license = 'MIT' 9 | gem.summary = 'Rake tasks for web applications using Grape APIs.' 10 | gem.description = 'Provides rake tasks to ease the development and debugging of Grape APIs.' 11 | gem.author = 'H. Henn' 12 | 13 | gem.files = `git ls-files`.split($OUTPUT_RECORD_SEPARATOR) 14 | gem.test_files = gem.files.grep(/^spec/) 15 | 16 | gem.add_runtime_dependency 'grape' 17 | gem.add_runtime_dependency 'rake' 18 | gem.add_runtime_dependency 'activesupport' 19 | 20 | gem.add_development_dependency 'rspec' 21 | end 22 | -------------------------------------------------------------------------------- /lib/grape-raketasks/processor.rb: -------------------------------------------------------------------------------- 1 | require 'active_support/core_ext/string' 2 | 3 | module GrapeRakeTasks 4 | class Processor 5 | attr_reader :routes 6 | 7 | def initialize(routes) 8 | @routes = routes 9 | end 10 | 11 | def format(formatter, filter = nil) 12 | routes_to_display = filter_by_api(filter) 13 | formatter.construct_output(routes_to_display, filter) 14 | formatter.result 15 | end 16 | 17 | def filter_by_api(filter = nil) 18 | return routes unless filter 19 | pattern = Regexp.new(filter, Regexp::IGNORECASE) 20 | filtered = routes.select do |r| 21 | matches_filter_pattern?(r, pattern) 22 | end 23 | filtered.uniq 24 | end 25 | 26 | private 27 | 28 | def matches_filter_pattern?(route, pattern) 29 | # match filter against string representation of a route's API 30 | api_as_string = route.route_api.to_s 31 | api_as_string.match(pattern) 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/grape-raketasks/route_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe GrapeRakeTasks::Route do 4 | describe '.route_with_api_name' do 5 | let(:route) { grape_route_object } 6 | 7 | it 'adds an API name to a route\'s options hash' do 8 | altered_route = described_class.route_with_api_name(route, SampleApiOne::API) 9 | expect(altered_route.options).to have_key(:api) 10 | end 11 | end 12 | 13 | describe '.api_routes' do 14 | let(:api_class) { SampleApiOne::API } 15 | 16 | it 'returns a collection of routes that know their parent API' do 17 | routes = described_class.api_routes(SampleApiOne::API) 18 | every_route_has_api = routes.all? { |r| r.options.key?(:api) } 19 | expect(every_route_has_api).to eq(true) 20 | end 21 | end 22 | 23 | describe '.all_routes' do 24 | it 'returns routes belonging to every subclass' do 25 | routes = described_class.all_routes(Grape::API) 26 | api_names = routes.map(&:route_api) 27 | expect(api_names).to include(SampleApiOne::API) 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 H. Henn 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /spec/grape-raketasks/console_formatter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe GrapeRakeTasks::ConsoleFormatter do 4 | let(:formatter) { described_class.new } 5 | 6 | describe '#construct_output' do 7 | before do 8 | allow(formatter) 9 | .to receive(:format_route) 10 | .and_return('formatted route') 11 | end 12 | 13 | context 'when routes exist' do 14 | before do 15 | formatter.construct_output(['a route']) 16 | end 17 | 18 | it 'adds formatted routes to the buffer' do 19 | expect(formatter.buffer).to include('formatted route') 20 | end 21 | end 22 | 23 | context 'when no routes exist' do 24 | before do 25 | formatter.construct_output([]) 26 | end 27 | 28 | it 'adds a message to the buffer' do 29 | msg = formatter.buffer.first 30 | expect(msg).to include("You don't have any Grape routes") 31 | end 32 | end 33 | end 34 | 35 | describe '#format_route' do 36 | let(:route_object) { grape_route_object } 37 | 38 | it 'returns a text representation of a route object' do 39 | result = formatter.format_route(route_object) 40 | expect(result).to match(/METHOD:\s+"GET"/) 41 | end 42 | end 43 | 44 | describe '#result' do 45 | before do 46 | formatter.construct_output([]) 47 | end 48 | 49 | it 'returns the contents of the buffer as a string' do 50 | expect(formatter.result).to be_a(String) 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/grape-raketasks/console_formatter.rb: -------------------------------------------------------------------------------- 1 | require 'active_support/core_ext/string' 2 | 3 | module GrapeRakeTasks 4 | class ConsoleFormatter 5 | attr_reader :buffer 6 | 7 | def initialize 8 | @buffer = [] 9 | end 10 | 11 | def result 12 | buffer.join << "\n\n" 13 | end 14 | 15 | def construct_output(routes, filter = nil) 16 | if routes.any? 17 | buffer << formatted_routes(routes) 18 | else 19 | buffer << no_routes_message(filter) 20 | end 21 | end 22 | 23 | def formatted_routes(routes) 24 | formatted = routes.map { |r| format_route(r) } 25 | formatted.join("\n\n") 26 | end 27 | 28 | def format_route(route) 29 | opts = route.options 30 | # two characters (colon + space) after the title 31 | longest_key_length = opts.keys.map(&:length).max + 2 32 | 33 | opts.sort.map do |key, value| 34 | title = key_to_title(key) 35 | title << build_padding(longest_key_length, key.length) 36 | title << value.inspect 37 | end.join("\n") 38 | end 39 | 40 | private 41 | 42 | def build_padding(max_len, title_len) 43 | # whitespace padding used to align output 44 | ' ' * (max_len - title_len) 45 | end 46 | 47 | def key_to_title(key) 48 | key.to_s.upcase.concat(': ') 49 | end 50 | 51 | def no_routes_message(filter) 52 | subject = filter ? filter : 'your application' 53 | <<-MSG.strip_heredoc 54 | You don't have any Grape routes defined for #{subject}. 55 | Visit https://github.com/intridea/grape for help. 56 | MSG 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /spec/grape-raketasks/processor_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe GrapeRakeTasks::Processor do 4 | describe '#filter_by_api' do 5 | let(:routes) do 6 | GrapeRakeTasks::Route.all_routes(Grape::API) 7 | end 8 | 9 | let(:processor) { described_class.new(routes) } 10 | 11 | shared_examples 'successful API filtering' do 12 | it 'returns routes belonging to one API' do 13 | filtered = processor.filter_by_api(filter) 14 | filtered_apis = filtered.map(&:route_api) 15 | expect(filtered_apis).to eq [SampleApiOne::API] 16 | end 17 | end 18 | 19 | context 'when given a Grape API to filter by' do 20 | let(:filter) { 'SampleAPIOne::API' } 21 | 22 | it_behaves_like 'successful API filtering' 23 | 24 | context "when filter's case does not exactly match an API" do 25 | let(:filter) { 'SampleApiOne::API' } 26 | 27 | it_behaves_like 'successful API filtering' 28 | end 29 | 30 | context 'when API is not nested within another constant' do 31 | let(:filter) { 'SampleAPIThree' } 32 | 33 | it 'returns routes belonging to that api' do 34 | filtered = processor.filter_by_api(filter) 35 | filtered_apis = filtered.map(&:route_api) 36 | expect(filtered_apis).to include(SampleApiThree) 37 | end 38 | end 39 | end 40 | 41 | context 'when no filter is given' do 42 | it 'returns routes from every Grape API' do 43 | unfiltered = processor.filter_by_api 44 | unfiltered_apis = unfiltered.map(&:route_api) 45 | expect(unfiltered_apis.length).to eq routes.length 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # grape-raketasks 2 | 3 | Rake tasks to ease the development and debugging of Grape APIs. 4 | 5 | [![Build Status](https://travis-ci.org/reprah/grape-raketasks.svg)](https://travis-ci.org/reprah/grape-raketasks) 6 | 7 | ## Available Tasks 8 | 9 | ### Routes 10 | 11 | `rake grape_raketasks:routes` is like `rake routes` for your Grape APIs. All routes within every Grape API in your web application will be printed to the terminal, along with parameter requiremements, HTTP verb, the API it belongs to, etc. 12 | 13 | #### Filtering 14 | 15 | If you want to see routes belonging to only one API: 16 | 17 | Pass an environment variable set to your API name after writing the task. Given the API below, and assuming we only want to see routes belonging to this CatPictures API... 18 | 19 | ```ruby 20 | module CatPictures 21 | class API < Grape::API 22 | # API stuff 23 | end 24 | end 25 | ``` 26 | 27 | I'd execute `rake grape_raketasks:routes API=CatPictures::API`. Notice how we have to list which constants the API is nested in (if any), separated by a double colon, like in Ruby code. 28 | 29 | ## Installation 30 | 31 | 1.) Add `grape-raketasks` to your Gemfile: 32 | 33 | ```ruby 34 | # Gemfile 35 | gem 'grape-raketasks' 36 | ``` 37 | 38 | 2.) Install the gem via Bundler: 39 | 40 | ```shell 41 | $ bundle install 42 | ``` 43 | or on the command line: 44 | 45 | ```shell 46 | $ gem install grape-raketasks 47 | ``` 48 | 49 | 3.) If your Grape APIs are defined in a Sinatra or Rack web application, you need to write a rake task called `:environment`that loads the application's environment first. This gem's tasks are dependent on it. You could put this in the root of your project directory: 50 | 51 | ```ruby 52 | # Rakefile 53 | 54 | require 'rake' 55 | require 'bundler' 56 | Bundler.setup 57 | require 'grape-raketasks' 58 | require 'grape-raketasks/tasks' 59 | 60 | desc 'load the Sinatra environment.' 61 | task :environment do 62 | require File.expand_path('your_app_file', File.dirname(__FILE__)) 63 | end 64 | ``` 65 | Rails applications with mounted Grape APIs don't require an extra step here. 66 | 67 | 4.) Run `rake -T` to see all available rake tasks. Tasks with a `grape_raketasks` namespace should appear somewhere. 68 | 69 | 5.) Use the tasks! Find bugs or ideas for improvement! Report them here! 70 | 71 | ## Contributing 72 | 73 | 1. Fork it 74 | 2. Create your feature branch (`git checkout -b my-new-feature`) 75 | 3. Write specs for your feature 76 | 4. Commit your changes (`git commit -am 'Add some feature'`) 77 | 5. Push to the branch (`git push origin my-new-feature`) 78 | 6. Create a new pull request 79 | 80 | ## License 81 | 82 | See LICENSE 83 | --------------------------------------------------------------------------------