silicon 0.2.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.
Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +5 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +372 -0
  8. data/Rakefile +6 -0
  9. data/bin/console +7 -0
  10. data/bin/setup +8 -0
  11. data/exe/silicon +5 -0
  12. data/lib/silicon.rb +6 -0
  13. data/lib/silicon/app.rb +90 -0
  14. data/lib/silicon/base/handle_errors.rb +16 -0
  15. data/lib/silicon/chain.rb +69 -0
  16. data/lib/silicon/chain_factory.rb +39 -0
  17. data/lib/silicon/config.rb +15 -0
  18. data/lib/silicon/errors/silicon_error.rb +4 -0
  19. data/lib/silicon/errors/syntax_error.rb +6 -0
  20. data/lib/silicon/errors/view_engine_error.rb +6 -0
  21. data/lib/silicon/extensions/hash.rb +18 -0
  22. data/lib/silicon/generators/cli.rb +23 -0
  23. data/lib/silicon/generators/templates/actions/common/handle_errors.rb +2 -0
  24. data/lib/silicon/generators/templates/actions/welcome.tt +9 -0
  25. data/lib/silicon/generators/templates/app.rb +4 -0
  26. data/lib/silicon/generators/templates/app.routes +4 -0
  27. data/lib/silicon/generators/templates/config.ru +8 -0
  28. data/lib/silicon/generators/templates/silicon.yml +7 -0
  29. data/lib/silicon/generators/templates/views/show_welcome.json.jbuilder +1 -0
  30. data/lib/silicon/loaders/dependency_loader.rb +14 -0
  31. data/lib/silicon/loaders/template_loader.rb +20 -0
  32. data/lib/silicon/loaders/type_loader.rb +25 -0
  33. data/lib/silicon/request.rb +65 -0
  34. data/lib/silicon/routing/builder.rb +96 -0
  35. data/lib/silicon/routing/file_reader.rb +27 -0
  36. data/lib/silicon/routing/match.rb +12 -0
  37. data/lib/silicon/routing/matcher.rb +40 -0
  38. data/lib/silicon/routing/parser.rb +25 -0
  39. data/lib/silicon/routing/route.rb +19 -0
  40. data/lib/silicon/routing/routing.rb +11 -0
  41. data/lib/silicon/routing/syntax.rb +28 -0
  42. data/lib/silicon/routing/syntax/action.rb +30 -0
  43. data/lib/silicon/routing/syntax/actions.rb +15 -0
  44. data/lib/silicon/routing/syntax/after_section.rb +19 -0
  45. data/lib/silicon/routing/syntax/before_section.rb +18 -0
  46. data/lib/silicon/routing/syntax/catch_section.rb +15 -0
  47. data/lib/silicon/routing/syntax/command.rb +27 -0
  48. data/lib/silicon/routing/syntax/commands.rb +7 -0
  49. data/lib/silicon/routing/syntax/node.rb +47 -0
  50. data/lib/silicon/routing/syntax/nodes.rb +13 -0
  51. data/lib/silicon/routing/syntax/primitives/arrow.rb +4 -0
  52. data/lib/silicon/routing/syntax/primitives/back_arrow.rb +4 -0
  53. data/lib/silicon/routing/syntax/primitives/eol.rb +4 -0
  54. data/lib/silicon/routing/syntax/primitives/http_status.rb +13 -0
  55. data/lib/silicon/routing/syntax/primitives/http_verb.rb +4 -0
  56. data/lib/silicon/routing/syntax/primitives/indent.rb +4 -0
  57. data/lib/silicon/routing/syntax/primitives/parameter.rb +4 -0
  58. data/lib/silicon/routing/syntax/primitives/path.rb +5 -0
  59. data/lib/silicon/routing/syntax/respond.rb +25 -0
  60. data/lib/silicon/routing/syntax/route.rb +25 -0
  61. data/lib/silicon/routing/syntax/sections.rb +19 -0
  62. data/lib/silicon/routing/syntax/tree_section.rb +11 -0
  63. data/lib/silicon/routing/syntax/view.rb +7 -0
  64. data/lib/silicon/routing/syntax_error_interpreter.rb +28 -0
  65. data/lib/silicon/routing/syntax_grammar.tt +95 -0
  66. data/lib/silicon/template_registry.rb +17 -0
  67. data/lib/silicon/version.rb +3 -0
  68. data/lib/silicon/view_builder.rb +18 -0
  69. data/lib/silicon/view_builder_registry.rb +21 -0
  70. data/lib/silicon/view_builders/json.rb +16 -0
  71. data/lib/silicon/view_factory.rb +21 -0
  72. data/silicon.gemspec +45 -0
  73. metadata +272 -0
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/exe/silicon ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'silicon/generators/cli'
4
+
5
+ Silicon::CLI.start ARGV
data/lib/silicon.rb ADDED
@@ -0,0 +1,6 @@
1
+ require 'silicon/version'
2
+ require 'silicon/app'
3
+ require 'silicon/base/handle_errors'
4
+
5
+ module Silicon
6
+ end
@@ -0,0 +1,90 @@
1
+ require 'hypo'
2
+ require 'json'
3
+ require 'rack/parser'
4
+ require 'silicon/loaders/type_loader'
5
+ require 'silicon/loaders/dependency_loader'
6
+ require 'silicon/loaders/template_loader'
7
+ require 'silicon/config'
8
+ require 'silicon/request'
9
+ require 'silicon/template_registry'
10
+ require 'silicon/view_builder_registry'
11
+ require 'silicon/view_factory'
12
+ require 'silicon/chain_factory'
13
+ require 'silicon/view_builders/json'
14
+ require 'silicon/routing/routing'
15
+
16
+ module Silicon
17
+ class App
18
+ def initialize(container = Hypo::Container.new)
19
+ @container = container
20
+
21
+ register_dependencies
22
+ load_basic_dependencies
23
+ parse_routes
24
+ add_view_builders
25
+ end
26
+
27
+ def call(env)
28
+ request = Request.new(@route_matcher, @container, @chain_factory, @view_factory)
29
+ request.handle(env)
30
+ end
31
+
32
+ protected
33
+
34
+ def add_view_builder(view_builder, format)
35
+ @view_builder_registry.add(view_builder, format)
36
+ end
37
+
38
+ private
39
+
40
+ def add_view_builders
41
+ add_view_builder(ViewBuilders::Json, 'json')
42
+ end
43
+
44
+ def register_dependencies
45
+ instances = {
46
+ config_file_path: 'silicon.yaml'
47
+ }
48
+
49
+ instances.keys.each do |name|
50
+ @container.register_instance(instances[name], name)
51
+ end
52
+
53
+ singletons = {
54
+ silicon_config: Config,
55
+ type_loader: TypeLoader,
56
+ dependency_loader: DependencyLoader,
57
+ template_loader: TemplateLoader,
58
+ template_registry: TemplateRegistry,
59
+ view_builder_registry: ViewBuilderRegistry,
60
+ view_factory: ViewFactory,
61
+ chain_factory: ChainFactory,
62
+ file_reader: Routing::FileReader,
63
+ syntax_parser: SyntaxParser,
64
+ syntax_error_interpreter: Routing::SyntaxErrorInterpreter,
65
+ routes_builder: Routing::Builder,
66
+ route_parser: Routing::Parser
67
+ }
68
+
69
+ singletons.keys.each do |name|
70
+ @container.register(singletons[name], name)
71
+ .using_lifetime(:singleton)
72
+ end
73
+ end
74
+
75
+ def load_basic_dependencies
76
+ @chain_factory = @container.resolve(:chain_factory)
77
+ @view_factory = @container.resolve(:view_factory)
78
+ @dependency_loader = @container.resolve(:dependency_loader)
79
+ @view_builder_registry = @container.resolve(:view_builder_registry)
80
+ @route_parser = @container.resolve(:route_parser)
81
+
82
+ @dependency_loader.load_components
83
+ end
84
+
85
+ def parse_routes
86
+ routes = @route_parser.parse
87
+ @route_matcher = Routing::Matcher.new(routes)
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,16 @@
1
+ module Silicon
2
+ class HandleErrors
3
+ def initialize(container, silicon_error)
4
+ @container = container
5
+ @silicon_error = silicon_error
6
+ end
7
+
8
+ protected
9
+ def set_http_status(status_code)
10
+ @container
11
+ .register_instance(status_code, :silicon_status)
12
+ .using_lifetime(:scope)
13
+ .bound_to(:silicon_request)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,69 @@
1
+ require 'timeout'
2
+
3
+ module Silicon
4
+ class Chain
5
+ def initialize(container, silicon_config, commands, catch_command = nil)
6
+ @commands = commands
7
+ @container = container
8
+ @catch_command = catch_command
9
+ @async_timeout = silicon_config[:action][:async_timeout]
10
+ end
11
+
12
+ def execute
13
+ if @catch_command.nil?
14
+ run_actions
15
+ else
16
+ begin
17
+ run_actions
18
+ rescue Exception => error
19
+ @container
20
+ .register_instance(error, :silicon_error)
21
+ .using_lifetime(:scope)
22
+ .bound_to(:silicon_request)
23
+
24
+ action = @container.resolve(@catch_command.name.to_sym)
25
+ action.call
26
+ end
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def run_actions
33
+ threads = []
34
+ Thread.abort_on_exception = true
35
+ @commands.each_with_index do |command, index|
36
+ if command.sequential?
37
+ # all previous parallel commands should be done before the current sequential
38
+ threads.each {|t| t.join}
39
+ threads = []
40
+
41
+ run_action(command.name)
42
+ else
43
+ thread = Thread.new {
44
+ Timeout::timeout(@async_timeout) {
45
+ run_action(command.name)
46
+ }
47
+ }
48
+ threads << thread if command.parallel?
49
+ end
50
+
51
+ # if it's last item in chain then wait until parallel commands are done
52
+ if index == @commands.length - 1
53
+ threads.each {|t| t.join}
54
+ end
55
+ end
56
+ end
57
+
58
+ def run_action(name)
59
+ action = @container.resolve(name.to_sym)
60
+ result = action.call
61
+ result_name = action.respond_to?(:result_name) ? action.result_name : "#{name}_result"
62
+
63
+ @container
64
+ .register_instance(result, result_name.to_sym)
65
+ .using_lifetime(:scope)
66
+ .bound_to(:silicon_request)
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,39 @@
1
+ require 'silicon/chain'
2
+
3
+ module Silicon
4
+ class ChainFactory
5
+ def initialize(container, silicon_config)
6
+ @container = container
7
+ @silicon_config = silicon_config
8
+ end
9
+
10
+ def create(match)
11
+ status = match.route.http_status || default_http_status(match.route.http_verb)
12
+ register_params(match.params)
13
+ register_status(status)
14
+ Chain.new(@container, @silicon_config, match.route.commands, match.route.catch)
15
+ end
16
+
17
+ private
18
+
19
+ def register_params(params)
20
+ params.keys.each do |key|
21
+ @container
22
+ .register_instance(params[key], key.to_sym)
23
+ .using_lifetime(:scope)
24
+ .bound_to(:silicon_request)
25
+ end
26
+ end
27
+
28
+ def register_status(status)
29
+ @container
30
+ .register_instance(status, :silicon_status)
31
+ .using_lifetime(:scope)
32
+ .bound_to(:silicon_request)
33
+ end
34
+
35
+ def default_http_status(http_verb)
36
+ http_verb == 'POST' ? 201 : 200
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,15 @@
1
+ require 'yaml'
2
+ require 'silicon/extensions/hash'
3
+
4
+ module Silicon
5
+ class Config
6
+ def initialize(config_file_path)
7
+ path = File.join(Dir.pwd, config_file_path)
8
+ @params = YAML.load_file(path).deep_symbolize_keys!
9
+ end
10
+
11
+ def [](key)
12
+ @params[key]
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,4 @@
1
+ module Silicon
2
+ class SiliconError < StandardError
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ require 'silicon/errors/silicon_error'
2
+
3
+ module Silicon
4
+ class SyntaxError < SiliconError
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ require 'silicon/errors/silicon_error'
2
+
3
+ module Silicon
4
+ class ViewEngineError < SiliconError
5
+ end
6
+ end
@@ -0,0 +1,18 @@
1
+ class Hash
2
+ # simplecov incorrectly covers activesupport variants of these overloads.
3
+ # Actually both methods are covered by unit tests.
4
+
5
+ # :nocov:
6
+ def symbolize_keys
7
+ Hash[map{|(k,v)| [k.to_sym,v]}]
8
+ end
9
+
10
+ def deep_symbolize_keys!
11
+ keys.each do |key|
12
+ val = delete(key)
13
+ self[(key.to_sym rescue key)] = val.is_a?(Hash) || val.is_a?(Array) ? val.deep_symbolize_keys! : val
14
+ end
15
+ self
16
+ end
17
+ # :nocov:
18
+ end
@@ -0,0 +1,23 @@
1
+ require 'thor'
2
+
3
+ module Silicon
4
+ class CLI < Thor
5
+ include Thor::Actions
6
+
7
+ def self.source_root
8
+ File.dirname(__FILE__)
9
+ end
10
+
11
+ desc 'new NAME', 'create new Silicon application'
12
+ def new(name)
13
+ copy_file 'templates/app.rb', "#{name}/app/app.rb"
14
+ copy_file 'templates/app.routes', "#{name}/app/app.routes"
15
+ copy_file 'templates/silicon.yml', "#{name}/silicon.yml"
16
+ copy_file 'templates/config.ru', "#{name}/config.ru"
17
+ copy_file 'templates/views/show_welcome.json.jbuilder', "#{name}/app/views/show_welcome.json.jbuilder"
18
+ copy_file 'templates/actions/common/handle_errors.rb', "#{name}/app/actions/common/handle_errors.rb"
19
+ template 'templates/actions/welcome.tt', "#{name}/app/actions/welcome.rb", {app_name: name}
20
+ end
21
+ end
22
+ end
23
+
@@ -0,0 +1,2 @@
1
+ class HandleErrors < Silicon::HandleErrors
2
+ end
@@ -0,0 +1,9 @@
1
+ class Welcome
2
+ def call
3
+ "Welcome to <%= config[:app_name] %>!"
4
+ end
5
+
6
+ def result_name
7
+ 'message'
8
+ end
9
+ end
@@ -0,0 +1,4 @@
1
+ require 'silicon'
2
+
3
+ class App < Silicon::App
4
+ end
@@ -0,0 +1,4 @@
1
+ :receive
2
+ .->
3
+ GET -> welcome -> :respond <- show_welcome
4
+ :catch -> handle_errors
@@ -0,0 +1,8 @@
1
+ require 'rack'
2
+ require './app'
3
+
4
+ use Rack::Parser, :content_types => {
5
+ 'application/json' => Proc.new { |body| ::MultiJson.decode body }
6
+ }
7
+
8
+ run App.new
@@ -0,0 +1,7 @@
1
+ path:
2
+ dependencies:
3
+ - app/actions
4
+ views: app/views
5
+ routes: app/app.routes
6
+ action:
7
+ async_timeout: 10 # in seconds
@@ -0,0 +1 @@
1
+ json.message @message
@@ -0,0 +1,14 @@
1
+ module Silicon
2
+ class DependencyLoader
3
+ def initialize(container, type_loader)
4
+ @container = container
5
+ @types = type_loader.load_types
6
+ end
7
+
8
+ def load_components
9
+ @types.each do |type|
10
+ @container.register(type).using_lifetime(:scope).bound_to(:silicon_request)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,20 @@
1
+ module Silicon
2
+ class TemplateLoader
3
+ def initialize(silicon_config)
4
+ @directory = File.join(silicon_config[:path][:views], '**/*')
5
+ end
6
+
7
+ def load_templates
8
+ result = {}
9
+
10
+ files = Dir.glob(@directory).reject {|file_path| File.directory? file_path}
11
+ files.each do |file|
12
+ path = File.join Dir.pwd, file
13
+ content = File.read(path)
14
+ result[file] = content
15
+ end
16
+
17
+ result
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,25 @@
1
+ module Silicon
2
+ class TypeLoader
3
+ def initialize(silicon_config)
4
+ @directories = silicon_config[:path][:dependencies]
5
+ end
6
+
7
+ def load_types
8
+ types = []
9
+ @directories.each do |directory|
10
+ dir = File.join(directory, '**/*')
11
+ files = Dir.glob(dir).reject {|file_path| File.directory? file_path}
12
+
13
+ files.each do |file|
14
+ path = File.join Dir.pwd, file
15
+ require path
16
+ file_name = File.basename(file).gsub(File.extname(file), '')
17
+ class_name = file_name.split('_').each(&:capitalize!).join('')
18
+ types << Object.const_get(class_name)
19
+ end
20
+ end
21
+
22
+ types
23
+ end
24
+ end
25
+ end