rascut 0.1.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.
data/History.txt ADDED
@@ -0,0 +1,4 @@
1
+ == 0.1.0 / 2007-09-02
2
+
3
+ * 1st Release.
4
+
data/Manifest.txt ADDED
@@ -0,0 +1,18 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ bin/rascut
6
+ lib/rascut.rb
7
+ lib/rascut/command.rb
8
+ lib/rascut/config.rb
9
+ lib/rascut/fcsh_wrapper.rb
10
+ lib/rascut/file_observer.rb
11
+ lib/rascut/httpd.rb
12
+ lib/rascut/logger.rb
13
+ lib/rascut/plugin/base.rb
14
+ lib/rascut/plugin/write_fcsh_error_output.rb
15
+ vendor/ruby/expect.rb
16
+ vendor/js/swfobject.js
17
+ test/test_rascut.rb
18
+ test/test_file_observer.rb
data/README.txt ADDED
@@ -0,0 +1,59 @@
1
+ rascut
2
+ by Yuichi Tateno
3
+
4
+ == DESCRIPTION:
5
+
6
+ Rascut is Ruby ActionSCript UTility :D
7
+
8
+ == FEATURES/PROBLEMS:
9
+
10
+
11
+ == SYNOPSIS:
12
+
13
+ * 1. flex2sdk and fcwrap setting.
14
+ * 2. $ rascut -s MyScript.as
15
+ * 3. Browser access http://localhost:3001/
16
+
17
+ == REQUIREMENTS:
18
+
19
+ * hoe
20
+ * rack
21
+ * mongrel
22
+ * rake
23
+
24
+ == INSTALL:
25
+
26
+ * gem install rascut
27
+
28
+ == OTHER:
29
+ * vendor/js/swfobject.js is SWFObject by http://blog.deconcept.com/swfobject/
30
+
31
+ == LICENSE:
32
+
33
+ (The MIT License)
34
+
35
+ Copyright (c) 2007 Yuichi Tateno
36
+
37
+ Permission is hereby granted, free of charge, to any person obtaining
38
+ a copy of this software and associated documentation files (the
39
+ 'Software'), to deal in the Software without restriction, including
40
+ without limitation the rights to use, copy, modify, merge, publish,
41
+ distribute, sublicense, and/or sell copies of the Software, and to
42
+ permit persons to whom the Software is furnished to do so, subject to
43
+ the following conditions:
44
+
45
+ The above copyright notice and this permission notice shall be
46
+ included in all copies or substantial portions of the Software.
47
+
48
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
49
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
50
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
51
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
52
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
53
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
54
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
55
+
56
+ == SPECIAL THANKS
57
+ Thank you feedback!
58
+
59
+ id:os0x, yugui, id:wanpark, saqoosha, id:ameema
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+ $LOAD_PATH << './lib'
6
+ require './lib/rascut.rb'
7
+
8
+ Hoe.new('rascut', Rascut::VERSION) do |p|
9
+ p.rubyforge_name = 'hotchpotch'
10
+ p.author = 'yuichi tateno'
11
+ p.email = 'hotchpotch@nononospam@gmail.com'
12
+ p.summary = 'Ruby ActionSCript UTility'
13
+ p.description = p.paragraphs_of('README.txt', 2..5).join("\n\n")
14
+ p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[1..-1]
15
+ p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
16
+
17
+ p.extra_deps << ['mongrel']
18
+ p.extra_deps << ['rake']
19
+ p.extra_deps << ['hoe']
20
+ p.extra_deps << ['rack']
21
+ end
22
+
23
+ # vim: syntax=Ruby
data/bin/rascut ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ begin
4
+ require 'rascut'
5
+ rescue LoadError
6
+ require 'rubygems'
7
+ require 'rascut'
8
+ end
9
+
10
+ Rascut::Command.new.run ARGV
@@ -0,0 +1,148 @@
1
+ require 'optparse'
2
+ require 'rascut/fcsh_wrapper'
3
+ require 'rascut/logger'
4
+ require 'rascut/config'
5
+ require 'pathname'
6
+ require 'yaml'
7
+
8
+ module Rascut
9
+ class Command
10
+ def initialize
11
+ @logger = Logger.new(STDOUT)
12
+ end
13
+ attr_accessor :logger
14
+
15
+ def run(argv)
16
+ @config = Config.new
17
+
18
+ if ENV['HOME']
19
+ home = Pathname.new ENV['HOME']
20
+ end
21
+
22
+ home.join('.rascut').mkpath if home
23
+
24
+ if home && home.join('.rascutrc').readable?
25
+ @config.merge_config home.join('.rascutrc')
26
+ end
27
+
28
+ if File.readable?('.rascut')
29
+ @config.merge_config('.rascut')
30
+ end
31
+
32
+ @config.parse_argv!(argv)
33
+
34
+ unless @target_script = argv.first
35
+ warn 'Target script is not found.'
36
+ Kernel::exit 1
37
+ end
38
+
39
+ @root = Pathname.new(@target_script).dirname.realpath
40
+ @wrapper = FcshWrapper.new(@target_script, @config)
41
+
42
+ start_server if @config[:server]
43
+ setting_signals
44
+ @wrapper.hooks[:compile_success] << method(:compile_success_proc)
45
+
46
+
47
+ if @config[:file_observing]
48
+ @file_observer = FileObserver.new(@config[:observe_files],
49
+ :interval => @config[:interval],
50
+ :ext => @config[:ext],
51
+ :logger => @config[:logger],
52
+ :update_handler => method(:file_update_handler))
53
+ @file_observer.run
54
+ end
55
+
56
+ read_log_loop if @config[:flashlog]
57
+
58
+ init_plugins
59
+
60
+ @wrapper.compile
61
+ #readline_loop
62
+ Thread.stop
63
+ end
64
+ attr_reader :config, :root, :target_script, :wrapper, :file_observer
65
+
66
+ def init_plugins
67
+ @config[:plugin].each do |name|
68
+ klass_name = name.gsub(/(^|_)(.)/) { $2.upcase }
69
+ logger.info "Load Plugin: #{klass_name}"
70
+ require "rascut/plugin/#{name}"
71
+ ::Rascut::Plugin.const_get(klass_name).new(self).run
72
+ end if @config[:plugin]
73
+ end
74
+
75
+ def file_update_handler
76
+ @wrapper.compile
77
+ end
78
+
79
+ def read_log_loop
80
+ log = Pathname.new(@config.params[:flashlog])
81
+ return unless (log && log.file?)
82
+
83
+ Thread.new(log) do |log|
84
+ flashlog_timestamp ||= log.mtime
85
+
86
+ log.open('r') do |f|
87
+ f.read
88
+ loop do
89
+ if log.mtime > flashlog_timestamp
90
+ f.rewind
91
+ text = f.read
92
+ if text.length == 0
93
+ f.rewind
94
+ text = f.read
95
+ end
96
+ logger.info("FLASHLOG\n" + text) unless text.strip.empty?
97
+ flashlog_timestamp = log.mtime
98
+ end
99
+ sleep 1
100
+ end
101
+ end
102
+ end
103
+ end
104
+
105
+ def compile_success_proc
106
+ if @httpd
107
+ @httpd.reload!
108
+ end
109
+ end
110
+
111
+ def start_server
112
+ require 'rascut/httpd'
113
+ @httpd = Httpd.new(self)
114
+ @httpd.start
115
+ end
116
+
117
+ def setting_signals
118
+ methods(true).each do |mname|
119
+ if m = mname.match(/^sig_(.+)$/)
120
+ begin
121
+ Signal.trap(m[1].upcase) { method(mname).call }
122
+ rescue ArgumentError
123
+ end
124
+ end
125
+ end
126
+ end
127
+
128
+ def sig_int
129
+ logger.debug 'SIG_INT'
130
+ self.exit()
131
+ end
132
+
133
+ def sig_usr2
134
+ logger.debug 'SIG_USR2'
135
+ @wrapper.compile
136
+ end
137
+
138
+ def exit
139
+ logger.info 'exiting...'
140
+ begin
141
+ @wrapper.close
142
+ rescue Exception => e
143
+ logger.error e.inspect
144
+ end
145
+ Kernel::exit 1
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,82 @@
1
+ require 'optparse'
2
+ require 'rascut/logger'
3
+ require 'logger'
4
+
5
+ module Rascut
6
+ class Config
7
+ DEFAULT_CONFIG = {
8
+ :interval => 1,
9
+ :compile_config => nil,
10
+ :file_observing => true,
11
+ :fcsh_cmd => 'fcsh',
12
+ :ext => ['as', 'css', 'mxml'],
13
+ :logger => Rascut::Logger.new(STDOUT),
14
+ }
15
+
16
+ def initialize
17
+ @params = DEFAULT_CONFIG.dup
18
+ @params[:logger].level = ::Logger::INFO
19
+ end
20
+ attr_accessor :params
21
+
22
+ def [](name)
23
+ @params[name]
24
+ end
25
+
26
+ def logger
27
+ @params[:logger]
28
+ end
29
+
30
+ def parse_argv!(argv)
31
+ op = OptionParser.new
32
+ op.banner = 'Usage: $ rascut HelloWrold.as'
33
+
34
+ #op.on('-a', 'Apollo compile mode') do |v|
35
+ # @params[:apollo] = true
36
+ # @params[:compile_config] = '+configname=apollo'
37
+ #end
38
+
39
+ op.on('-b=VAL', '--bind-address=VAL', 'server bind address(default 0.0.0.0)') {|v| @params[:bind_address] = v }
40
+ op.on('--compile-config=VAL', '-c=VAL', 'mxmlc compile config ex:) --compile-config="-benchmark -strict=true"') do |v|
41
+ if @params[:compile_config]
42
+ @params[:compile_config] << ' ' << v
43
+ else
44
+ @params[:compile_config] = v
45
+ end
46
+ end
47
+
48
+ op.on('--fcsh-cmd=VAL', 'fcsh command path') {|v| @params[:fcsh_cmd] = v }
49
+ @params[:observe_files] = []
50
+ op.on('-I=VAL', '--observe-files=VAL', 'observe files and directories path') {|v| @params[:observe_files] << v }
51
+
52
+ if @params[:observe_files].empty?
53
+ @params[:observe_files] << '.'
54
+ end
55
+
56
+ op.on('-i=VAL', '--interval=VAL', 'interval time(min)') {|v| @params[:interval] = v.to_i }
57
+ op.on('-l=VAL', '--log=VAL', 'showing flashlog.txt') {|v| @params[:flashlog] = v }
58
+ op.on('-m=VAL', '--mapping=VAL', 'server mapping path :example) -m "../assets=assets" -m "../images/=img"') {|v|
59
+ @params[:mapping] ||= []
60
+ @params[:mapping] << v.split('=', 2)
61
+ }
62
+ op.on('--no-file-observe', "don't observing files") {|v| @params[:file_observing] = false }
63
+ op.on('--observe-ext=VAL', 'observe ext ex:) --observe-ext="as3,actionscript3,css,mxml"') {|v| @params[:ext] = v.split(',') }
64
+ op.on('--server', '-s', 'start autoreload webserver') {|v| @params[:server] = true }
65
+ op.on('--server-handler=val', 'set server hander :example) --server-handler=webrick') {|v| @params[:server] = v }
66
+ op.on('--port=val', '-p=val', 'server port(default: 3001)') {|v| @params[:port] = v.to_i }
67
+ op.on('--plugin=VAL', 'load plugin(s)') {|v|
68
+ @params[:plugin] ||= []
69
+ @params[:plugin] << v
70
+ }
71
+ op.on('-t=VAL', '--template=VAL', 'server use template file') {|v| @params[:template] = v }
72
+ op.on('-v', '--verbose', 'detail messages') {|v| @params[:logger].level = Logger::DEBUG }
73
+ op.on('--help', 'show this message') { puts op; Kernel::exit 1 }
74
+ op.parse! argv
75
+ @params[:logger].debug 'config' + @params.inspect
76
+ end
77
+
78
+ def merge_config(file)
79
+ @params.merge! YAML.load_file(file)
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,109 @@
1
+ begin
2
+ require 'expect'
3
+ rescue LoadError
4
+ require 'pathname'
5
+ require Pathname.new(__FILE__).parent.parent.parent.join('vendor/ruby/expect')
6
+ end
7
+ require 'thread'
8
+ require 'rascut/file_observer'
9
+
10
+ module Rascut
11
+ class FcshWrapper
12
+ FCSH_RESULT_RE = /fcsh: Assigned (\d+) as the compile target id/
13
+ FCSH_WAIT_RE = /^\(fcsh\)\s*$/
14
+
15
+ attr_accessor :original_files, :files, :config, :target_script, :hooks
16
+ def initialize(target_script, config)
17
+ @target_script = target_script
18
+ @config = config
19
+ @hooks = Hash.new {|h, k| h[k] = []}
20
+ @mutex = Mutex.new
21
+ @compile_mutex = Mutex.new
22
+ @compile_id = nil
23
+ @process = nil
24
+ @not_first_read = nil
25
+ end
26
+
27
+ def reload!
28
+ if @compile_id
29
+ process_sync_exec("clear #{@compile_id}")
30
+ @compile_id = nil
31
+ end
32
+ call_hook :reload, @compile_id
33
+ end
34
+
35
+ def process_sync_exec(str, result_get = true)
36
+ res = nil
37
+ @mutex.synchronize do
38
+ @process.puts str
39
+ res = read_result(@process) if result_get
40
+ end
41
+ res
42
+ end
43
+
44
+ def close
45
+ if @process
46
+ @process.close
47
+ call_hook :close
48
+ end
49
+ end
50
+
51
+ def logger
52
+ @config[:logger]
53
+ end
54
+
55
+ def mxmlc_cmd
56
+ cmd = ['mxmlc', @config[:compile_config], @target_script].join(' ')
57
+ logger.debug cmd
58
+ cmd
59
+ end
60
+
61
+ def compile
62
+ return false if @compile_mutex.locked?
63
+
64
+ @compile_mutex.synchronize do
65
+ logger.info "Compile Start"
66
+ out = nil
67
+ @process = IO.popen(@config[:fcsh_cmd] + ' 2>&1', 'r+') unless @process
68
+ if @compile_id
69
+ out = process_sync_exec "compile #{@compile_id}"
70
+ else
71
+ out = process_sync_exec mxmlc_cmd
72
+ if m = out.match(FCSH_RESULT_RE)
73
+ @compile_id = m[1]
74
+ else
75
+ raise "Can't get Compile ID\n" + out.to_s
76
+ end
77
+ end
78
+ logger.info out
79
+ if out.match(/bytes\)/)
80
+ call_hook :compile_success, out
81
+ else
82
+ call_hook :compile_error, out
83
+ end
84
+ call_hook :compile, out
85
+ end
86
+ end
87
+
88
+ def call_hook(name, *args)
89
+ @hooks[name].each do |hook|
90
+ if hook.arity == 0 || args.length == 0
91
+ hook.call
92
+ else
93
+ hook.call(*args)
94
+ end
95
+ end
96
+ end
97
+
98
+ def read_result(process)
99
+ unless @not_first_read
100
+ # first_time, FIXME uncool...
101
+ process.expect(FCSH_WAIT_RE)
102
+ @not_first_read = true
103
+ end
104
+
105
+ process.expect(FCSH_WAIT_RE).first.sub(FCSH_WAIT_RE, '')
106
+ end
107
+ end
108
+ end
109
+
@@ -0,0 +1,160 @@
1
+ require 'pathname'
2
+ require 'find'
3
+ require 'logger'
4
+
5
+ module Rascut
6
+ class FileObserver
7
+ DEFAULT_OPTIONS = {
8
+ :interval => 1,
9
+ :ignore_files => [],
10
+ :ignore_dirs => [/\/.svn/],
11
+ :logger => Logger.new(STDOUT),
12
+ :dir_counter => 5,
13
+ :ext => nil
14
+ }
15
+
16
+ def initialize(files, options)
17
+ @files = {}
18
+ @dirs = {}
19
+ @options = DEFAULT_OPTIONS.merge options
20
+ @update_handlers = []
21
+ @th = nil
22
+
23
+ if options[:update_handler]
24
+ add_update_handler options.delete(:update_handler)
25
+ end
26
+
27
+ observe files
28
+ end
29
+ attr_accessor :options
30
+
31
+ def logger
32
+ options[:logger]
33
+ end
34
+
35
+ def run
36
+ if @th
37
+ @th.run
38
+ else
39
+ @th = Thread.start do
40
+ loop do
41
+ update_check
42
+ sleep @options[:interval]
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ def add_update_handler(handler)
49
+ unless @update_handlers.include? handler
50
+ @update_handlers << handler
51
+ end
52
+ end
53
+
54
+ def remove_update_handler(handler)
55
+ @update_handlers.delete_if {|h| h == handler}
56
+ end
57
+
58
+ def stop
59
+ @th.kill
60
+ end
61
+
62
+ def observe(files)
63
+ Array(files).each do |file|
64
+ file = Pathname.new(file)
65
+ if file.directory?
66
+ dir_observe file
67
+ else
68
+ next if @options[:ignore_files].include?(file.realpath)
69
+
70
+ file_observe file
71
+ end
72
+ end
73
+ end
74
+
75
+ private
76
+
77
+ def update_check
78
+ update_files = []
79
+ check_dirs if check_dir?
80
+
81
+ @files.each do |file, mtime|
82
+ if !file.readable?
83
+ @files.delete file
84
+ elsif file.mtime > mtime
85
+ @files[file] = file.mtime
86
+ update_files << file
87
+ end
88
+ end
89
+
90
+ unless update_files.empty?
91
+ logger.info 'Found update file(s)' + update_files.map{|f| f.to_s}.inspect
92
+ @update_handlers.each do |handler|
93
+ if handler.arity == 1
94
+ handler.call update_files
95
+ else
96
+ handler.call
97
+ end
98
+ end
99
+ end
100
+ end
101
+
102
+ def check_dir?
103
+ @check_dir_count ||= options[:dir_counter]
104
+ if @check_dir_count > options[:dir_counter]
105
+ @check_dir_count = 0
106
+ else
107
+ @check_dir_count += 1
108
+ end
109
+ @check_dir_count.zero?
110
+ end
111
+
112
+ def check_dirs
113
+ dfiles = []
114
+ @dirs.each do |dir, mtime|
115
+ next if @options[:ignore_dirs].include?(dir.realpath)
116
+
117
+ if !dir.directory?
118
+ @dirs.delete dir
119
+ elsif dir.to_s.match %r{/\.svn|/CVS}
120
+ @dirs.delete dir
121
+ elsif dir.mtime > mtime
122
+ @dirs[dir] = dir.mtime
123
+
124
+ if @options[:ext]
125
+ e = '.{' + @options[:ext].join(',') + '}'
126
+ else
127
+ e = ''
128
+ end
129
+ dfiles.concat Pathname.glob(dir.to_s + "/{**/*}#{e}")
130
+ end
131
+ end
132
+ dfiles.uniq.each do |file|
133
+ if file.directory?
134
+ dir_observe file
135
+ else
136
+ file_observe file, Time.at(0)
137
+ end
138
+ end
139
+ end
140
+
141
+ def file_observe(file, mtime = nil)
142
+ if @options[:ext]
143
+ if @options[:ext].include? file.extname.sub(/^\./, '')
144
+ @files[file] ||= (mtime || file.mtime)
145
+ end
146
+ else
147
+ @files[file] ||= (mtime || file.mtime)
148
+ end
149
+ end
150
+
151
+ def dir_observe(dir)
152
+ Find::find(dir.to_s) do |file|
153
+ if File.directory?(file)
154
+ dir = Pathname.new(file)
155
+ @dirs[dir] ||= dir.mtime
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,304 @@
1
+
2
+ begin
3
+ require 'rack'
4
+ rescue LoadError
5
+ require 'rubygems'
6
+ gem 'rack'
7
+ end
8
+
9
+ require 'rack/showexceptions'
10
+ require 'rack/urlmap'
11
+ require 'rack/file'
12
+ require 'rack/request'
13
+ require 'rack/response'
14
+ require 'thread'
15
+ require 'logger'
16
+ require 'pathname'
17
+ require 'open-uri'
18
+
19
+ module Rascut
20
+ class Httpd
21
+ class FileOnly < Rack::File
22
+ def _call(env)
23
+ if env["PATH_INFO"].include? ".."
24
+ return [403, {"Content-Type" => "text/plain"}, ["Forbidden\n"]]
25
+ end
26
+
27
+ @path = env["PATH_INFO"] == '/' ? @root : F.join(@root, env['PATH_INFO'])
28
+ ext = F.extname(@path)[1..-1]
29
+
30
+ if F.file?(@path) && F.readable?(@path)
31
+ [200, {
32
+ "Content-Type" => MIME_TYPES[ext] || "text/plain",
33
+ "Content-Length" => F.size(@path).to_s
34
+ }, self]
35
+ else
36
+ return [404, {"Content-Type" => "text/plain"},
37
+ ["File not found: #{env["PATH_INFO"]}\n"]]
38
+ end
39
+ end
40
+ end
41
+
42
+ def initialize(command)
43
+ @command = command
44
+ @threads = []
45
+ end
46
+ attr_reader :command
47
+
48
+ def start
49
+ swf_path = command.root.to_s
50
+ logger.debug "swf_path: #{swf_path}"
51
+ vendor = Pathname.new(__FILE__).parent.parent.parent.join('vendor')
52
+ logger.debug "vendor_path: #{vendor}"
53
+ reload = reload_handler
54
+ index = index_handler
55
+
56
+ #app = Rack::FixedBuilder.new do
57
+ # use Rack::ShowExceptions
58
+ # map('/reload') { run reload }
59
+ # map('/swf/') { run Rack::File.new(swf_path) }
60
+ # map('/') { run index }
61
+ #end
62
+
63
+ urls = []
64
+ urls.concat(config_url_mapping) if config[:mapping]
65
+
66
+ urls.concat([
67
+ ['/js/swfobject.js', Rack::ShowExceptions.new(Httpd::FileOnly.new(vendor.join('js/swfobject.js').to_s))],
68
+ ['/swf', Rack::ShowExceptions.new(Rack::File.new(swf_path))],
69
+ ['/reload', Rack::ShowExceptions.new(reload_handler)],
70
+ ['/proxy', Rack::ShowExceptions.new(proxy_handler)],
71
+ ['/', Rack::ShowExceptions.new(index_handler)]
72
+ ])
73
+ logger.debug 'url mappings: ' + urls.map{|u| u.first}.inspect
74
+ app = Rack::URLMap.new(urls)
75
+ port = config[:port] || 3001
76
+ host = config[:bind_address] || '0.0.0.0'
77
+
78
+ _args = [app, {:Host => host, :Port => port}]
79
+ server_handler = detect_server
80
+ Thread.new(_args) do |args|
81
+ server_handler.run *args
82
+ end
83
+ logger.info "Start #{server_handler} http://#{host}:#{port}/"
84
+ end
85
+
86
+ def config
87
+ command.config
88
+ end
89
+
90
+ def logger
91
+ config.logger
92
+ end
93
+
94
+ def reload!
95
+ while t = @threads.pop
96
+ t.run
97
+ end
98
+ end
99
+
100
+ private
101
+ def config_url_mapping
102
+ urls = []
103
+ config[:mapping].each do |m|
104
+ filepath = m[0]
105
+ mappath = m[1]
106
+ mappath = '/' + mappath if mappath[0..0] != '/'
107
+ urls << [mappath, Rack::ShowExceptions.new(Rack::File.new(command.root.join(filepath).to_s))]
108
+ end
109
+ urls
110
+ end
111
+
112
+ def detect_server
113
+ begin
114
+ case config[:server]
115
+ when 'mongrel'
116
+ require_mongrel_handler
117
+ when 'webrick'
118
+ require_webrick_handler
119
+ else
120
+ require_mongrel_handler
121
+ end
122
+ rescue Exception => e
123
+ require_webrick_handler
124
+ end
125
+ end
126
+
127
+ def require_mongrel_handler
128
+ begin
129
+ require 'mongrel'
130
+ rescue LoadError
131
+ require 'rubygems'
132
+ gem 'mongrel', '> 1.0'
133
+ end
134
+ require 'rack/handler/mongrel'
135
+ Rack::Handler::Mongrel
136
+ end
137
+
138
+ def require_webrick_handler
139
+ require 'webrick'
140
+ require 'rack/handler/webrick'
141
+ Rack::Handler::WEBrick
142
+ end
143
+
144
+ def reload_handler
145
+ Proc.new do |env|
146
+ @threads << Thread.current
147
+ Thread.stop
148
+
149
+ logger.debug 'httpd /reload reloading now'
150
+ Rack::Response.new.finish do |r|
151
+ r.write '1'
152
+ end
153
+ end
154
+ end
155
+
156
+ def index_handler
157
+ if config[:template] && File.readable?(config[:template])
158
+ res = File.read(config[:template]) + "\n" + RELOAD_SCRIPT
159
+ else
160
+ res = INDEX.sub('__SWFOBJECT__', swftag(command.target_script, config)).sub('<!--__RELOAD__-->', RELOAD_SCRIPT)
161
+ end
162
+
163
+ Proc.new do |env|
164
+ req = Rack::Request.new(env)
165
+ res.sub!('__SWF_VARS__', swfvars(req.GET))
166
+ Rack::Response.new.finish do |r|
167
+ r.write res
168
+ end
169
+ end
170
+ end
171
+
172
+ def proxy_handler
173
+ Proc.new do |env|
174
+ req = Rack::Request.new(env)
175
+ url = req.query_string
176
+ if url.empty?
177
+ url = req.path_info[1..-1].gsub(%r{^(https?:/)/?}, '\\1/')
178
+ end
179
+ Rack::Response.new.finish do |r|
180
+ open(url) { |io|
181
+ r['Content-Type'] = io.content_type
182
+ while part = io.read(8192)
183
+ r.write part
184
+ end
185
+ }
186
+ end
187
+ end
188
+ end
189
+
190
+ def swfvars(vars)
191
+ res = []
192
+ vars.each do |key, value|
193
+ res << %Q[so.addVariable('#{key}', '#{value}');]
194
+ end
195
+ res.join("\n")
196
+ end
197
+
198
+ def swftag(target_script, options)
199
+ name = output_file(options[:compile_config]) || target_script
200
+ name = File.basename(name, '.*')
201
+ swf = "/swf/#{name}.swf"
202
+ height, width = wh_parse options[:compile_config]
203
+ bgcol = bg_parse options[:compile_config]
204
+ # %Q[new SWFObject("#{swf}?#{Time.now.to_i}#{Time.now.usec}", "#{name}", "#{width}", "#{height}", '9', '#{bgcol}');]
205
+ %Q[new SWFObject("#{swf}?" + (new Date()).getTime(), "idswf", "#{width}", "#{height}", '9', '#{bgcol}');]
206
+ end
207
+
208
+ def bg_parse(opt)
209
+ if m = opt.to_s.match(/-default-background-color=0x([a-fA-F0-9]{3,6})/)
210
+ '#' + m[1]
211
+ else
212
+ '#ffffff'
213
+ end
214
+ end
215
+
216
+ def wh_parse(opt)
217
+ if m = opt.to_s.match(/-default-size\s+(\d+)\s+(\d+)/)
218
+ m.to_a[1..2]
219
+ else
220
+ ['100%', '100%']
221
+ end
222
+ end
223
+
224
+ def output_file(opt)
225
+ if m = opt.to_s.match(/-(o|output)\s+([^\s]+)/)
226
+ m[2]
227
+ else
228
+ nil
229
+ end
230
+ end
231
+
232
+ INDEX = <<-EOF
233
+ <html>
234
+ <head>
235
+ <title>Rascut</title>
236
+ <style>
237
+ * {
238
+ margin:0;
239
+ padding:0;
240
+ }
241
+ #content {
242
+ text-align:center;
243
+ }
244
+ </style>
245
+ <script type="text/javascript" src="/js/swfobject.js"></script>
246
+ <!--__RELOAD__-->
247
+ </head>
248
+ <body>
249
+ <div id="content">
250
+ </div>
251
+
252
+ <script type="text/javascript">
253
+ var so = __SWFOBJECT__;
254
+ __SWF_VARS__
255
+ so.addVariable('rascut', 'true');
256
+ so.write("content");
257
+ </script>
258
+ </body>
259
+ </html>
260
+ EOF
261
+
262
+ RELOAD_SCRIPT = <<-EOF
263
+ <script type="text/javascript">
264
+ var Rascut = new Object;
265
+
266
+ Rascut.xhr = (function() {
267
+ if (typeof XMLHttpRequest != 'undefined') {
268
+ return new XMLHttpRequest();
269
+ } else {
270
+ try {
271
+ return new ActiveXObject("Msxml2.XMLHTTP");
272
+ } catch(e) {
273
+ return new ActiveXObject("Microsoft.XMLHTTP");
274
+ }
275
+ }
276
+ })();
277
+
278
+ Rascut.reloadObserver = function() {
279
+ var x = Rascut.xhr;
280
+ x.open('GET', '/reload?' + (new Date()).getTime(), true);
281
+ x.onreadystatechange = function() {
282
+ try {
283
+ if (x.readyState == 4) {
284
+ if (x.status == 200 && Number(x.responseText) == 1) {
285
+ // thanks os0x!
286
+ so.attributes.swf = so.attributes.swf + '+';
287
+ so.write('content');
288
+ Rascut.reloadObserver();
289
+ } else {
290
+ setTimeout(Rascut.reloadObserver, 5000);
291
+ }
292
+ }
293
+ } catch(e) {
294
+ setTimeout(Rascut.reloadObserver, 5000);
295
+ }
296
+ }
297
+ x.send(null);
298
+ }
299
+
300
+ Rascut.reloadObserver();
301
+ </script>
302
+ EOF
303
+ end
304
+ end
@@ -0,0 +1,18 @@
1
+ require 'logger'
2
+
3
+ module Rascut
4
+ class Logger < ::Logger
5
+ # This code from ActiveSupport
6
+ private
7
+ alias old_format_message format_message
8
+ if method_defined?(:formatter=)
9
+ def format_message(severity, timestamp, progname, msg)
10
+ "[#{Time.now.strftime('%m/%d %H:%M:%S')}] #{msg}\n"
11
+ end
12
+ else
13
+ def format_message(severity, timestamp, msg, progname)
14
+ "[#{Time.now.strftime('%m/%d %H:%M:%S')}] #{msg}\n"
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+
2
+ module Rascut
3
+ module Plugin
4
+ class Base
5
+ def initialize(command)
6
+ @command = command
7
+ end
8
+
9
+ def config
10
+ # ...
11
+ {}
12
+ end
13
+
14
+ def run
15
+ raise 'should\'d be run method override!'
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,31 @@
1
+
2
+ require 'rascut/plugin/base'
3
+ require 'pathname'
4
+
5
+ module Rascut
6
+ module Plugin
7
+ class WriteFcshErrorOutput < Base
8
+ def run
9
+ file = config[:filename] || Pathname.new(ENV['HOME']).join('.rascut/error_output')
10
+ @path = Pathname.new(file.to_s)
11
+
12
+ @command.wrapper.hooks[:compile_error] << method(:write_error_output)
13
+ @command.wrapper.hooks[:compile_success] << method(:write_error_none)
14
+ end
15
+
16
+ def write_error_output(str)
17
+ str.each_line do |line|
18
+ if line.match 'Error: '
19
+ @path.open('w'){|f| f.puts line.chomp }
20
+ break
21
+ end
22
+ end
23
+ end
24
+
25
+ def write_error_none(str)
26
+ @path.open('w'){|f| f.write '' }
27
+ end
28
+
29
+ end
30
+ end
31
+ end
data/lib/rascut.rb ADDED
@@ -0,0 +1,9 @@
1
+
2
+ module Rascut
3
+ VERSION = '0.1.0'
4
+ end
5
+
6
+ require 'rascut/logger'
7
+ require 'rascut/fcsh_wrapper'
8
+ require 'rascut/command'
9
+
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH << File.expand_path(File.dirname(__FILE__) + "/../lib")
3
+ require 'rascut/file_observer'
4
+ require 'test/unit'
5
+ require 'tmpdir'
6
+ require 'pathname'
7
+ $DEBUG = true
8
+
9
+ class FileObserverTest < Test::Unit::TestCase
10
+ def setup
11
+ @tmpdir = Pathname.new(Dir.tmpdir).join('test_file_observer_' + Time.now.to_i.to_s)
12
+ @tmpdir.mkpath
13
+
14
+ @fo = Rascut::FileObserver.new(@tmpdir.to_s, :interval => 1, :dir_counter => 0)
15
+ @update_files = []
16
+
17
+ @fo.add_update_handler method(:save_files)
18
+
19
+ @call_update_handler_flag = false
20
+ @fo.add_update_handler method(:call_update_handler)
21
+
22
+ @fo.run
23
+ end
24
+
25
+ def call_update_handler(files)
26
+ @call_update_handler_flag = true
27
+ end
28
+
29
+ def save_files(files)
30
+ @update_files << files
31
+ end
32
+
33
+ def test_dir
34
+ sleep 1.1
35
+ @tmpdir.join('foo').mkpath
36
+ sleep 1.1
37
+ assert !@call_update_handler_flag
38
+
39
+ @tmpdir.join('foo/foo.txt').open('w') {|f| f.puts 'file'}
40
+ sleep 1.1
41
+ assert @call_update_handler_flag
42
+ @call_update_handler_flag = false
43
+
44
+ @tmpdir.join('foo/foo').mkpath
45
+ sleep 1.1
46
+ assert !@call_update_handler_flag
47
+
48
+ @tmpdir.join('foo/foo/foo.txt').open('w') {|f| f.puts 'file'}
49
+ sleep 1.1
50
+ assert @call_update_handler_flag
51
+ end
52
+
53
+ def test_fileput
54
+ sleep 1
55
+ @tmpdir.join('foo.txt').open('w') {|f| f.puts 'file'}
56
+ sleep 1
57
+ assert @call_update_handler_flag
58
+
59
+ @call_update_handler_flag = false
60
+ sleep 1
61
+ @tmpdir.join('foo.txt').open('w+') {|f| f.puts 'file'}
62
+ sleep 1
63
+ assert @call_update_handler_flag
64
+
65
+ @call_update_handler_flag = false
66
+ sleep 1
67
+ assert(!@call_update_handler_flag)
68
+ end
69
+
70
+ def test_fileput_with_ext
71
+ @fo.options[:ext] = ['txt']
72
+ sleep 1
73
+ @tmpdir.join('foo.txt').open('w') {|f| f.puts 'file'}
74
+ sleep 1
75
+ assert @call_update_handler_flag
76
+ end
77
+
78
+ def test_fileput_with_ext_nomatch
79
+ @fo.options[:ext] = ['nontext']
80
+ sleep 1
81
+ @tmpdir.join('foo.txt').open('w') {|f| f.puts 'file'}
82
+ sleep 1
83
+ assert !@call_update_handler_flag
84
+ end
85
+
86
+ def teardown
87
+ @fo.stop
88
+ @tmpdir.rmtree
89
+ end
90
+ end
File without changes
@@ -0,0 +1,8 @@
1
+ /**
2
+ * SWFObject v1.5: Flash Player detection and embed - http://blog.deconcept.com/swfobject/
3
+ *
4
+ * SWFObject is (c) 2007 Geoff Stearns and is released under the MIT License:
5
+ * http://www.opensource.org/licenses/mit-license.php
6
+ *
7
+ */
8
+ if(typeof deconcept=="undefined"){var deconcept=new Object();}if(typeof deconcept.util=="undefined"){deconcept.util=new Object();}if(typeof deconcept.SWFObjectUtil=="undefined"){deconcept.SWFObjectUtil=new Object();}deconcept.SWFObject=function(_1,id,w,h,_5,c,_7,_8,_9,_a){if(!document.getElementById){return;}this.DETECT_KEY=_a?_a:"detectflash";this.skipDetect=deconcept.util.getRequestParameter(this.DETECT_KEY);this.params=new Object();this.variables=new Object();this.attributes=new Array();if(_1){this.setAttribute("swf",_1);}if(id){this.setAttribute("id",id);}if(w){this.setAttribute("width",w);}if(h){this.setAttribute("height",h);}if(_5){this.setAttribute("version",new deconcept.PlayerVersion(_5.toString().split(".")));}this.installedVer=deconcept.SWFObjectUtil.getPlayerVersion();if(!window.opera&&document.all&&this.installedVer.major>7){deconcept.SWFObject.doPrepUnload=true;}if(c){this.addParam("bgcolor",c);}var q=_7?_7:"high";this.addParam("quality",q);this.setAttribute("useExpressInstall",false);this.setAttribute("doExpressInstall",false);var _c=(_8)?_8:window.location;this.setAttribute("xiRedirectUrl",_c);this.setAttribute("redirectUrl","");if(_9){this.setAttribute("redirectUrl",_9);}};deconcept.SWFObject.prototype={useExpressInstall:function(_d){this.xiSWFPath=!_d?"expressinstall.swf":_d;this.setAttribute("useExpressInstall",true);},setAttribute:function(_e,_f){this.attributes[_e]=_f;},getAttribute:function(_10){return this.attributes[_10];},addParam:function(_11,_12){this.params[_11]=_12;},getParams:function(){return this.params;},addVariable:function(_13,_14){this.variables[_13]=_14;},getVariable:function(_15){return this.variables[_15];},getVariables:function(){return this.variables;},getVariablePairs:function(){var _16=new Array();var key;var _18=this.getVariables();for(key in _18){_16[_16.length]=key+"="+_18[key];}return _16;},getSWFHTML:function(){var _19="";if(navigator.plugins&&navigator.mimeTypes&&navigator.mimeTypes.length){if(this.getAttribute("doExpressInstall")){this.addVariable("MMplayerType","PlugIn");this.setAttribute("swf",this.xiSWFPath);}_19="<embed type=\"application/x-shockwave-flash\" src=\""+this.getAttribute("swf")+"\" width=\""+this.getAttribute("width")+"\" height=\""+this.getAttribute("height")+"\" style=\""+this.getAttribute("style")+"\"";_19+=" id=\""+this.getAttribute("id")+"\" name=\""+this.getAttribute("id")+"\" ";var _1a=this.getParams();for(var key in _1a){_19+=[key]+"=\""+_1a[key]+"\" ";}var _1c=this.getVariablePairs().join("&");if(_1c.length>0){_19+="flashvars=\""+_1c+"\"";}_19+="/>";}else{if(this.getAttribute("doExpressInstall")){this.addVariable("MMplayerType","ActiveX");this.setAttribute("swf",this.xiSWFPath);}_19="<object id=\""+this.getAttribute("id")+"\" classid=\"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000\" width=\""+this.getAttribute("width")+"\" height=\""+this.getAttribute("height")+"\" style=\""+this.getAttribute("style")+"\">";_19+="<param name=\"movie\" value=\""+this.getAttribute("swf")+"\" />";var _1d=this.getParams();for(var key in _1d){_19+="<param name=\""+key+"\" value=\""+_1d[key]+"\" />";}var _1f=this.getVariablePairs().join("&");if(_1f.length>0){_19+="<param name=\"flashvars\" value=\""+_1f+"\" />";}_19+="</object>";}return _19;},write:function(_20){if(this.getAttribute("useExpressInstall")){var _21=new deconcept.PlayerVersion([6,0,65]);if(this.installedVer.versionIsValid(_21)&&!this.installedVer.versionIsValid(this.getAttribute("version"))){this.setAttribute("doExpressInstall",true);this.addVariable("MMredirectURL",escape(this.getAttribute("xiRedirectUrl")));document.title=document.title.slice(0,47)+" - Flash Player Installation";this.addVariable("MMdoctitle",document.title);}}if(this.skipDetect||this.getAttribute("doExpressInstall")||this.installedVer.versionIsValid(this.getAttribute("version"))){var n=(typeof _20=="string")?document.getElementById(_20):_20;n.innerHTML=this.getSWFHTML();return true;}else{if(this.getAttribute("redirectUrl")!=""){document.location.replace(this.getAttribute("redirectUrl"));}}return false;}};deconcept.SWFObjectUtil.getPlayerVersion=function(){var _23=new deconcept.PlayerVersion([0,0,0]);if(navigator.plugins&&navigator.mimeTypes.length){var x=navigator.plugins["Shockwave Flash"];if(x&&x.description){_23=new deconcept.PlayerVersion(x.description.replace(/([a-zA-Z]|\s)+/,"").replace(/(\s+r|\s+b[0-9]+)/,".").split("."));}}else{if(navigator.userAgent&&navigator.userAgent.indexOf("Windows CE")>=0){var axo=1;var _26=3;while(axo){try{_26++;axo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash."+_26);_23=new deconcept.PlayerVersion([_26,0,0]);}catch(e){axo=null;}}}else{try{var axo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7");}catch(e){try{var axo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6");_23=new deconcept.PlayerVersion([6,0,21]);axo.AllowScriptAccess="always";}catch(e){if(_23.major==6){return _23;}}try{axo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash");}catch(e){}}if(axo!=null){_23=new deconcept.PlayerVersion(axo.GetVariable("$version").split(" ")[1].split(","));}}}return _23;};deconcept.PlayerVersion=function(_29){this.major=_29[0]!=null?parseInt(_29[0]):0;this.minor=_29[1]!=null?parseInt(_29[1]):0;this.rev=_29[2]!=null?parseInt(_29[2]):0;};deconcept.PlayerVersion.prototype.versionIsValid=function(fv){if(this.major<fv.major){return false;}if(this.major>fv.major){return true;}if(this.minor<fv.minor){return false;}if(this.minor>fv.minor){return true;}if(this.rev<fv.rev){return false;}return true;};deconcept.util={getRequestParameter:function(_2b){var q=document.location.search||document.location.hash;if(_2b==null){return q;}if(q){var _2d=q.substring(1).split("&");for(var i=0;i<_2d.length;i++){if(_2d[i].substring(0,_2d[i].indexOf("="))==_2b){return _2d[i].substring((_2d[i].indexOf("=")+1));}}}return "";}};deconcept.SWFObjectUtil.cleanupSWFs=function(){var _2f=document.getElementsByTagName("OBJECT");for(var i=_2f.length-1;i>=0;i--){_2f[i].style.display="none";for(var x in _2f[i]){if(typeof _2f[i][x]=="function"){_2f[i][x]=function(){};}}}};if(deconcept.SWFObject.doPrepUnload){if(!deconcept.unloadSet){deconcept.SWFObjectUtil.prepUnload=function(){__flash_unloadHandler=function(){};__flash_savedUnloadHandler=function(){};window.attachEvent("onunload",deconcept.SWFObjectUtil.cleanupSWFs);};window.attachEvent("onbeforeunload",deconcept.SWFObjectUtil.prepUnload);deconcept.unloadSet=true;}}if(!document.getElementById&&document.all){document.getElementById=function(id){return document.all[id];};}var getQueryParamValue=deconcept.util.getRequestParameter;var FlashObject=deconcept.SWFObject;var SWFObject=deconcept.SWFObject;
@@ -0,0 +1,36 @@
1
+ $expect_verbose = false
2
+
3
+ class IO
4
+ def expect(pat,timeout=9999999)
5
+ buf = ''
6
+ case pat
7
+ when String
8
+ e_pat = Regexp.new(Regexp.quote(pat))
9
+ when Regexp
10
+ e_pat = pat
11
+ end
12
+ while true
13
+ if IO.select([self],nil,nil,timeout).nil? then
14
+ result = nil
15
+ break
16
+ end
17
+ c = getc.chr
18
+ buf << c
19
+ if $expect_verbose
20
+ STDOUT.print c
21
+ STDOUT.flush
22
+ end
23
+ if mat=e_pat.match(buf) then
24
+ result = [buf,*mat.to_a[1..-1]]
25
+ break
26
+ end
27
+ end
28
+ if block_given? then
29
+ yield result
30
+ else
31
+ return result
32
+ end
33
+ nil
34
+ end
35
+ end
36
+
metadata ADDED
@@ -0,0 +1,111 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.1
3
+ specification_version: 1
4
+ name: rascut
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.1.0
7
+ date: 2007-09-02 00:00:00 +09:00
8
+ summary: Ruby ActionSCript UTility
9
+ require_paths:
10
+ - lib
11
+ email: hotchpotch@nononospam@gmail.com
12
+ homepage: " by Yuichi Tateno"
13
+ rubyforge_project: hotchpotch
14
+ description: "== FEATURES/PROBLEMS: == SYNOPSIS: * 1. flex2sdk and fcwrap setting. * 2. $ rascut -s MyScript.as * 3. Browser access http://localhost:3001/ == REQUIREMENTS:"
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - yuichi tateno
31
+ files:
32
+ - History.txt
33
+ - Manifest.txt
34
+ - README.txt
35
+ - Rakefile
36
+ - bin/rascut
37
+ - lib/rascut.rb
38
+ - lib/rascut/command.rb
39
+ - lib/rascut/config.rb
40
+ - lib/rascut/fcsh_wrapper.rb
41
+ - lib/rascut/file_observer.rb
42
+ - lib/rascut/httpd.rb
43
+ - lib/rascut/logger.rb
44
+ - lib/rascut/plugin/base.rb
45
+ - lib/rascut/plugin/write_fcsh_error_output.rb
46
+ - vendor/ruby/expect.rb
47
+ - vendor/js/swfobject.js
48
+ - test/test_rascut.rb
49
+ - test/test_file_observer.rb
50
+ test_files:
51
+ - test/test_rascut.rb
52
+ - test/test_file_observer.rb
53
+ rdoc_options:
54
+ - --main
55
+ - README.txt
56
+ extra_rdoc_files:
57
+ - History.txt
58
+ - Manifest.txt
59
+ - README.txt
60
+ executables:
61
+ - rascut
62
+ extensions: []
63
+
64
+ requirements: []
65
+
66
+ dependencies:
67
+ - !ruby/object:Gem::Dependency
68
+ name: mongrel
69
+ version_requirement:
70
+ version_requirements: !ruby/object:Gem::Version::Requirement
71
+ requirements:
72
+ - - ">"
73
+ - !ruby/object:Gem::Version
74
+ version: 0.0.0
75
+ version:
76
+ - !ruby/object:Gem::Dependency
77
+ name: rake
78
+ version_requirement:
79
+ version_requirements: !ruby/object:Gem::Version::Requirement
80
+ requirements:
81
+ - - ">"
82
+ - !ruby/object:Gem::Version
83
+ version: 0.0.0
84
+ version:
85
+ - !ruby/object:Gem::Dependency
86
+ name: hoe
87
+ version_requirement:
88
+ version_requirements: !ruby/object:Gem::Version::Requirement
89
+ requirements:
90
+ - - ">"
91
+ - !ruby/object:Gem::Version
92
+ version: 0.0.0
93
+ version:
94
+ - !ruby/object:Gem::Dependency
95
+ name: rack
96
+ version_requirement:
97
+ version_requirements: !ruby/object:Gem::Version::Requirement
98
+ requirements:
99
+ - - ">"
100
+ - !ruby/object:Gem::Version
101
+ version: 0.0.0
102
+ version:
103
+ - !ruby/object:Gem::Dependency
104
+ name: hoe
105
+ version_requirement:
106
+ version_requirements: !ruby/object:Gem::Version::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: 1.3.0
111
+ version: