quick_serve 0.3.4

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/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Marcin Bunsch
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,37 @@
1
+ = quick_serve
2
+
3
+ quick_serve is a super-simple way of serving static files for development. It was made mainly for javascript and css development, but might have other uses.
4
+
5
+ === General usage
6
+
7
+ Type 'qs' or 'quick_serve'. It will start a mongrel web server using the current directory as docroot. By default it will use port 5000, but if you run 'qs -p 4000' it will use 4000 (or any other you specify). Type 'qs -h' for more options.
8
+
9
+ === Snapshot mode usage
10
+
11
+ When evoked with -s trailed with an url, quick_serve will fetch the url and serve it regardless of the path you call. It's useful for testing js/css as it serves the snapshot from memory, minimizing cpu/memory usage leaving the browser with reloading js/css.
12
+
13
+ === Rails usage
14
+
15
+ quick_serve has a Rails adapter which can be called by adding this in environment.rb:
16
+
17
+ require 'quick_serve'
18
+
19
+ It's aimed at giving you a boost of speed on resource hungry pages when you're working on js/css.
20
+ It will store all generated pages requested with GET and having no flash on it and free the snapshot collection when:
21
+
22
+ * Any object is saved or destroyed in ActiveRecord
23
+ * Any file is changed in the app/ directory
24
+
25
+ Keep in mind, it will run only in development mode.
26
+
27
+ == Note on Patches/Pull Requests
28
+
29
+ * Fork the project.
30
+ * Make your feature addition or bug fix.
31
+ * Commit, do not mess with rakefile, version, or history.
32
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
33
+ * Send me a pull request.
34
+
35
+ == Copyright
36
+
37
+ Copyright (c) 2009 Marcin Bunsch . See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,60 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "quick_serve"
8
+ gem.summary = %Q{Super simple web server mainly for javascript development}
9
+ gem.description = %Q{}
10
+ gem.email = "marcin@applicake.com"
11
+ gem.homepage = "http://github.com/marcinbunsch/quick_serve"
12
+ gem.authors = ["Marcin Bunsch"]
13
+ gem.bindir = 'bin'
14
+ gem.executables = ['quick_serve', 'qs']
15
+ gem.default_executable = 'qs'
16
+ gem.files = []
17
+ gem.files = FileList['*', '{bin,lib,images,spec}/**/*']
18
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
19
+ end
20
+ rescue LoadError
21
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
22
+ end
23
+
24
+ require 'rake/testtask'
25
+ Rake::TestTask.new(:test) do |test|
26
+ test.libs << 'lib' << 'test'
27
+ test.pattern = 'test/**/*_test.rb'
28
+ test.verbose = true
29
+ end
30
+
31
+ begin
32
+ require 'rcov/rcovtask'
33
+ Rcov::RcovTask.new do |test|
34
+ test.libs << 'test'
35
+ test.pattern = 'test/**/*_test.rb'
36
+ test.verbose = true
37
+ end
38
+ rescue LoadError
39
+ task :rcov do
40
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
41
+ end
42
+ end
43
+
44
+ task :test => :check_dependencies
45
+
46
+ task :default => :test
47
+
48
+ require 'rake/rdoctask'
49
+ Rake::RDocTask.new do |rdoc|
50
+ if File.exist?('VERSION')
51
+ version = File.read('VERSION')
52
+ else
53
+ version = ""
54
+ end
55
+
56
+ rdoc.rdoc_dir = 'rdoc'
57
+ rdoc.title = "quick_serve #{version}"
58
+ rdoc.rdoc_files.include('README*')
59
+ rdoc.rdoc_files.include('lib/**/*.rb')
60
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.3.4
data/bin/qs ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'quick_serve'
4
+ QuickServe::Server.new.start
data/bin/quick_serve ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'quick_serve'
4
+ QuickServe::Server.new.start
@@ -0,0 +1,74 @@
1
+ module QuickServe
2
+ module Handlers
3
+ class Directory < Mongrel::DirHandler
4
+
5
+ STYLESHEET = <<-stylesheet
6
+ html, body {
7
+ font-family: "Lucida Grande", Verdana, sans-serif;
8
+ font-size: 90%;
9
+ font-weight: normal;
10
+ line-height: auto;
11
+ }
12
+ html {
13
+ background-color: #F0F0F0;
14
+ }
15
+ #body {
16
+ -moz-border-radius-bottomleft:10px;
17
+ -moz-border-radius-bottomright:10px;
18
+ -moz-border-radius-topleft:10px;
19
+ -moz-border-radius-topright:10px;
20
+ background-color: #fff;
21
+ border:1px solid #E1E1E1;
22
+ color:-moz-fieldtext;
23
+ width: 70%;
24
+ margin:4em auto;
25
+ padding:3em;
26
+ }
27
+ h1 {
28
+ font-size: 130%;
29
+ border-bottom: 1px solid #999;
30
+ padding: 3px;
31
+ }
32
+ a {
33
+ color: #666;
34
+ text-decoration: none
35
+ }
36
+ a:hover { color: #000 }
37
+ h3 {
38
+ font-size: 115%;
39
+ margin-bottom: 10px;
40
+ }
41
+ stylesheet
42
+
43
+ def send_dir_listing(base, dir, response)
44
+ # take off any trailing / so the links come out right
45
+ base = Mongrel::HttpRequest.unescape(base)
46
+ base.chop! if base[-1] == "/"[-1]
47
+
48
+ if @listing_allowed
49
+ response.start(200) do |head,out|
50
+ head[Mongrel::Const::CONTENT_TYPE] = "text/html"
51
+
52
+ out << "<html><head><title>Directory Listing for #{dir}</title><style type=\"text/css\">#{STYLESHEET}</style></head><body><div id=\"body\"><h1>Directory Listing for #{dir}</h1>"
53
+ entries = Dir.entries(dir)
54
+ entries = entries - ['..']
55
+ out << "<h3><a href=\"#{base}/..\">Up to higher level directory</a></h3>"
56
+ entries.each do |child|
57
+ next if child == "."
58
+ out << "<a href=\"#{base}/#{ Mongrel::HttpRequest.escape(child)}\">"
59
+ out << child
60
+ out << "</a><br/>"
61
+ end
62
+ out << "</div></body></html>"
63
+ end
64
+ else
65
+ response.start(403) do |head,out|
66
+ out.write("Directory listings not allowed")
67
+ end
68
+ end
69
+ end
70
+
71
+ end
72
+ end
73
+ end
74
+
@@ -0,0 +1,31 @@
1
+ module QuickServe
2
+ module Handlers
3
+ class Snapshot < Mongrel::HttpHandler
4
+
5
+ def initialize(url)
6
+ @url = url
7
+ @https = (url.match('https:') ? true : false)
8
+ @host = url.gsub(/(http)(s*)(\:\/\/)/, '').split('/').first
9
+ @snapshot = nil
10
+ fetch
11
+ super()
12
+ end
13
+
14
+ def fetch
15
+ require 'open-uri'
16
+ @snapshot = open(@url).read
17
+ host_root = (@https ? 'https://' : 'http://') + @host + '/'
18
+ @snapshot.gsub!(/(href=|src=)(['"])(\/)/, "\\1\\2#{host_root}\\4")
19
+ end
20
+
21
+ def process(request, response)
22
+ response.start do |head,out|
23
+ puts "quick_serve: served snapshot of #{@url}"
24
+ head["Content-Type"] = "text/html"
25
+ out << @snapshot
26
+ end
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,27 @@
1
+ # Here we hack mongrel do jump in front of the handler
2
+ module Mongrel
3
+ module Rails
4
+ class RailsHandler
5
+
6
+ alias_method :original_process, :process
7
+
8
+ def process(request, response)
9
+ # only serve GET
10
+ return original_process(request, response) if request.params['REQUEST_METHOD'] != 'GET'
11
+ url = 'http://' + request.params["HTTP_HOST"] + request.params["REQUEST_URI"]
12
+ snapshot = QuickServe::Rails::Snapshot.fetch(url)
13
+ if snapshot
14
+ response.start do |head,out|
15
+ puts "quick_serve: served snapshot of #{url}"
16
+ head["Content-Type"] = "text/html"
17
+ out << snapshot
18
+ end
19
+ else
20
+ original_process(request, response)
21
+ end
22
+ end
23
+
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ module ActionController
2
+ class Base
3
+ after_filter :store_snapshot
4
+ def store_snapshot
5
+ puts "quick_serve: stored snapshot as #{request.url}"
6
+ # do not store redirects or pages with flash
7
+ QuickServe::Rails::Snapshot.store(request.url, response.body) if !response.redirected_to and flash == {}
8
+ end
9
+ end
10
+ end
11
+
12
+ module ActiveRecord
13
+ class Base
14
+
15
+ after_save :free_snapshots
16
+ after_destroy :free_snapshots
17
+
18
+ def free_snapshots
19
+ # if rails uses active_record store, it changes with each request, making quick_serve unusable
20
+ # this probably should be a config option (somehow)
21
+ return if self.class.to_s == 'CGI::Session::ActiveRecordStore::Session'
22
+ puts "quick_serve: detected change in #{self.class}"
23
+ QuickServe::Rails::Snapshot.reset
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,81 @@
1
+ require 'find'
2
+ #
3
+ # QuickServe::Listener
4
+ #
5
+ # The generic Listener, which polls the files after a specific interval
6
+ #
7
+ module QuickServe
8
+ module Rails
9
+ class Listener
10
+ attr_accessor :files, :interval
11
+
12
+ # constructor
13
+ def initialize
14
+ self.interval = 2 # decrease the CPU load by increasing the interval
15
+ end
16
+
17
+ # find files and start the listener
18
+ def start
19
+ puts "** quick_serve: scanning for files... "
20
+ # build a file collection
21
+ find_files
22
+ puts "** quick_serve: watching #{files.size} files for changes... "
23
+ wait
24
+ end
25
+
26
+ # wait for a specified interval and check files for changes
27
+ # source: ZenTest/autotest.rb
28
+ def wait
29
+ Kernel.sleep self.interval until check_files
30
+ end
31
+
32
+ # check files to find these that have changed
33
+ def check_files
34
+ updated = []
35
+ files.each do |filename, mtime|
36
+ begin
37
+ current_mtime = File.stat(filename).mtime
38
+ rescue Errno::ENOENT
39
+ # file was not found and was probably deleted
40
+ # remove the file from the file list
41
+ files.delete(filename)
42
+ next
43
+ end
44
+ if current_mtime != mtime
45
+ updated << filename
46
+ # update the mtime in file registry so we it's only send once
47
+ files[filename] = current_mtime
48
+ puts "quick_serve: spotted change in #{filename}"
49
+ end
50
+ end
51
+ QuickServe::Rails::Snapshot.reset if updated != []
52
+ false
53
+ end
54
+
55
+ ##
56
+ # Find the files to process, ignoring temporary files, source
57
+ # configuration management files, etc., and return a Hash mapping
58
+ # filename to modification time.
59
+ # source: ZenTest/autotest.rb
60
+ def find_files
61
+ result = {}
62
+ targets = ['app'] # start simple
63
+ targets.each do |target|
64
+ order = []
65
+ Find.find(target) do |f|
66
+ next if test ?d, f
67
+ next if f =~ /(swp|~|rej|orig)$/ # temporary/patch files
68
+ next if f =~ /(\.svn|\.git)$/ # subversion/git
69
+ next if f =~ /\/\.?#/ # Emacs autosave/cvs merge files
70
+ filename = f.sub(/^\.\//, '')
71
+
72
+ result[filename] = File.stat(filename).mtime rescue next
73
+ end
74
+ end
75
+
76
+ self.files = result
77
+ end
78
+
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,28 @@
1
+ # Storage for snapshots of pages generated by rails
2
+ module QuickServe
3
+ module Rails
4
+ class Snapshot
5
+ @@snapshots = {}
6
+
7
+ # return collection
8
+ def self.dump
9
+ @@snapshots
10
+ end
11
+
12
+ # remove all snapshots
13
+ def self.reset
14
+ @@snapshots = {}
15
+ end
16
+
17
+ # store a snapshot under specified key
18
+ def self.store(key, snapshot)
19
+ @@snapshots[key] = snapshot
20
+ end
21
+
22
+ # fetch a snapshot stored under specified key
23
+ def self.fetch(key)
24
+ @@snapshots[key]
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,6 @@
1
+ require 'quick_serve/rails/snapshot'
2
+ require 'quick_serve/rails/listener'
3
+ require 'quick_serve/rails/ext/mongrel'
4
+ require 'quick_serve/rails/ext/rails'
5
+ puts "** quick_serve: attaching rails snapshot handler"
6
+ Thread.new { QuickServe::Rails::Listener.new.start }
@@ -0,0 +1,93 @@
1
+ module QuickServe
2
+ class Server
3
+
4
+ def initialize
5
+ @options = { :dir => '.', :port => 5000, :host => '0.0.0.0', :deamon => false, :url => nil }
6
+ parse
7
+ end
8
+
9
+ def start
10
+ if @options[:url]
11
+ puts "quick_serve: running in snapshot mode using #{@options[:url]} as source"
12
+ else
13
+ puts "quick_serve: mongrel running on port #{@options[:port]} with current directory as docroot"
14
+ end
15
+ begin
16
+ if @options[:deamon]
17
+ pid = fork do
18
+ $stderr, $stdout = StringIO.new, StringIO.new
19
+ serve
20
+ end
21
+ File.open(pidfile, 'w') {|f| f.write(pid) }
22
+ else
23
+ serve
24
+ end
25
+ rescue Errno::EADDRINUSE
26
+ puts "quick_serve: port #{@options[:port]} is used by another process. Please specify other port using the -p option"
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def serve
33
+ options = @options
34
+ config = Mongrel::Configurator.new :host => options[:host], :port => options[:port] do
35
+ listener do
36
+ if options[:url]
37
+ require 'quick_serve/handlers/snapshot'
38
+ uri "/", :handler => QuickServe::Handlers::Snapshot.new(options[:url])
39
+ else
40
+ require 'quick_serve/handlers/directory'
41
+ uri "/", :handler => QuickServe::Handlers::Directory.new(options[:dir])
42
+ end
43
+ end
44
+ trap("INT") { stop }
45
+ run
46
+ end
47
+ config.join
48
+ end
49
+
50
+ def parse
51
+ require 'optparse'
52
+ OptionParser.new do |opts|
53
+ opts.banner = "Usage: qs [options]"
54
+
55
+ opts.on("-p PORT", "--port PORT", "Specify port (default: 5000)") { |value| @options[:port] = value; }
56
+ opts.on("--dir DIRECTORY", "Specify directory to act as docroot (default: current directory)") { |value| @options[:dir] = value; }
57
+ opts.on("--host HOST", "Specify host (default: 0.0.0.0)") { |value| @options[:host] = value; }
58
+ opts.on("-s URL", "--snapshot URL", "Specify url for snapshot") { |value| @options[:url] = value; }
59
+ opts.on("-q", "quit deamon (if present)") { quit }
60
+ opts.on("-d", "--deamon", "Run as a deamon process") { @options[:deamon] = true; }
61
+ opts.on_tail("-h", "--help", "Show this message") do
62
+ puts opts
63
+ exit
64
+ end
65
+ end.parse!
66
+ end
67
+
68
+ def quit
69
+
70
+ print "Stopping quick serve... "
71
+ die(" pid file not found") if !File.exists?(pidfile)
72
+ pid = File.read(pidfile)
73
+ begin
74
+ if Process.kill("INT", pid.to_i) == 1
75
+ puts ' ok'
76
+ File.delete(pidfile) if File.exist?(pidfile)
77
+ end
78
+ rescue Errno::ESRCH
79
+ puts ' not found'
80
+ end
81
+ exit
82
+ end
83
+
84
+ def pidfile
85
+ File.join(@options[:dir], "qs.#{@options[:port]}.pid")
86
+ end
87
+
88
+ def die(msg)
89
+ puts msg
90
+ exit(1)
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,15 @@
1
+ if defined?(RAILS_ENV)
2
+ if RAILS_ENV == 'development'
3
+ require 'quick_serve/rails'
4
+ else
5
+ puts "** quick_serve: quick_serve rails adapter can run only in development environment"
6
+ end
7
+ else
8
+ begin
9
+ require 'mongrel'
10
+ rescue LoadError
11
+ puts "quick_serve requires mongrel. Install it with: gem install mongrel"
12
+ exit(1)
13
+ end
14
+ require 'quick_serve/server'
15
+ end
@@ -0,0 +1,58 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{quick_serve}
8
+ s.version = "0.3.4"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Marcin Bunsch"]
12
+ s.date = %q{2009-09-17}
13
+ s.default_executable = %q{qs}
14
+ s.description = %q{}
15
+ s.email = %q{marcin@applicake.com}
16
+ s.executables = ["quick_serve", "qs"]
17
+ s.extra_rdoc_files = [
18
+ "LICENSE",
19
+ "README.rdoc"
20
+ ]
21
+ s.files = [
22
+ "LICENSE",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "bin/qs",
27
+ "bin/quick_serve",
28
+ "lib/quick_serve.rb",
29
+ "lib/quick_serve/handlers/directory.rb",
30
+ "lib/quick_serve/handlers/snapshot.rb",
31
+ "lib/quick_serve/rails.rb",
32
+ "lib/quick_serve/rails/ext/mongrel.rb",
33
+ "lib/quick_serve/rails/ext/rails.rb",
34
+ "lib/quick_serve/rails/listener.rb",
35
+ "lib/quick_serve/rails/snapshot.rb",
36
+ "lib/quick_serve/server.rb",
37
+ "quick_serve.gemspec"
38
+ ]
39
+ s.has_rdoc = true
40
+ s.homepage = %q{http://github.com/marcinbunsch/quick_serve}
41
+ s.rdoc_options = ["--charset=UTF-8"]
42
+ s.require_paths = ["lib"]
43
+ s.rubygems_version = %q{1.3.1}
44
+ s.summary = %q{Super simple web server mainly for javascript development}
45
+ s.test_files = [
46
+ "test/test_helper.rb"
47
+ ]
48
+
49
+ if s.respond_to? :specification_version then
50
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
51
+ s.specification_version = 2
52
+
53
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
54
+ else
55
+ end
56
+ else
57
+ end
58
+ end
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'quick_serve'
8
+
9
+ class Test::Unit::TestCase
10
+ end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: quick_serve
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.4
5
+ platform: ruby
6
+ authors:
7
+ - Marcin Bunsch
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-10-25 00:00:00 +02:00
13
+ default_executable: qs
14
+ dependencies: []
15
+
16
+ description: ""
17
+ email: marcin@applicake.com
18
+ executables:
19
+ - quick_serve
20
+ - qs
21
+ extensions: []
22
+
23
+ extra_rdoc_files:
24
+ - LICENSE
25
+ - README.rdoc
26
+ files:
27
+ - LICENSE
28
+ - README.rdoc
29
+ - Rakefile
30
+ - VERSION
31
+ - bin/qs
32
+ - bin/quick_serve
33
+ - lib/quick_serve.rb
34
+ - lib/quick_serve/handlers/directory.rb
35
+ - lib/quick_serve/handlers/snapshot.rb
36
+ - lib/quick_serve/rails.rb
37
+ - lib/quick_serve/rails/ext/mongrel.rb
38
+ - lib/quick_serve/rails/ext/rails.rb
39
+ - lib/quick_serve/rails/listener.rb
40
+ - lib/quick_serve/rails/snapshot.rb
41
+ - lib/quick_serve/server.rb
42
+ - quick_serve.gemspec
43
+ has_rdoc: true
44
+ homepage: http://github.com/marcinbunsch/quick_serve
45
+ licenses: []
46
+
47
+ post_install_message:
48
+ rdoc_options:
49
+ - --charset=UTF-8
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ version:
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: "0"
63
+ version:
64
+ requirements: []
65
+
66
+ rubyforge_project:
67
+ rubygems_version: 1.3.5
68
+ signing_key:
69
+ specification_version: 3
70
+ summary: Super simple web server mainly for javascript development
71
+ test_files:
72
+ - test/test_helper.rb