redwing 0.0.5 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 95c43721b5368bb64065451f424231f3af8962c514d09c577f4c82886409ec4b
4
- data.tar.gz: 04b14110d7ac8d37fe544494b615c6849cc8a950036e4c7a31a9042cb876ae62
3
+ metadata.gz: c562d3c79811799adcb0a02faedd22a7ebd4b6f4fcba2b1a7fa0049400580e28
4
+ data.tar.gz: 0ab3fd11b2c75bb5b4391e6ee310b7193eeff2057c68e069d922fc7eb1a3b3e3
5
5
  SHA512:
6
- metadata.gz: ffa1d019b6eaab8f24b0c5f9acc4ec4a68f9c747d033c9f398c37fff420925e175c9ce7ff9794331c80a7703af381ce249b105e8a1965f38a0941557cdf81e81
7
- data.tar.gz: f106f414ab0ce5593daf65aa5399eb20b0a86e8abd6ccbe07319d4a0e2dbc9a88bc8e5ceed262107eab3c7b3fc24b2b329ba6a0d1839591e2350d119b24139d1
6
+ metadata.gz: a75d8e35db5f8fadc24321358144cd042b4e8dde6903e855730e2cf1e992ab28fb56bcba48be9fb9620555b9227e7b3dc2389000295cb024722ba74e1c72fd25
7
+ data.tar.gz: 7d0de17fc8e3adf55eda1e1551bd74c53adb213f9505440a0744a63f7205cd19db7cd32cb52f604f7e64a93b5dd4a785433c4f4c87586419b8564f9be6472e5f
data/README.md CHANGED
@@ -1,3 +1,42 @@
1
1
  # redwing
2
2
 
3
3
  Sometimes, all you need is a little server. That´s `redwing` for you. What you make out of it, is up to you.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ gem install redwing
9
+ redwing new api my-api
10
+ cd my-api
11
+ redwing server
12
+ ```
13
+
14
+ Then visit `http://localhost:3001/hello`.
15
+
16
+ ## What you get
17
+
18
+ ```
19
+ my-api/
20
+ Gemfile
21
+ README.md
22
+ config/
23
+ redwing.yml
24
+ routes.rb
25
+ ```
26
+
27
+ ### Define routes
28
+
29
+ ```ruby
30
+ # config/routes.rb
31
+ Redwing.routes do
32
+ get '/hello' do
33
+ {message: 'Hello from my-api'}
34
+ end
35
+ end
36
+ ```
37
+
38
+ Hash returns are auto-serialized to JSON.
39
+
40
+ ## Requirements
41
+
42
+ - Ruby 3.4+
data/bin/redwing CHANGED
@@ -6,4 +6,4 @@ $LOAD_PATH.unshift(lib_path) unless $LOAD_PATH.include?(lib_path)
6
6
 
7
7
  require 'redwing/command'
8
8
 
9
- Redwing::Command.invoke ARGV.dup
9
+ Redwing::Command.execute ARGV.dup
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+ require 'thor/actions'
5
+
6
+ module Redwing
7
+ module Command
8
+ class BaseCommand < Thor
9
+ include Thor::Actions
10
+ end
11
+ end
12
+ end
@@ -1,31 +1,114 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'errors'
4
+ require_relative 'command/base_command'
5
+
3
6
  require 'redwing/commands/console_command'
7
+ require 'redwing/commands/new_command'
4
8
  require 'redwing/commands/server_command'
5
9
 
6
10
  module Redwing
7
11
  module Command
8
12
  COMMAND_WHITELIST = {
9
13
  console: %w[console c],
10
- server: %w[server s]
14
+ server: %w[server s],
15
+ new: %w[new]
11
16
  }.freeze
12
17
 
13
18
  VERSION_MAPPINGS = %w[-v --version].to_set
14
19
 
15
- def self.invoke(argv)
16
- return puts Redwing::VERSION if VERSION_MAPPINGS.include?(argv.first)
20
+ class << self
21
+ attr_reader :argv, :args
22
+
23
+ def execute(argv)
24
+ return puts Redwing::VERSION if VERSION_MAPPINGS.include?(argv.first)
25
+
26
+ @argv = argv
27
+ @args = @argv.drop(1)
28
+
29
+ raise Error::UnknownCommand, "Unknown command: #{@argv.first}" if command_klass.nil?
30
+
31
+ exec_klass = command_klass.new(@args, command_options)
32
+ unless exec_klass.respond_to?(:perform)
33
+ raise Error::PerformNotImplemented, "#{exec_klass.class} does not implement #perform"
34
+ end
35
+
36
+ trap_interrupt!
37
+ exec_klass.perform
38
+
39
+ exit_ok!
40
+ rescue StandardError => e
41
+ shell.say(e.message)
42
+
43
+ exit_failed!
44
+ end
45
+
46
+ def reset!
47
+ @argv = nil
48
+ @args = nil
49
+ @command_name = nil
50
+ @command_klass = nil
51
+ end
52
+
53
+ def command_name
54
+ @command_name ||= COMMAND_WHITELIST.find { |_, aliases| aliases.include?(@argv.first) }&.first
55
+ end
56
+
57
+ def command_klass
58
+ @command_klass ||= {
59
+ console: Redwing::Commands::ConsoleCommand,
60
+ new: Redwing::Commands::NewCommand,
61
+ server: Redwing::Commands::ServerCommand
62
+ }[command_name]
63
+ end
64
+
65
+ private
17
66
 
18
- command_name = COMMAND_WHITELIST.find { |_, aliases| aliases.include?(argv.first) }&.first
19
- args = argv.drop(1)
67
+ def shell
68
+ @shell ||= Thor::Base.shell.new
69
+ end
70
+
71
+ def command_options
72
+ Thor::Options.new(
73
+ command_klass.class_options,
74
+ {},
75
+ stop_on_unknown_option?,
76
+ disable_required_check?,
77
+ command_options_relation
78
+ ).parse(@args)
79
+ end
80
+
81
+ def stop_on_unknown_option?
82
+ false
83
+ end
84
+
85
+ def disable_required_check?
86
+ return true if %w[-h --help].include?(@argv.first)
87
+
88
+ command_klass.disable_required_check? command_klass
89
+ end
90
+
91
+ def command_options_relation
92
+ {exclusive_option_names: [], at_least_one_option_names: []}
93
+ end
94
+
95
+ def exit_ok!
96
+ Kernel.exit(0)
97
+ end
98
+
99
+ def exit_failed!
100
+ Kernel.exit(1)
101
+ end
102
+
103
+ def exit_unable_to_finish!
104
+ Kernel.exit(2)
105
+ end
20
106
 
21
- case command_name
22
- when :console
23
- Redwing::Commands::ConsoleCommand.start(args)
24
- when :server
25
- Redwing::Commands::ServerCommand.start(args)
26
- else
27
- warn "Unknown command: #{argv.first}"
28
- exit(1)
107
+ def trap_interrupt!
108
+ Signal.trap('INT') do
109
+ shell.say("\nExiting... Interrupt again to exit immediately.")
110
+ exit_unable_to_finish!
111
+ end
29
112
  end
30
113
  end
31
114
  end
@@ -1,30 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'thor'
4
3
  require 'redwing/version'
4
+ require 'redwing/command/base_command'
5
5
 
6
6
  module Redwing
7
7
  module Commands
8
- class ConsoleCommand < Thor
9
- desc 'console', 'Start an interactive Redwing console'
8
+ class ConsoleCommand < Command::BaseCommand
9
+ no_commands do
10
+ def perform
11
+ app_file = File.join(Dir.pwd, 'config', 'application.rb')
12
+ load app_file if File.exist?(app_file)
10
13
 
11
- def console
12
- app_root = Dir.pwd
13
- app_file = File.join(app_root, 'config', 'application.rb')
14
- load app_file if File.exist?(app_file)
14
+ puts "Redwing #{Redwing::VERSION} console (#{RUBY_ENGINE} #{RUBY_VERSION})"
15
15
 
16
- puts "Redwing #{Redwing::VERSION} console (#{RUBY_ENGINE} #{RUBY_VERSION})"
17
-
18
- begin
19
- require 'pry'
20
- Pry.start
21
- rescue LoadError
22
- require 'irb'
23
- IRB.start(__FILE__)
16
+ begin
17
+ require 'pry'
18
+ Pry.start
19
+ rescue LoadError
20
+ require 'irb'
21
+ IRB.start(__FILE__)
22
+ end
24
23
  end
25
24
  end
26
-
27
- default_task :console
28
25
  end
29
26
  end
30
27
  end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+
5
+ require 'redwing/command/base_command'
6
+ require 'redwing/generator'
7
+
8
+ module Redwing
9
+ module Commands
10
+ class NewCommand < Command::BaseCommand
11
+ VALID_APP_TYPES = %w[api web].freeze
12
+ TEMPLATES = %w[
13
+ README.md
14
+ Gemfile
15
+ config/redwing.yml
16
+ config/routes.rb
17
+ ].freeze
18
+
19
+ argument :type, required: true, values: %w[api web]
20
+ argument :name, required: true, type: :string
21
+
22
+ no_commands do
23
+ def perform
24
+ validate!
25
+
26
+ target_path = "#{Pathname.pwd}/#{app_name}"
27
+ data = {name: app_name, type: app_type}
28
+
29
+ TEMPLATES.each do |template_name|
30
+ template = "templates/#{template_name}"
31
+ destination = "#{target_path}/#{template_name}"
32
+ Generator.create_file_by_template(template, destination, data)
33
+ end
34
+ end
35
+
36
+ def app_type
37
+ @type
38
+ end
39
+
40
+ def app_name
41
+ @name
42
+ end
43
+
44
+ def validate!
45
+ raise ArgumentError, "Invalid app type '#{app_type}'. Valid types: #{valid_app_types}" unless valid_app_type?
46
+ end
47
+
48
+ def valid_app_type?
49
+ VALID_APP_TYPES.include?(app_type)
50
+ end
51
+
52
+ def valid_app_types
53
+ VALID_APP_TYPES.join(', ')
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -1,21 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'thor'
4
3
  require 'redwing/server'
4
+ require 'redwing/command/base_command'
5
5
 
6
6
  module Redwing
7
7
  module Commands
8
- class ServerCommand < Thor
9
- desc 'server', 'Start the Redwing server'
8
+ class ServerCommand < Command::BaseCommand
9
+ class_option :host, type: :string, default: 'localhost', aliases: '-b', desc: 'Bind to host'
10
+ class_option :port, type: :numeric, default: 3001, aliases: '-p', desc: 'Listen on port'
10
11
 
11
- method_option :host, type: :string, default: 'localhost', aliases: '-b', desc: 'Bind to host'
12
- method_option :port, type: :numeric, default: 3001, aliases: '-p', desc: 'Listen on port'
12
+ no_commands do
13
+ def perform
14
+ load_routes
15
+ Redwing::Server.start(host: options[:host], port: options[:port])
16
+ end
13
17
 
14
- def server
15
- Redwing::Server.start(host: options[:host], port: options[:port])
18
+ def load_routes
19
+ require File.expand_path('config/routes')
20
+ end
16
21
  end
17
-
18
- default_task :server
19
22
  end
20
23
  end
21
24
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+
5
+ module Redwing
6
+ class Config
7
+ attr_reader :type
8
+
9
+ def initialize(path = 'config/redwing.yml')
10
+ data = YAML.safe_load_file(path, symbolize_names: true)
11
+ @type = data[:type].to_sym
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Redwing
4
+ module Error
5
+ class Base < StandardError; end
6
+
7
+ class UnknownCommand < Base; end
8
+ class PerformNotImplemented < Base; end
9
+ end
10
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor/group'
4
+ require 'thor/actions'
5
+
6
+ module Redwing
7
+ module Generator
8
+ class BaseGenerator < Thor::Group
9
+ include Thor::Actions
10
+ end
11
+
12
+ class FileByTemplate < BaseGenerator
13
+ argument :source, type: :string
14
+ argument :destination, type: :string
15
+ argument :data, type: :hash, default: {}
16
+
17
+ def self.source_root
18
+ File.dirname(__FILE__)
19
+ end
20
+
21
+ def create
22
+ data.each { |k, v| define_singleton_method(k) { v } }
23
+ template(source, destination)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'generator/file_by_template'
4
+
5
+ module Redwing
6
+ module Generator
7
+ def create_file_by_template(template, destination, config = {})
8
+ FileByTemplate.start([template, destination, config])
9
+ end
10
+
11
+ module_function :create_file_by_template
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Redwing
4
+ class Router
5
+ attr_reader :routes
6
+
7
+ def initialize
8
+ @routes = []
9
+ end
10
+
11
+ def get(path, &block)
12
+ @routes << {method: 'GET', path: path, handler: block}
13
+ end
14
+
15
+ def match(method, path)
16
+ routes.find { |r| r[:method] == method && r[:path] == path }
17
+ end
18
+ end
19
+ end
@@ -1,14 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'json'
3
4
  require 'rack'
4
5
  require 'rackup'
6
+ require 'redwing'
5
7
 
6
8
  module Redwing
7
9
  module Server
8
10
  def self.start(host:, port:)
9
11
  app = proc do |env|
10
- _request = Rack::Request.new(env)
11
- [200, {}, ['hello world']]
12
+ request = Rack::Request.new(env)
13
+ route = Redwing.routes.match(request.request_method, request.path_info)
14
+
15
+ if route
16
+ body = route[:handler].call
17
+ [200, {'content-type' => 'application/json'}, [body.to_json]]
18
+ else
19
+ [404, {'content-type' => 'application/json'}, ['{"error":"Not Found"}']]
20
+ end
12
21
  end
13
22
 
14
23
  handler = Rackup::Handler.get('puma')
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Redwing
4
- VERSION = '0.0.5'
4
+ VERSION = '0.0.6'
5
5
 
6
6
  def self.gem_version
7
7
  Gem::Version.new VERSION
data/lib/redwing.rb CHANGED
@@ -5,4 +5,18 @@ require 'redwing/version'
5
5
 
6
6
  module Redwing
7
7
  extend ActiveSupport::Autoload
8
+
9
+ autoload :Config
10
+ autoload :Generator
11
+ autoload :Router
12
+
13
+ def self.routes(&block)
14
+ @router ||= Router.new
15
+ @router.instance_eval(&block) if block
16
+ @router
17
+ end
18
+
19
+ def self.reset_routes!
20
+ @router = nil
21
+ end
8
22
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redwing
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marek Witkowski
@@ -167,6 +167,20 @@ dependencies:
167
167
  - - "~>"
168
168
  - !ruby/object:Gem::Version
169
169
  version: 1.86.0
170
+ - !ruby/object:Gem::Dependency
171
+ name: rubocop-rake
172
+ requirement: !ruby/object:Gem::Requirement
173
+ requirements:
174
+ - - "~>"
175
+ - !ruby/object:Gem::Version
176
+ version: '0.6'
177
+ type: :development
178
+ prerelease: false
179
+ version_requirements: !ruby/object:Gem::Requirement
180
+ requirements:
181
+ - - "~>"
182
+ - !ruby/object:Gem::Version
183
+ version: '0.6'
170
184
  - !ruby/object:Gem::Dependency
171
185
  name: simplecov
172
186
  requirement: !ruby/object:Gem::Requirement
@@ -192,8 +206,15 @@ files:
192
206
  - bin/redwing
193
207
  - lib/redwing.rb
194
208
  - lib/redwing/command.rb
209
+ - lib/redwing/command/base_command.rb
195
210
  - lib/redwing/commands/console_command.rb
211
+ - lib/redwing/commands/new_command.rb
196
212
  - lib/redwing/commands/server_command.rb
213
+ - lib/redwing/config.rb
214
+ - lib/redwing/errors.rb
215
+ - lib/redwing/generator.rb
216
+ - lib/redwing/generator/file_by_template.rb
217
+ - lib/redwing/router.rb
197
218
  - lib/redwing/server.rb
198
219
  - lib/redwing/version.rb
199
220
  homepage: https://github.com/mrmarekwitkowski/redwing