apiway 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +3 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +1 -0
  5. data/Rakefile +0 -0
  6. data/bin/apiway +13 -0
  7. data/lib/apiway/application.rb +53 -0
  8. data/lib/apiway/client.rb +171 -0
  9. data/lib/apiway/commands.rb +62 -0
  10. data/lib/apiway/controller.rb +124 -0
  11. data/lib/apiway/diff.rb +84 -0
  12. data/lib/apiway/errors.rb +62 -0
  13. data/lib/apiway/events.rb +23 -0
  14. data/lib/apiway/extensions.rb +29 -0
  15. data/lib/apiway/generator.rb +111 -0
  16. data/lib/apiway/logger.rb +41 -0
  17. data/lib/apiway/model.rb +58 -0
  18. data/lib/apiway/path.rb +7 -0
  19. data/lib/apiway/resource.rb +110 -0
  20. data/lib/apiway/version.rb +5 -0
  21. data/lib/apiway.rb +17 -0
  22. data/lib/generator/application/.gitignore +1 -0
  23. data/lib/generator/application/Gemfile +9 -0
  24. data/lib/generator/application/Procfile +1 -0
  25. data/lib/generator/application/README.md +0 -0
  26. data/lib/generator/application/Rakefile +3 -0
  27. data/lib/generator/application/app/base/.keep +0 -0
  28. data/lib/generator/application/app/base/base.rb +5 -0
  29. data/lib/generator/application/app/base/client.rb +21 -0
  30. data/lib/generator/application/app/base/routes.rb +11 -0
  31. data/lib/generator/application/app/controllers/.keep +0 -0
  32. data/lib/generator/application/app/controllers/application.rb +5 -0
  33. data/lib/generator/application/app/models/.keep +0 -0
  34. data/lib/generator/application/app/resources/.keep +0 -0
  35. data/lib/generator/application/app/resources/application.rb +5 -0
  36. data/lib/generator/application/config/application.rb +13 -0
  37. data/lib/generator/application/config/database.yml +17 -0
  38. data/lib/generator/application/config/environments/development.rb +9 -0
  39. data/lib/generator/application/config/environments/production.rb +9 -0
  40. data/lib/generator/application/config/environments/test.rb +10 -0
  41. data/lib/generator/application/config.ru +3 -0
  42. data/lib/generator/application/db/migrate/.keep +0 -0
  43. data/lib/generator/application/lib/.keep +0 -0
  44. data/lib/generator/application/public/.keep +0 -0
  45. data/lib/generator/templates/controller.tpl +22 -0
  46. data/lib/generator/templates/model.tpl +5 -0
  47. data/lib/generator/templates/resource.tpl +29 -0
  48. metadata +174 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e359eb647d0797382fe8d4960a74390d9e453527
4
+ data.tar.gz: a9bb742d1d44c02040161ed4332efe8cb5b425c1
5
+ SHA512:
6
+ metadata.gz: 87bbe8b23efea6c9d3b9d97c41771e26331e139bcfe2bd122babd82481373093111deafdf7382da9f31c626d24617695c2f5877b170f3412ad1052056ae27145
7
+ data.tar.gz: 2375c4599a6acbcbd3434fc205692cb2362582fd1afa6e71bae644d3b71ae93b7de2a0e11c0bc67435e6428fd5c0d0a99f5a0580eac67efcdab66ea614dd4ff0
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 4urbanoff
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.
data/README.md ADDED
@@ -0,0 +1 @@
1
+ ## Apiway
data/Rakefile ADDED
File without changes
data/bin/apiway ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'erb'
4
+ require 'fileutils'
5
+ require 'logger'
6
+ require 'apiway/path'
7
+ require 'apiway/logger'
8
+ require 'apiway/version'
9
+ require 'apiway/extensions'
10
+ require 'apiway/generator'
11
+ require 'apiway/commands'
12
+
13
+ Apiway::Commands.run *ARGV
@@ -0,0 +1,53 @@
1
+ module Apiway
2
+
3
+ class Application < Sinatra::Base
4
+
5
+
6
+ set root: File.expand_path( '.' )
7
+ set static: true
8
+ set apiway_log: true
9
+ set active_record_log: true
10
+ set log_level: :debug
11
+ set database_file: File.join( root, 'config/database.yml' )
12
+
13
+
14
+ register Sinatra::ActiveRecordExtension
15
+
16
+
17
+ configure :development do
18
+ register Sinatra::Reloader
19
+ also_reload File.join( root, '**/*.rb' )
20
+ # also_reload File.join( Apiway.path, '**/*.rb' )
21
+ end
22
+
23
+
24
+ get '*' do
25
+ request.websocket? ? request.websocket{ |ws| Apiway::Client.new ws } : pass
26
+ end
27
+
28
+
29
+ %W(
30
+ lib/**/*.rb
31
+ config/environments/#{ environment.to_s }.rb
32
+ config/initializers/**/*.rb
33
+ app/base/**/*.rb
34
+ app/controllers/application.rb
35
+ app/resources/application.rb
36
+ app/**/*.rb
37
+ )
38
+ .map{ |path| Dir[ File.join( root, path ) ] }
39
+ .flatten.uniq.each{ |path| require path }
40
+
41
+
42
+ LoggerBase::apiway_log_level apiway_log
43
+ LoggerBase::activerecord_log_level activerecord_log
44
+
45
+
46
+ def self.tasks
47
+ require 'sinatra/activerecord/rake'
48
+ end
49
+
50
+
51
+ end
52
+
53
+ end
@@ -0,0 +1,171 @@
1
+ module Apiway
2
+
3
+ class Client
4
+
5
+
6
+ HANDLERS = Hash[
7
+ API::ALIVE, :alive,
8
+ API::QUERY, :run_controller,
9
+ RESOURCE::SYNC, :sync_resource,
10
+ RESOURCE::DESTROY, :destroy_resource
11
+ ]
12
+
13
+
14
+ class << self
15
+
16
+ def all
17
+ @@all ||= []
18
+ @@all.each { |client| yield client } if block_given?
19
+ @@all
20
+ end
21
+
22
+ def sync_changes( changed_models )
23
+ all { |client| client.sync_changes changed_models }
24
+ end
25
+
26
+ def on_connected( &block )
27
+ block_given? ? @@on_connected = block : @@on_connected ||= Proc.new {}
28
+ end
29
+
30
+ def on_message( &block )
31
+ block_given? ? @@on_message = block : @@on_message ||= Proc.new {}
32
+ end
33
+
34
+ def on_disconnected( &block )
35
+ block_given? ? @@on_disconnected = block : @@on_disconnected ||= Proc.new {}
36
+ end
37
+
38
+ end
39
+
40
+
41
+ def initialize( ws )
42
+ @ws = ws
43
+ @ws.onopen { on_connected }
44
+ @ws.onmessage { |msg| on_message msg }
45
+ @ws.onclose { on_disconnected }
46
+ @storage = {}
47
+ @resources = {}
48
+ end
49
+
50
+ def []( name = nil )
51
+ @storage[ name ]
52
+ end
53
+
54
+ def []=( name, value )
55
+ @storage[ name ] = value
56
+ Thread.current[ :changed_models ].concat Apiway::Model.all
57
+ value
58
+ end
59
+
60
+ def sync_changes( changed_models )
61
+ @resources.values.each { |resource| resource.sync_changes changed_models }
62
+ end
63
+
64
+ def trigger( *args )
65
+ send_event API::TRIGGER, args: args
66
+ end
67
+
68
+
69
+ private
70
+
71
+ def processing
72
+ begin
73
+ Thread.new {
74
+ Thread.current[ :changed_models ] = []
75
+ Thread.current[ :methods_to_call ] = []
76
+ yield
77
+ self.class.sync_changes Thread.current[ :changed_models ].uniq
78
+ call_methods Thread.current[ :methods_to_call ]
79
+ }.join
80
+ rescue Exception => e
81
+ Log.error "#{ e.message }\n#{ e.backtrace.join "\n" }"
82
+ end
83
+ end
84
+
85
+ def on_connected
86
+ processing do
87
+ self.class.all << self
88
+ instance_eval &self.class.on_connected
89
+ end
90
+ Log.info "Client connected"
91
+ end
92
+
93
+ def on_message( msg )
94
+ @msg = parseMessage( msg ) rescue { event: API::ALIVE, data: nil }
95
+ Log.debug "New message: \n#{ JSON.pretty_generate( @msg ) }" if Log.debug?
96
+ processing do
97
+ instance_exec @msg, &self.class.on_message
98
+ handler = HANDLERS[ @msg[ :event ] ] or raise EventHandlerNotExists, @msg[ :event ]
99
+ send handler, @msg[ :data ]
100
+ end
101
+ end
102
+
103
+ def on_disconnected
104
+ processing do
105
+ self.class.all.delete self
106
+ instance_eval &self.class.on_disconnected
107
+ end
108
+ Log.info "Client disconnected"
109
+ end
110
+
111
+ def parseMessage( msg )
112
+ msg = JSON.parse msg, quirks_mode: true
113
+ msg.keys_to_sym!
114
+ end
115
+
116
+ def send_json( msg )
117
+ @ws.send JSON.generate( msg, quirks_mode: true )
118
+ Log.debug "Send message: \n#{ JSON.pretty_generate( msg ) }" if Log.debug?
119
+ end
120
+
121
+ def send_event( event, data = nil )
122
+ send_json event: event, data: data
123
+ end
124
+
125
+ def success( result )
126
+ send_event API::SUCCESS, result: result, query_id: @msg[ :data ][ :query_id ]
127
+ end
128
+
129
+ def failure( result )
130
+ send_event API::FAILURE, result: result, query_id: @msg[ :data ][ :query_id ]
131
+ end
132
+
133
+ def call_methods( methods )
134
+ methods.each { |method, args| send method, *args }
135
+ end
136
+
137
+ def alive( data )
138
+ send_event API::ALIVE
139
+ end
140
+
141
+ def run_controller( data )
142
+ name, action, params = data.values_at :name, :action, :params
143
+ name = "#{ name }Controller"
144
+ begin
145
+ controller = Object.const_get( name ).new action.to_sym, self, params
146
+ rescue NameError
147
+ raise ControllerNotExists, name
148
+ else
149
+ controller.run
150
+ end
151
+ end
152
+
153
+ def sync_resource( data )
154
+ id, name, params = data.values_at :id, :name, :params
155
+ name = "#{ name }Resource"
156
+ begin
157
+ @resources[ id ] ||= Object.const_get( name ).new id, self
158
+ rescue
159
+ raise ResourceNotExists, name
160
+ else
161
+ @resources[ id ].set_params( params ).sync
162
+ end
163
+ end
164
+
165
+ def destroy_resource( data )
166
+ @resources.delete data[ :id ]
167
+ end
168
+
169
+ end
170
+
171
+ end
@@ -0,0 +1,62 @@
1
+ module Apiway
2
+
3
+ module Commands
4
+
5
+
6
+ class << self
7
+
8
+ HANDLERS = {
9
+ version: [ '-v', 'v', 'version' ],
10
+ server: [ '-s', 's', 'server' ],
11
+ generate: [ '-g', 'g', 'generate' ],
12
+ create: [ '-n', 'n', 'new' ],
13
+ help: [ '-h', 'h', 'help' ]
14
+ }
15
+
16
+ DESC = {
17
+ version: 'Show gem version',
18
+ server: 'Launch thin server, alias for `bundle exec thin start`',
19
+ generate: 'Launch generator, run `apiway generator help` to show commands of generator',
20
+ create: 'Creating a new application',
21
+ help: 'Show list of commands'
22
+ }
23
+
24
+
25
+ def run( command = nil, *args )
26
+ return help unless command
27
+ HANDLERS.each { |handler, commands| return send( handler, *args ) if commands.include? command }
28
+ puts "Apiway: Unknown command `#{ args.unshift( command ).join " " }`"
29
+ end
30
+
31
+
32
+ private
33
+
34
+ def version( *args )
35
+ puts "Apiway version #{ Apiway::VERSION }"
36
+ end
37
+
38
+ def server( *args )
39
+ exec "bundle exec thin start #{ args.join " " }"
40
+ end
41
+
42
+ def generate( *args )
43
+ Generator.run *args
44
+ end
45
+
46
+ def create( *args )
47
+ generate "app", *args
48
+ end
49
+
50
+ def help( *args )
51
+ puts "\n Apiway commands: \n\n"
52
+ HANDLERS.each do |handler, commands|
53
+ puts " [#{ commands.join( "], [" ) }]".ljust(30) << "# #{ DESC[ handler ] } "
54
+ end
55
+ end
56
+
57
+ end
58
+
59
+
60
+ end
61
+
62
+ end
@@ -0,0 +1,124 @@
1
+ module Apiway
2
+
3
+ module Controller
4
+
5
+
6
+
7
+ class << self
8
+
9
+ def included( base )
10
+ base.class_eval do
11
+ extend ClassMethods
12
+ include InstanceMethods
13
+ end
14
+ end
15
+
16
+ end
17
+
18
+
19
+
20
+ module ClassMethods
21
+
22
+ def action( name, &block )
23
+ block_given? ? actions[ name ] = block : actions[ name ] or raise ControllerActionNotExists.new( self.name, name )
24
+ end
25
+
26
+ def before_action( method_name, only: [], except: [] )
27
+ register_filter :before, method_name, only, except
28
+ end
29
+
30
+ def after_action( method_name, only: [], except: [] )
31
+ register_filter :after, method_name, only, except
32
+ end
33
+
34
+ def select_filters( type, action_name )
35
+ filters( type ).select do |method_name, only, except|
36
+ ( only.empty? || only.include?( action_name ) ) && ( except.empty? || !except.include?( action_name ) )
37
+ end
38
+ end
39
+
40
+
41
+ private
42
+
43
+ def actions
44
+ @actions ||= {}
45
+ end
46
+
47
+ def filters( type )
48
+ ( @filters ||= {} )[ type ] ||= []
49
+ end
50
+
51
+ def register_filter( type, method_name, only, except )
52
+ only = [].push( only ).flatten
53
+ except = [].push( except ).flatten
54
+ filters( type ) << [ method_name, only, except ]
55
+ end
56
+
57
+ end
58
+
59
+
60
+
61
+ module InstanceMethods
62
+
63
+ def initialize( action_name, client, params = {} )
64
+ @action_name = action_name
65
+ @action = self.class.action @action_name
66
+ @client = client
67
+ @params = params
68
+ end
69
+
70
+ def run
71
+ begin
72
+ run_filters :before
73
+ result = run_action
74
+ run_filters :after
75
+ rescue ControllerError => e
76
+ failure e.params
77
+ else
78
+ success result
79
+ end
80
+ end
81
+
82
+
83
+ protected
84
+
85
+ attr_reader :client, :params
86
+
87
+ def trigger( *args )
88
+ add_method_to_call :trigger, args
89
+ end
90
+
91
+ def error( params )
92
+ raise ControllerError, params
93
+ end
94
+
95
+
96
+ private
97
+
98
+ def run_action
99
+ instance_eval &@action
100
+ end
101
+
102
+ def run_filters( type )
103
+ self.class.select_filters( type, @action_name ).each { |method_name, only, except| send method_name }
104
+ end
105
+
106
+ def add_method_to_call( method, args )
107
+ Thread.current[ :methods_to_call ] << [ method, args ]
108
+ end
109
+
110
+ def success( *args )
111
+ add_method_to_call :success, args
112
+ end
113
+
114
+ def failure( *args )
115
+ add_method_to_call :failure, args
116
+ end
117
+
118
+ end
119
+
120
+
121
+
122
+ end
123
+
124
+ end
@@ -0,0 +1,84 @@
1
+ module Apiway
2
+
3
+ class Diff
4
+
5
+
6
+ def initialize( source, target )
7
+ @del, @add, @del_c, @add_c = {}, {}, 0, 0
8
+ calculate source.to_s, target.to_s
9
+ end
10
+
11
+ def patch
12
+ [ @del, @add ]
13
+ end
14
+
15
+
16
+ private
17
+
18
+ def calculate( source, target )
19
+
20
+ if found = find_middle( source, target )
21
+
22
+ source_l, target_l, source, source_r, target_r = found
23
+ calculate source_l, target_l
24
+ @del_c += source.size
25
+ @add_c += source.size
26
+ calculate source_r, target_r
27
+
28
+ else
29
+
30
+ unless source.empty?
31
+ @del[ @del_c ] = source.size
32
+ end
33
+
34
+ unless target.empty?
35
+ @add[ @add_c ] = target
36
+ @add_c += target.size
37
+ end
38
+
39
+ end
40
+
41
+ end
42
+
43
+ def find_middle( source, target, min = 0, max = nil )
44
+
45
+ return nil if source.empty? || target.empty?
46
+
47
+ max = source.size unless max
48
+ size = min + ( ( max - min ) / 2.to_f ).round
49
+
50
+ subsets_each( source, size ) do |subset, first, last|
51
+
52
+ if found = target.index( subset )
53
+
54
+ return (
55
+ size != min && find_middle( source, target, size, max ) ||
56
+ (
57
+ source_l = source[ 0...first ]
58
+ target_l = target[ 0...found ]
59
+ source_r = source[ last...source.size ]
60
+ target_r = target[ found + subset.size...target.size ]
61
+ [ source_l, target_l, subset, source_r, target_r ]
62
+ )
63
+ )
64
+
65
+ end
66
+
67
+ end
68
+
69
+ size != max && find_middle( source, target, min, size ) || nil
70
+
71
+ end
72
+
73
+ def subsets_each( source, size )
74
+ ( source.size - size + 1 ).times do |first|
75
+ last = first + size
76
+ subset = source[ first...last ]
77
+ yield subset, first, last
78
+ end
79
+ end
80
+
81
+
82
+ end
83
+
84
+ end
@@ -0,0 +1,62 @@
1
+ module Apiway
2
+
3
+
4
+ class EventHandlerNotExists < StandardError
5
+
6
+ def initialize( name )
7
+ super "Event handler \"#{ name }\" not exists"
8
+ end
9
+
10
+ end
11
+
12
+
13
+ class ControllerNotExists < StandardError
14
+
15
+ def initialize( name )
16
+ super "\"#{ name }\" not exists"
17
+ end
18
+
19
+ end
20
+
21
+
22
+ class ControllerActionNotExists < StandardError
23
+
24
+ def initialize( controller_name, action_name )
25
+ super "Action \"#{ action_name }\" not exists in \"#{ controller_name }\""
26
+ end
27
+
28
+ end
29
+
30
+
31
+ class ResourceNotExists < StandardError
32
+
33
+ def initialize( name )
34
+ super "\"#{ name }\" not exists"
35
+ end
36
+
37
+ end
38
+
39
+
40
+ class ResourceError < StandardError
41
+
42
+ attr_reader :params
43
+
44
+ def initialize( params = nil )
45
+ @params = params
46
+ end
47
+
48
+ end
49
+
50
+
51
+ class ControllerError < StandardError
52
+
53
+ attr_reader :params
54
+
55
+ def initialize( params = nil )
56
+ @params = params
57
+ end
58
+
59
+ end
60
+
61
+
62
+ end
@@ -0,0 +1,23 @@
1
+ module Apiway
2
+
3
+
4
+ module API
5
+
6
+ ALIVE = 'alv'
7
+ QUERY = 'que'
8
+ SUCCESS = 'sus'
9
+ FAILURE = 'fai'
10
+ TRIGGER = 'trg'
11
+
12
+ end
13
+
14
+
15
+ module RESOURCE
16
+
17
+ SYNC = 'snc'
18
+ DESTROY = 'dst'
19
+
20
+ end
21
+
22
+
23
+ end
@@ -0,0 +1,29 @@
1
+ class Hash
2
+
3
+ def keys_to_sym!
4
+ self.keys.each do |key|
5
+ self[ key ].keys_to_sym! if self[ key ].is_a? Hash
6
+ self[ ( key.to_sym rescue key ) ] = self.delete key
7
+ end
8
+ self
9
+ end
10
+
11
+ end
12
+
13
+
14
+ class String
15
+
16
+ def underscore!
17
+ gsub!( /(.)([A-Z])/, '\1_\2' )
18
+ downcase!
19
+ end
20
+
21
+ def underscore
22
+ dup.tap { |s| s.underscore! }
23
+ end
24
+
25
+ def camelize
26
+ self.split( '_' ).collect( &:capitalize ).join
27
+ end
28
+
29
+ end