flannel 0.1.5 → 0.2.1
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/VERSION.yml +2 -2
- data/flannel.gemspec +6 -2
- data/lib/flannel.rb +1 -0
- data/lib/flannel/cache_location_does_not_exist_error.rb +4 -0
- data/lib/flannel/cutting_board.rb +2 -4
- data/lib/flannel/feed_parser.rb +17 -13
- data/lib/flannel/file_cache.rb +52 -0
- data/lib/flannel/shears.rb +5 -1
- data/lib/flannel/square.rb +3 -1
- data/lib/flannel/stripe.rb +14 -14
- data/test/feed_parser_test.rb +14 -0
- data/test/file_cache_test.rb +114 -0
- data/test/square_test.rb +0 -1
- data/test/stripe_test.rb +19 -7
- data/test/test_helper.rb +17 -0
- metadata +6 -2
data/VERSION.yml
CHANGED
data/flannel.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{flannel}
|
8
|
-
s.version = "0.1
|
8
|
+
s.version = "0.2.1"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Jamal Hansen"]
|
12
|
-
s.date = %q{2010-01-
|
12
|
+
s.date = %q{2010-01-14}
|
13
13
|
s.email = %q{jamal.hansen@gmail.com}
|
14
14
|
s.executables = ["quilt-it~", "quilt-it"]
|
15
15
|
s.extra_rdoc_files = [
|
@@ -50,14 +50,17 @@ Gem::Specification.new do |s|
|
|
50
50
|
"features/wiki_links.feature",
|
51
51
|
"flannel.gemspec",
|
52
52
|
"lib/flannel.rb",
|
53
|
+
"lib/flannel/cache_location_does_not_exist_error.rb",
|
53
54
|
"lib/flannel/cutting_board.rb",
|
54
55
|
"lib/flannel/feed_parser.rb",
|
56
|
+
"lib/flannel/file_cache.rb",
|
55
57
|
"lib/flannel/shears.rb",
|
56
58
|
"lib/flannel/square.rb",
|
57
59
|
"lib/flannel/stripe.rb",
|
58
60
|
"lib/flannel/wrappable.rb",
|
59
61
|
"test/cutting_board_test.rb",
|
60
62
|
"test/feed_parser_test.rb",
|
63
|
+
"test/file_cache_test.rb",
|
61
64
|
"test/flannel_test.rb",
|
62
65
|
"test/shears_test.rb",
|
63
66
|
"test/square_test.rb",
|
@@ -72,6 +75,7 @@ Gem::Specification.new do |s|
|
|
72
75
|
s.summary = %q{A soft comfortable worn in markup language for Ruby}
|
73
76
|
s.test_files = [
|
74
77
|
"test/test_helper.rb",
|
78
|
+
"test/file_cache_test.rb",
|
75
79
|
"test/stripe_test.rb",
|
76
80
|
"test/cutting_board_test.rb",
|
77
81
|
"test/feed_parser_test.rb",
|
data/lib/flannel.rb
CHANGED
@@ -1,14 +1,12 @@
|
|
1
|
-
#require 'shears'
|
2
|
-
|
3
1
|
|
4
2
|
module Flannel
|
5
3
|
class CuttingBoard
|
6
4
|
def initialize params={}
|
7
|
-
@
|
5
|
+
@params = params
|
8
6
|
end
|
9
7
|
|
10
8
|
def cut markup
|
11
|
-
shears = Flannel::Shears.new
|
9
|
+
shears = Flannel::Shears.new @params
|
12
10
|
squares = shears.cut markup
|
13
11
|
squares.map { |square| square.to_h }.join("\n\n")
|
14
12
|
end
|
data/lib/flannel/feed_parser.rb
CHANGED
@@ -3,6 +3,10 @@ require 'open-uri'
|
|
3
3
|
|
4
4
|
module Flannel
|
5
5
|
class FeedParser
|
6
|
+
def initialize params={}
|
7
|
+
@cache = params[:cache] if params.has_key?(:cache)
|
8
|
+
end
|
9
|
+
|
6
10
|
def sub_feeds(url)
|
7
11
|
url = format_url(url)
|
8
12
|
get_news(url)
|
@@ -15,7 +19,10 @@ module Flannel
|
|
15
19
|
end
|
16
20
|
|
17
21
|
def get_document url
|
18
|
-
|
22
|
+
doc = @cache.retrieve(url) if @cache
|
23
|
+
doc = open(url) unless doc
|
24
|
+
@cache.save url, doc if @cache
|
25
|
+
doc
|
19
26
|
end
|
20
27
|
|
21
28
|
def format_item(link, title)
|
@@ -24,19 +31,16 @@ module Flannel
|
|
24
31
|
|
25
32
|
def get_news url
|
26
33
|
item_string = ""
|
27
|
-
#begin
|
28
|
-
doc = Hpricot.XML(get_document(url))
|
29
34
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
#end
|
35
|
+
doc = Hpricot.XML(get_document(url))
|
36
|
+
|
37
|
+
(doc/"item").each do |item|
|
38
|
+
link = (item/"link").inner_html
|
39
|
+
title = (item/"title").inner_html
|
40
|
+
item_string << format_item(link, title)
|
41
|
+
end
|
42
|
+
|
43
|
+
item_string
|
40
44
|
end
|
41
45
|
end
|
42
46
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'flannel/cache_location_does_not_exist_error'
|
2
|
+
require 'digest/md5'
|
3
|
+
|
4
|
+
module Flannel
|
5
|
+
class FileCache
|
6
|
+
attr_reader :seconds_till_expiration
|
7
|
+
|
8
|
+
def initialize cache_location, seconds_till_expiration=3600
|
9
|
+
unless File.exists?(cache_location) && File.directory?(cache_location)
|
10
|
+
raise CacheLocationDoesNotExistError
|
11
|
+
end
|
12
|
+
|
13
|
+
@cache_location = cache_location
|
14
|
+
@seconds_till_expiration = seconds_till_expiration
|
15
|
+
end
|
16
|
+
|
17
|
+
def save url, data
|
18
|
+
key = generate_key url
|
19
|
+
File.open(File.join(@cache_location, key), 'w') {|f| f.write(data) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def retrieve url
|
23
|
+
key = generate_key url
|
24
|
+
file = File.join(@cache_location, key)
|
25
|
+
|
26
|
+
return nil unless File.exists?(file)
|
27
|
+
|
28
|
+
saved_at = File.mtime(file)
|
29
|
+
expires_at = saved_at + @seconds_till_expiration
|
30
|
+
|
31
|
+
if Time.now >= expires_at
|
32
|
+
File.delete(file)
|
33
|
+
return nil
|
34
|
+
end
|
35
|
+
|
36
|
+
read file
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def generate_key url
|
41
|
+
Digest::MD5.hexdigest(url)
|
42
|
+
end
|
43
|
+
|
44
|
+
def read file
|
45
|
+
data = ''
|
46
|
+
File.open(file, "r") { |f|
|
47
|
+
data = f.read
|
48
|
+
}
|
49
|
+
data
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/flannel/shears.rb
CHANGED
@@ -3,6 +3,10 @@
|
|
3
3
|
|
4
4
|
module Flannel
|
5
5
|
class Shears
|
6
|
+
def initialize params={}
|
7
|
+
@params = params
|
8
|
+
end
|
9
|
+
|
6
10
|
def cut markup
|
7
11
|
@squares = []
|
8
12
|
@square = Flannel::Square.new
|
@@ -23,7 +27,7 @@ module Flannel
|
|
23
27
|
|
24
28
|
def shift_square
|
25
29
|
@squares << @square unless @square.blank?
|
26
|
-
@square = Flannel::Square.new
|
30
|
+
@square = Flannel::Square.new @params
|
27
31
|
end
|
28
32
|
|
29
33
|
def need_new_square? line
|
data/lib/flannel/square.rb
CHANGED
@@ -9,11 +9,13 @@ module Flannel
|
|
9
9
|
@tags ={:preformatted => "pre", :feed => "ul", :list => "ul", :header_1 => "h1", :header_2 => "h2", :header_3 => "h3", :header_4 => "h4", :header_5 => "h5", :header_6 => "h6"}
|
10
10
|
@stripes = []
|
11
11
|
@style = :paragraph
|
12
|
+
@params = params
|
12
13
|
end
|
13
14
|
|
14
15
|
def << text
|
15
16
|
text = text.strip unless @style == :preformatted
|
16
|
-
@
|
17
|
+
@params[:style] = @style
|
18
|
+
@stripes << Flannel::Stripe.stitch(text, @params.dup)
|
17
19
|
end
|
18
20
|
|
19
21
|
def to_s
|
data/lib/flannel/stripe.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
#require 'wrappable'
|
2
|
-
#require 'feed_parser'
|
3
1
|
|
4
2
|
module Flannel
|
5
3
|
class Stripe
|
@@ -12,22 +10,19 @@ module Flannel
|
|
12
10
|
|
13
11
|
def initialize weave="", params={}
|
14
12
|
@weave = weave
|
15
|
-
@
|
16
|
-
@style = params[:style]
|
13
|
+
@params = params
|
17
14
|
end
|
18
15
|
|
19
16
|
def wiki_link topic
|
20
|
-
if @wiki_link
|
21
|
-
@wiki_link.call(permalink topic)
|
17
|
+
if @params[:wiki_link]
|
18
|
+
@params[:wiki_link].call(permalink topic)
|
22
19
|
else
|
23
20
|
permalink topic[1..-2]
|
24
21
|
end
|
25
22
|
end
|
26
23
|
|
27
24
|
def permalink topic
|
28
|
-
|
29
|
-
# thanks to ismasan http://snippets.dzone.com/posts/show/4457
|
30
|
-
(Iconv.new('US-ASCII//TRANSLIT', 'utf-8').iconv topic).gsub(/[^\w\s\-]/,'').gsub(/[^\w]|[\_]/,' ').split.join('-').downcase
|
25
|
+
topic.gsub(%r{[^/\w\s\-]},'').gsub(%r{[^\w/]|[\_]},' ').split.join('-').downcase
|
31
26
|
end
|
32
27
|
|
33
28
|
def empty?
|
@@ -36,12 +31,17 @@ module Flannel
|
|
36
31
|
|
37
32
|
def build_wiki_links
|
38
33
|
return @weave if preformatted
|
39
|
-
@weave.gsub(/-\w(.*?)\w>/) { |match| %{<a href="#{wiki_link match}">#{match
|
34
|
+
@weave.gsub(/-\w(.*?)\w>/) { |match| %{<a href="#{wiki_link match}">#{format_link_display(match)}</a>}}
|
35
|
+
end
|
36
|
+
|
37
|
+
def format_link_display text
|
38
|
+
text[1..-2].split("/").last
|
40
39
|
end
|
41
40
|
|
42
41
|
def to_h
|
42
|
+
|
43
43
|
if feed
|
44
|
-
parser = Flannel::FeedParser.new
|
44
|
+
parser = Flannel::FeedParser.new @params
|
45
45
|
parser.sub_feeds @weave
|
46
46
|
else
|
47
47
|
text = build_wiki_links
|
@@ -50,15 +50,15 @@ module Flannel
|
|
50
50
|
end
|
51
51
|
|
52
52
|
def preformatted
|
53
|
-
@style == :preformatted
|
53
|
+
@params[:style] == :preformatted
|
54
54
|
end
|
55
55
|
|
56
56
|
def list
|
57
|
-
@style == :list
|
57
|
+
@params[:style] == :list
|
58
58
|
end
|
59
59
|
|
60
60
|
def feed
|
61
|
-
@style == :feed
|
61
|
+
@params[:style] == :feed
|
62
62
|
end
|
63
63
|
|
64
64
|
def markup text
|
data/test/feed_parser_test.rb
CHANGED
@@ -2,6 +2,20 @@ require 'test_helper'
|
|
2
2
|
require 'mocha'
|
3
3
|
|
4
4
|
class FeedTest < Test::Unit::TestCase
|
5
|
+
context "wiring" do
|
6
|
+
setup do
|
7
|
+
@rubyyot = File.read(File.join(File.dirname(__FILE__), "..", "features", "fixtures", "rubyyot.rss"))
|
8
|
+
@expected_rubyyot = "<ul><li>\n <a href='http://blog.rubyyot"
|
9
|
+
end
|
10
|
+
|
11
|
+
should "be set up for feeds" do
|
12
|
+
feed = "http://blog.rubyyot.com/tag/rubyyot/feed/rss"
|
13
|
+
body = Flannel.quilt "& #{feed}"
|
14
|
+
|
15
|
+
assert_equal(@expected_rubyyot, body[0..40])
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
5
19
|
context "bootstrapping test" do
|
6
20
|
setup do
|
7
21
|
@devlicious = File.read(File.join(File.dirname(__FILE__), "..", "features", "fixtures", "devlicious.rss"))
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'mocha'
|
3
|
+
|
4
|
+
class FileCacheTest < Test::Unit::TestCase
|
5
|
+
context "Creating File cache" do
|
6
|
+
should "require a location" do
|
7
|
+
begin
|
8
|
+
cache = Flannel::FileCache.new
|
9
|
+
assert_fail "should have raised ArgumentError"
|
10
|
+
rescue ArgumentError => e
|
11
|
+
assert true
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
should "require a valid location" do
|
16
|
+
begin
|
17
|
+
cache = Flannel::FileCache.new "foo/bar"
|
18
|
+
assert_fail "should have raised Flannel::CacheLocationDoesNotExistError"
|
19
|
+
rescue Flannel::CacheLocationDoesNotExistError => e
|
20
|
+
assert true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context "Generating a key" do
|
26
|
+
setup do
|
27
|
+
@cache = Flannel::FileCache.new "tmp"
|
28
|
+
end
|
29
|
+
|
30
|
+
should "be private" do
|
31
|
+
begin
|
32
|
+
key = @cache.generate_key "http://example.com"
|
33
|
+
assert_fail "generate_key should be private"
|
34
|
+
rescue NoMethodError => e
|
35
|
+
assert true
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
should "make MD5 hash" do
|
40
|
+
url = "http://example.com"
|
41
|
+
key = @cache.send :generate_key, url
|
42
|
+
assert_equal(Digest::MD5.hexdigest(url), key)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context "saving a file" do
|
47
|
+
setup do
|
48
|
+
clear_dir "tmp"
|
49
|
+
@url = "http://example.com"
|
50
|
+
@cache_location = "tmp"
|
51
|
+
@cache = Flannel::FileCache.new @cache_location
|
52
|
+
@key = @cache.send :generate_key, @url
|
53
|
+
end
|
54
|
+
|
55
|
+
should "save data to a file" do
|
56
|
+
@cache.save @url, "some data"
|
57
|
+
assert File.exists?(File.join(@cache_location, @key)), "cache did not save data"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context "retrieving data" do
|
62
|
+
setup do
|
63
|
+
clear_dir "tmp"
|
64
|
+
@url = "http://example.com"
|
65
|
+
@cache_location = "tmp"
|
66
|
+
@data = "this is some data"
|
67
|
+
@cache = Flannel::FileCache.new @cache_location
|
68
|
+
@key = @cache.send :generate_key, @url
|
69
|
+
end
|
70
|
+
|
71
|
+
should "should return the saved data" do
|
72
|
+
@cache.save @url, @data
|
73
|
+
data = @cache.retrieve @url
|
74
|
+
assert_equal @data, data
|
75
|
+
end
|
76
|
+
|
77
|
+
should "should return nil if there is no data" do
|
78
|
+
data = @cache.retrieve @url
|
79
|
+
assert_nil data
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context "expiration" do
|
84
|
+
setup do
|
85
|
+
clear_dir "tmp"
|
86
|
+
@url = "http://example.com"
|
87
|
+
@cache_location = "tmp"
|
88
|
+
@data = "this is some data"
|
89
|
+
end
|
90
|
+
|
91
|
+
should "accept an expiration time" do
|
92
|
+
cache = Flannel::FileCache.new @cache_location, 1
|
93
|
+
assert_equal 1, cache.seconds_till_expiration
|
94
|
+
end
|
95
|
+
|
96
|
+
should "default expiration to 1 hour" do
|
97
|
+
cache = Flannel::FileCache.new @cache_location
|
98
|
+
assert_equal 3600, cache.seconds_till_expiration
|
99
|
+
end
|
100
|
+
|
101
|
+
should "miss when content is expired" do
|
102
|
+
cache = Flannel::FileCache.new @cache_location, 1
|
103
|
+
cache.save @url, @data
|
104
|
+
sleep 1
|
105
|
+
assert_nil(cache.retrieve(@url))
|
106
|
+
end
|
107
|
+
|
108
|
+
should "hit when content is not expired" do
|
109
|
+
cache = Flannel::FileCache.new @cache_location, 2
|
110
|
+
cache.save @url, @data
|
111
|
+
assert_equal(@data, cache.retrieve(@url))
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
data/test/square_test.rb
CHANGED
data/test/stripe_test.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'test_helper'
|
2
|
-
|
2
|
+
require 'flannel/stripe'
|
3
3
|
|
4
4
|
class StripeTest < Test::Unit::TestCase
|
5
5
|
context "basic operations" do
|
@@ -23,13 +23,12 @@ class StripeTest < Test::Unit::TestCase
|
|
23
23
|
context "building wiki links" do
|
24
24
|
setup do
|
25
25
|
wiki_link = lambda { |keyword| "http://www.example.com/foo/#{keyword}"}
|
26
|
-
@stripe = Flannel::Stripe.stitch "the -roof is on fire>", :wiki_link =>
|
27
|
-
wiki_link
|
26
|
+
@stripe = Flannel::Stripe.stitch "the -roof is on fire>", :wiki_link => wiki_link
|
28
27
|
end
|
29
28
|
|
30
29
|
should "build wiki links based on a lambda" do
|
31
30
|
assert_equal "http://www.example.com/foo/cheese",
|
32
|
-
@stripe.wiki_link("cheese")
|
31
|
+
@stripe.wiki_link("-cheese>")
|
33
32
|
end
|
34
33
|
|
35
34
|
should "find and replace wiki link markup" do
|
@@ -39,13 +38,26 @@ wiki_link
|
|
39
38
|
|
40
39
|
should "permalink topics when making wiki links" do
|
41
40
|
assert_equal "http://www.example.com/foo/cheese-tastes-good",
|
42
|
-
@stripe.wiki_link("cheese tastes good")
|
41
|
+
@stripe.wiki_link("-cheese tastes good>")
|
43
42
|
end
|
44
43
|
|
45
44
|
should "not be greedy in matching" do
|
46
45
|
stripe = Flannel::Stripe.stitch("a -foo> and a -bar>.")
|
47
|
-
assert_equal 'a <a href="foo">foo</a> and a <a href="bar">bar</a>.',
|
48
|
-
|
46
|
+
assert_equal 'a <a href="foo">foo</a> and a <a href="bar">bar</a>.', stripe.build_wiki_links
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "subdirectories" do
|
51
|
+
setup do
|
52
|
+
@stripe = Flannel::Stripe.stitch "I think it -cheese/tastes good>."
|
53
|
+
end
|
54
|
+
|
55
|
+
should "handle subdirectories" do
|
56
|
+
assert_equal "cheese/tastes-good", @stripe.wiki_link("-cheese/tastes good>")
|
57
|
+
end
|
58
|
+
|
59
|
+
should "not display directory info" do
|
60
|
+
assert_equal 'I think it <a href="cheese/tastes-good">tastes good</a>.', @stripe.build_wiki_links
|
49
61
|
end
|
50
62
|
end
|
51
63
|
|
data/test/test_helper.rb
CHANGED
@@ -1,10 +1,27 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'test/unit'
|
3
3
|
require 'shoulda'
|
4
|
+
require 'fileutils'
|
4
5
|
|
5
6
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
6
7
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
7
8
|
require 'flannel'
|
8
9
|
|
9
10
|
class Test::Unit::TestCase
|
11
|
+
def assert_fail message
|
12
|
+
assert false, message
|
13
|
+
end
|
14
|
+
|
15
|
+
def clear_dir dirname
|
16
|
+
Dir.foreach(dirname) do |f|
|
17
|
+
path = File.join(dirname, f)
|
18
|
+
if f == '.' or f == '..' then
|
19
|
+
next
|
20
|
+
elsif
|
21
|
+
File.directory?(path) then FileUtils.rm_rf(path)
|
22
|
+
else
|
23
|
+
FileUtils.rm( path )
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
10
27
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flannel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jamal Hansen
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2010-01-
|
12
|
+
date: 2010-01-14 00:00:00 -06:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -76,14 +76,17 @@ files:
|
|
76
76
|
- features/wiki_links.feature
|
77
77
|
- flannel.gemspec
|
78
78
|
- lib/flannel.rb
|
79
|
+
- lib/flannel/cache_location_does_not_exist_error.rb
|
79
80
|
- lib/flannel/cutting_board.rb
|
80
81
|
- lib/flannel/feed_parser.rb
|
82
|
+
- lib/flannel/file_cache.rb
|
81
83
|
- lib/flannel/shears.rb
|
82
84
|
- lib/flannel/square.rb
|
83
85
|
- lib/flannel/stripe.rb
|
84
86
|
- lib/flannel/wrappable.rb
|
85
87
|
- test/cutting_board_test.rb
|
86
88
|
- test/feed_parser_test.rb
|
89
|
+
- test/file_cache_test.rb
|
87
90
|
- test/flannel_test.rb
|
88
91
|
- test/shears_test.rb
|
89
92
|
- test/square_test.rb
|
@@ -119,6 +122,7 @@ specification_version: 3
|
|
119
122
|
summary: A soft comfortable worn in markup language for Ruby
|
120
123
|
test_files:
|
121
124
|
- test/test_helper.rb
|
125
|
+
- test/file_cache_test.rb
|
122
126
|
- test/stripe_test.rb
|
123
127
|
- test/cutting_board_test.rb
|
124
128
|
- test/feed_parser_test.rb
|