kenji 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/Gemfile +5 -0
- data/LICENSE +15 -0
- data/README.md +84 -0
- data/Rakefile +2 -0
- data/bin/kenji-init +36 -0
- data/inited/.gitignore +2 -0
- data/inited/Gemfile +4 -0
- data/inited/README.md +3 -0
- data/inited/__APP_NAME__ +52 -0
- data/inited/config.ru +11 -0
- data/inited/configuration/README +1 -0
- data/inited/controllers/main.rb +6 -0
- data/inited/lib/README +0 -0
- data/inited/models/README +1 -0
- data/inited/public/humans.txt +1 -0
- data/inited/public/index.html +19 -0
- data/inited/scripts/README +1 -0
- data/inited/tmp/README +1 -0
- data/inited/tmp/always_restart.txt +0 -0
- data/kenji.gemspec +24 -0
- data/lib/kenji/controller.rb +108 -0
- data/lib/kenji/string_extensions.rb +16 -0
- data/lib/kenji/version.rb +5 -0
- data/lib/kenji.rb +114 -0
- metadata +94 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
The Azure License
|
2
|
+
|
3
|
+
Copyright (c) 2011 Kenneth Ballenegger
|
4
|
+
|
5
|
+
Attribute to Kenneth Ballenegger - http://kswizz.com/
|
6
|
+
|
7
|
+
You (the licensee) are hereby granted permission, free of charge, to deal in this software or source code (this "Software") without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, and/or sublicense this Software, subject to the following conditions:
|
8
|
+
|
9
|
+
You must give attribution to the party mentioned above, by name and by hyperlink, in the about box, credits document and/or documentation of any derivative work using a substantial portion of this Software.
|
10
|
+
|
11
|
+
You may not use the name of the copyright holder(s) to endorse or promote products derived from this Software without specific prior written permission.
|
12
|
+
|
13
|
+
THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THIS SOFTWARE OR THE USE OR OTHER DEALINGS IN THIS SOFTWARE.
|
14
|
+
|
15
|
+
http://license.azuretalon.com/
|
data/README.md
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
*Project is still actively in development.*
|
2
|
+
|
3
|
+
|
4
|
+
# Kenji
|
5
|
+
|
6
|
+
Kenji is a lightweight backend framework for Ruby.
|
7
|
+
|
8
|
+
|
9
|
+
## Rationale
|
10
|
+
|
11
|
+
Kenji believes that a traditional web application should be divided into two parts: an client application running in the browser (HTML/JS/CSS), and a backend API with which it communicates. Kenji is the backend side of the equation, while the front-end architecture is left up to the user. (Popular options are [backbone][] and [spine][].)
|
12
|
+
|
13
|
+
[backbone]: http://documentcloud.github.com/backbone/
|
14
|
+
[spine]: http://spinejs.com/
|
15
|
+
|
16
|
+
Kenji believes that in order to keep clean and organized code, routes should be defined inline with their code.
|
17
|
+
|
18
|
+
Kenji believes that an app should be usable as a library from scripts or from the command line. An app should be automatable and testable.
|
19
|
+
|
20
|
+
Lastly, Kenji is opinionated, but only about things that directly pertain to routing and code architecture. Kenji believes in being a ligthweight module that only solves the problem it focuses on. Everything else is left up to the user. (ORM, data store, web server, message queue, front-end framework, deployment process, etc.)
|
21
|
+
|
22
|
+
|
23
|
+
### Routing
|
24
|
+
|
25
|
+
Kenji wants you to organize your code into logical units of code, aka. controllers. The controllers will automatically be selected based on the url requested, and the rest of the route is defined inline in the controller, with a domain-specific-language.
|
26
|
+
|
27
|
+
The canonical Hello World example for the URL `/hello/world` in Kenji would look like this, in `controller/hello.rb`:
|
28
|
+
|
29
|
+
````ruby
|
30
|
+
class HelloController < Kenji::Controller
|
31
|
+
get '/world' do
|
32
|
+
{hello: :world}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
````
|
36
|
+
|
37
|
+
A more representative example might be:
|
38
|
+
|
39
|
+
````ruby
|
40
|
+
class UserController < Kenji::Controller
|
41
|
+
|
42
|
+
# ...
|
43
|
+
|
44
|
+
get '/:id/friends' do |id|
|
45
|
+
# list friends for id
|
46
|
+
end
|
47
|
+
|
48
|
+
post '/:id/friend/:id' do |id, friend_id|
|
49
|
+
# add connection from user id to friend_id
|
50
|
+
end
|
51
|
+
|
52
|
+
delete '/:id/friend/:id' do |id, friend_id|
|
53
|
+
# delete connection from user id to friend_id
|
54
|
+
end
|
55
|
+
end
|
56
|
+
````
|
57
|
+
|
58
|
+
|
59
|
+
### Data Transport
|
60
|
+
|
61
|
+
JSON is used as the singular data transport for Kenji. Requests are assumed to have:
|
62
|
+
|
63
|
+
Content-Type: application/json; charset=utf-8
|
64
|
+
Accept: application/json; charset=utf-8
|
65
|
+
|
66
|
+
|
67
|
+
## Usage
|
68
|
+
|
69
|
+
Getting started with Kenji could not be any easier. All it takes is a few lines and a terminal:
|
70
|
+
|
71
|
+
$ gem install kenji # (once kenji is on the rubygems main source)
|
72
|
+
$ kenji-init app_name; cd app_name
|
73
|
+
$ rackup # launch the webserver
|
74
|
+
|
75
|
+
And already, your app is ready to go:
|
76
|
+
|
77
|
+
$ curl http://localhost:9292/hello/world
|
78
|
+
{"hello":"world"}
|
79
|
+
|
80
|
+
|
81
|
+
## Requirements & Assumptions
|
82
|
+
|
83
|
+
- Requires rubygems and bundler.
|
84
|
+
- Requires Ruby 1.9.
|
data/Rakefile
ADDED
data/bin/kenji-init
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
path = '.' << '/' << (ARGV.first || '.')
|
4
|
+
app_name = File.basename(path)
|
5
|
+
|
6
|
+
Dir.mkdir(path) unless File.directory?(path)
|
7
|
+
path = File.realpath(path)
|
8
|
+
|
9
|
+
init_source = File.realpath(File.dirname(File.dirname(__FILE__))) << '/inited'
|
10
|
+
|
11
|
+
for_earch_item = lambda do |item, base|
|
12
|
+
next if item == '.' || item == '..'
|
13
|
+
item_to_create = item.gsub('__APP_NAME__', app_name)
|
14
|
+
|
15
|
+
|
16
|
+
if File.directory?("#{init_source}/#{base}/#{item}")
|
17
|
+
Dir.mkdir("#{path}/#{base}/#{item_to_create}") unless File.directory?("#{path}/#{base}/#{item}")
|
18
|
+
puts "Initializing directory #{base}/#{item_to_create}"
|
19
|
+
Dir.foreach("#{init_source}/#{base}/#{item}") { |i| for_earch_item.call(i, "#{base}/#{item}") }
|
20
|
+
else
|
21
|
+
puts "Generating content for file #{base}/#{item_to_create}"
|
22
|
+
data = ''
|
23
|
+
f = File.open("#{init_source}/#{base}/#{item}", 'r')
|
24
|
+
while line = f.gets
|
25
|
+
data += (line.gsub('__APP_NAME__', app_name))
|
26
|
+
end
|
27
|
+
f.close
|
28
|
+
f = File.open("#{path}/#{base}/#{item_to_create}", 'w')
|
29
|
+
f.puts data
|
30
|
+
f.close
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
Dir.foreach(init_source) { |i| for_earch_item.call(i, '.') }
|
35
|
+
|
36
|
+
system("cd #{path}; bundle install")
|
data/inited/.gitignore
ADDED
data/inited/Gemfile
ADDED
data/inited/README.md
ADDED
data/inited/__APP_NAME__
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
class Main
|
4
|
+
|
5
|
+
def self.init
|
6
|
+
require 'rubygems'
|
7
|
+
require 'bundler/setup'
|
8
|
+
require $root+'/lib/kenji/kenji'
|
9
|
+
@@k = Kenji::Kenji.new({}, $root)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.main args
|
13
|
+
verb = args.first.to_sym unless args.empty?
|
14
|
+
|
15
|
+
# case verb
|
16
|
+
# when :import
|
17
|
+
# puts "Calling import script..."
|
18
|
+
# @@k.controller_for(:import)._caffeine @@k
|
19
|
+
# when :process
|
20
|
+
# puts "Processing values..."
|
21
|
+
# @@k.controller_for(:processing)._value @@k
|
22
|
+
# when :configure
|
23
|
+
# require $root + '/lib/configure'
|
24
|
+
# skip_update = (args[1] =~ /-?-?skip[-_]update/)
|
25
|
+
# AnalyticsModule.configure __FILE__, skip_update
|
26
|
+
# else
|
27
|
+
# puts <<-EOO
|
28
|
+
# No verb defined. Usage: ./main [verb]
|
29
|
+
#
|
30
|
+
# configure [--skip-update]:
|
31
|
+
# process the value of users
|
32
|
+
# optionally, skip the self-update process
|
33
|
+
# import:
|
34
|
+
# import install from caffeine.io
|
35
|
+
# process:
|
36
|
+
# process the value of users
|
37
|
+
# EOO
|
38
|
+
# end
|
39
|
+
end
|
40
|
+
|
41
|
+
def kenji
|
42
|
+
@@k
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
require 'pathname'
|
47
|
+
$root = File.dirname Pathname.new(__FILE__).realpath.to_s
|
48
|
+
|
49
|
+
Main.init unless ARGV.first == 'configure'
|
50
|
+
if __FILE__ == $0
|
51
|
+
Main.main ARGV
|
52
|
+
end
|
data/inited/config.ru
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
This is where configuration goes.
|
data/inited/lib/README
ADDED
File without changes
|
@@ -0,0 +1 @@
|
|
1
|
+
This is where models go.
|
@@ -0,0 +1 @@
|
|
1
|
+
Handcrafted with love by Kenneth Ballenegger
|
@@ -0,0 +1,19 @@
|
|
1
|
+
<html>
|
2
|
+
<head>
|
3
|
+
<title>Hello, world!</title>
|
4
|
+
|
5
|
+
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" type="text/javascript" charset="utf-8"></script>
|
6
|
+
<script type="text/javascript" charset="utf-8">
|
7
|
+
$(document).ready(function() {
|
8
|
+
$.ajax('/main/index.json', {
|
9
|
+
success: function(data) {
|
10
|
+
$('#name').html(data.hello);
|
11
|
+
}
|
12
|
+
});
|
13
|
+
});
|
14
|
+
</script>
|
15
|
+
</head>
|
16
|
+
<body>
|
17
|
+
Hello, <span id="name">User</span>!
|
18
|
+
</body>
|
19
|
+
</html>
|
@@ -0,0 +1 @@
|
|
1
|
+
This is where scripts go.
|
data/inited/tmp/README
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
This folder is for Rack & Passenger to use.
|
File without changes
|
data/kenji.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
$:.push File.expand_path('../lib', __FILE__)
|
2
|
+
require 'kenji/version'
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = 'kenji'
|
6
|
+
s.version = Kenji::VERSION
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.date = '2012-04-05'
|
9
|
+
s.summary = 'Kenji'
|
10
|
+
s.description = 'A lightweight Ruby web framework.'
|
11
|
+
s.authors = ['Kenneth Ballenegger']
|
12
|
+
s.email = ['kenneth@ballenegger.com']
|
13
|
+
s.homepage =
|
14
|
+
'https://github.com/kballenegger/kenji'
|
15
|
+
|
16
|
+
s.add_dependency 'json'
|
17
|
+
s.add_dependency 'rack'
|
18
|
+
|
19
|
+
s.files = `git ls-files`.split("\n")
|
20
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
21
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
22
|
+
s.require_paths = ['lib']
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
|
2
|
+
module Kenji
|
3
|
+
class Controller
|
4
|
+
|
5
|
+
# use the reader freely to grab the kenji object
|
6
|
+
attr_accessor :kenji
|
7
|
+
|
8
|
+
# Routes below will accept routes in the format, eg.:
|
9
|
+
# /hello/:id/children
|
10
|
+
# Can contain any number of :id, but must be in their own url segment.
|
11
|
+
# Colon-prefixed segments become variables, passed onto the given block as arguments.
|
12
|
+
# The name given to the variable is irrelevant, and is thrown away: /hello/:/children is equivalent to the example above.
|
13
|
+
# Given block must have correct arity.
|
14
|
+
|
15
|
+
# Route GET
|
16
|
+
def self.get(path, &block)
|
17
|
+
route(:get, path, &block)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Route POST
|
21
|
+
def self.post(path, &block)
|
22
|
+
route(:post, path, &block)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Route PUT
|
26
|
+
def self.put(path, &block)
|
27
|
+
route(:put, path, &block)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Route DELETE
|
31
|
+
def self.delete(path, &block)
|
32
|
+
route(:delete, path, &block)
|
33
|
+
end
|
34
|
+
|
35
|
+
# TODO: figure out whether I want PATCH, OPTIONS, HEAD
|
36
|
+
|
37
|
+
# Route all methods for given path
|
38
|
+
def self.all(path, &block)
|
39
|
+
route(:get, :post, :put, :delete, path, &block)
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.fallback(&block)
|
43
|
+
define_method(:fallback, &block)
|
44
|
+
nil # void method
|
45
|
+
end
|
46
|
+
|
47
|
+
# Route a given path to the correct block, for any given methods
|
48
|
+
#
|
49
|
+
# Note: this works by building a tree for the path,
|
50
|
+
# each node being a path segment or variable segment, and the leaf @action being the block
|
51
|
+
|
52
|
+
def self.route(*methods, path, &block)
|
53
|
+
# bind the block to self as an instance method, so its context is correct
|
54
|
+
define_method(:_tmp_route_action, &block)
|
55
|
+
block = instance_method(:_tmp_route_action)
|
56
|
+
remove_method(:_tmp_route_action)
|
57
|
+
# store the block for each method
|
58
|
+
methods.each do |method|
|
59
|
+
node = ((@routes ||= {})[method] ||= {})
|
60
|
+
segments = path.split('/')
|
61
|
+
segments = segments.drop(1) if segments.first == '' # discard leading /'s empty segment
|
62
|
+
segments.each do |segment| # lazily create tree
|
63
|
+
segment = ':' if segment =~ /^:/ # discard :variable name
|
64
|
+
node = (node[segment.to_sym] ||= {})
|
65
|
+
end
|
66
|
+
node[:@action] = block # store block as leaf in @action
|
67
|
+
end
|
68
|
+
nil # void method
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
# Most likely only used by Kenji itself.
|
73
|
+
# Override to implement your own routing, if you'd like.
|
74
|
+
def call(method, path)
|
75
|
+
segments = path.split('/')
|
76
|
+
segments = segments.drop(1) if segments.first == '' # discard leading /'s empty segment
|
77
|
+
node = self.class.routes[method]
|
78
|
+
variables = []
|
79
|
+
segments.each do |segment| # traverse tree to find
|
80
|
+
if node[segment.to_sym]
|
81
|
+
node = node[segment.to_sym] # attempt to move down to the plain text segment
|
82
|
+
else
|
83
|
+
node = node[:':'] # attempt to find a variable segment
|
84
|
+
variables << segment # either we've found a variable, or the `unless` below will trigger
|
85
|
+
end
|
86
|
+
break unless node # break if as an instance method nil, fallback below
|
87
|
+
end
|
88
|
+
if node && action = node[:@action] # the block is stored in the @action leaf
|
89
|
+
return action.bind(self).call(*variables)
|
90
|
+
else # or, fallback if necessary store the block for each method
|
91
|
+
if respond_to? :fallback
|
92
|
+
if self.class.instance_method(:fallback).arity == 1
|
93
|
+
return fallback(path)
|
94
|
+
else
|
95
|
+
return fallback
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
# Accessor for @routes
|
103
|
+
def self.routes
|
104
|
+
@routes || {}
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# Kenji string extensions
|
2
|
+
|
3
|
+
class String
|
4
|
+
def to_underscore!
|
5
|
+
self.gsub!(/(.)([A-Z])/,'\1_\2').downcase!
|
6
|
+
end
|
7
|
+
def to_underscore
|
8
|
+
self.clone.to_underscore!
|
9
|
+
end
|
10
|
+
def to_camelcase!
|
11
|
+
self.replace self.split('_').each{ |s| s.capitalize! }.join('')
|
12
|
+
end
|
13
|
+
def to_camelcase
|
14
|
+
self.clone.to_camelcase!
|
15
|
+
end
|
16
|
+
end
|
data/lib/kenji.rb
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'kenji/controller'
|
3
|
+
require 'kenji/string_extensions'
|
4
|
+
require 'rack'
|
5
|
+
|
6
|
+
module Kenji
|
7
|
+
class Kenji
|
8
|
+
|
9
|
+
attr_reader :env, :root
|
10
|
+
|
11
|
+
def initialize(env, root)
|
12
|
+
@headers = {
|
13
|
+
'Content-Type' => 'application/json'
|
14
|
+
}
|
15
|
+
@status = 200
|
16
|
+
@root = File.expand_path(root) + '/'
|
17
|
+
@env = env
|
18
|
+
end
|
19
|
+
|
20
|
+
def call
|
21
|
+
path = @env['PATH_INFO']
|
22
|
+
|
23
|
+
# deal with static files
|
24
|
+
static = "#{@root}public#{path}"
|
25
|
+
return Rack::File.new("#{@root}public").call(@env) if File.file?(static)
|
26
|
+
|
27
|
+
|
28
|
+
# new routing code
|
29
|
+
segments = path.split('/')
|
30
|
+
segments = segments.drop(1) if segments.first == '' # discard leading /'s empty segment
|
31
|
+
|
32
|
+
acc = ''; out = {}
|
33
|
+
while head = segments.shift
|
34
|
+
acc = "#{acc}/#{head}"
|
35
|
+
if controller = controller_for(acc) # if we have a valid controller
|
36
|
+
begin
|
37
|
+
out = controller.call(@env['REQUEST_METHOD'].downcase.to_sym, '/'+segments.join('/')).to_json
|
38
|
+
rescue KenjiRespondControlFlowInterrupt => e
|
39
|
+
out = e.response
|
40
|
+
end
|
41
|
+
break
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
[@status, @headers, [out]]
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
|
50
|
+
# Methods for users!
|
51
|
+
|
52
|
+
|
53
|
+
# Sets one or multiple headers, as named arametres. eg.
|
54
|
+
#
|
55
|
+
# kenji.header 'Content-Type' => 'hello/world'
|
56
|
+
def header(hash={})
|
57
|
+
hash.each do |key, value|
|
58
|
+
@headers[key] = value
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Fetch (and cache) the json input to the request
|
63
|
+
# Return a Hash
|
64
|
+
def input_as_json
|
65
|
+
return @json_input if @json_input
|
66
|
+
require 'json'
|
67
|
+
raw = @env['rack.input'].read if @env['rack.input']
|
68
|
+
begin
|
69
|
+
return @json_input = JSON.parse(raw)
|
70
|
+
rescue JSON::ParserError => e
|
71
|
+
end if raw
|
72
|
+
{} # default return value
|
73
|
+
end
|
74
|
+
|
75
|
+
# Respond to the request
|
76
|
+
def respond code, message, hash={}
|
77
|
+
@status = code
|
78
|
+
response = { # default structure. TODO: figure out if i really want to keep this
|
79
|
+
:status => code,
|
80
|
+
:message => message
|
81
|
+
}
|
82
|
+
hash.each { |k,v| response[k]=v }
|
83
|
+
raise KenjiRespondControlFlowInterrupt.new(response.to_json)
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
|
88
|
+
# Private methods
|
89
|
+
|
90
|
+
# Will attempt to fetch the controller, and verify that it is a implements call
|
91
|
+
def controller_for subpath
|
92
|
+
path = "#{@root}controllers#{subpath}.rb"
|
93
|
+
return nil unless File.exists?(path)
|
94
|
+
require path
|
95
|
+
controller_name = subpath.split('/').last
|
96
|
+
controller_class = Object.const_get(controller_name.to_s.to_camelcase+'Controller')
|
97
|
+
return unless controller_class.method_defined?(:call) && controller_class.instance_method(:call).arity == 2 # ensure protocol compliance
|
98
|
+
controller = controller_class.new
|
99
|
+
controller.kenji = self if controller.respond_to?(:kenji=)
|
100
|
+
return controller if controller
|
101
|
+
nil # default return value
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
class KenjiRespondControlFlowInterrupt < StandardError
|
108
|
+
attr_accessor :response
|
109
|
+
def initialize(response)
|
110
|
+
@response = response
|
111
|
+
end
|
112
|
+
end # early exit containing a response
|
113
|
+
end
|
114
|
+
|
metadata
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: kenji
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Kenneth Ballenegger
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-04-05 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: json
|
16
|
+
requirement: &70195188110740 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70195188110740
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rack
|
27
|
+
requirement: &70195188110000 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70195188110000
|
36
|
+
description: A lightweight Ruby web framework.
|
37
|
+
email:
|
38
|
+
- kenneth@ballenegger.com
|
39
|
+
executables:
|
40
|
+
- kenji-init
|
41
|
+
extensions: []
|
42
|
+
extra_rdoc_files: []
|
43
|
+
files:
|
44
|
+
- .gitignore
|
45
|
+
- Gemfile
|
46
|
+
- LICENSE
|
47
|
+
- README.md
|
48
|
+
- Rakefile
|
49
|
+
- bin/kenji-init
|
50
|
+
- inited/.gitignore
|
51
|
+
- inited/Gemfile
|
52
|
+
- inited/README.md
|
53
|
+
- inited/__APP_NAME__
|
54
|
+
- inited/config.ru
|
55
|
+
- inited/configuration/README
|
56
|
+
- inited/controllers/main.rb
|
57
|
+
- inited/lib/README
|
58
|
+
- inited/models/README
|
59
|
+
- inited/public/humans.txt
|
60
|
+
- inited/public/index.html
|
61
|
+
- inited/scripts/README
|
62
|
+
- inited/tmp/README
|
63
|
+
- inited/tmp/always_restart.txt
|
64
|
+
- kenji.gemspec
|
65
|
+
- lib/kenji.rb
|
66
|
+
- lib/kenji/controller.rb
|
67
|
+
- lib/kenji/string_extensions.rb
|
68
|
+
- lib/kenji/version.rb
|
69
|
+
homepage: https://github.com/kballenegger/kenji
|
70
|
+
licenses: []
|
71
|
+
post_install_message:
|
72
|
+
rdoc_options: []
|
73
|
+
require_paths:
|
74
|
+
- lib
|
75
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
76
|
+
none: false
|
77
|
+
requirements:
|
78
|
+
- - ! '>='
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
81
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
82
|
+
none: false
|
83
|
+
requirements:
|
84
|
+
- - ! '>='
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
requirements: []
|
88
|
+
rubyforge_project:
|
89
|
+
rubygems_version: 1.8.10
|
90
|
+
signing_key:
|
91
|
+
specification_version: 3
|
92
|
+
summary: Kenji
|
93
|
+
test_files: []
|
94
|
+
has_rdoc:
|