sapphire 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/bin/sapphire ADDED
@@ -0,0 +1,12 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
2
+
3
+ require 'sapphire'
4
+
5
+ include Sapphire::Sapphire
6
+
7
+ ARGV.each do |arg|
8
+ require arg
9
+ Runner.instance.last_scenario.execute 1
10
+ end
11
+
12
+ ConsoleReporter.new().OutputResults
data/lib/sapphire.rb CHANGED
@@ -1,4 +1,3 @@
1
- require "test/unit"
2
1
  require "rubygems"
3
2
  require "rspec"
4
3
  require 'yaml'
@@ -6,14 +5,18 @@ require 'selenium-webdriver'
6
5
  require 'delegate'
7
6
  require 'Forwardable'
8
7
  require 'win32ole'
8
+ require 'win32console'
9
9
  require 'json'
10
+ require 'colorize'
10
11
 
11
12
  require File.expand_path(File.dirname(__FILE__) +'/sapphire/Strategies/Strategy.rb', __FILE__)
13
+ require File.expand_path(File.dirname(__FILE__) +'/sapphire/Testing/Reporter.rb', __FILE__)
12
14
  Dir[File.dirname(__FILE__) + '/sapphire/Testing/*.rb'].each {|file| require file }
13
15
  Dir[File.dirname(__FILE__) + '/sapphire/Configuration/*.rb'].each {|file| require file }
14
16
  Dir[File.dirname(__FILE__) + '/sapphire/WebAbstractions/Controls/Base/*.rb'].each {|file| require file }
15
17
  Dir[File.dirname(__FILE__) + '/sapphire/WebAbstractions/Controls/*.rb'].each {|file| require file }
16
18
  Dir[File.dirname(__FILE__) + '/sapphire/DataAbstractions/*.rb'].each {|file| require file }
19
+ Dir[File.dirname(__FILE__) + '/sapphire/JobAbstractions/*.rb'].each {|file| require file }
17
20
  Dir[File.dirname(__FILE__) + '/sapphire/Strategies/*.rb'].each {|file| require file }
18
21
  Dir[File.dirname(__FILE__) + '/sapphire/DSL/Browser/*.rb'].each {|file| require file }
19
22
  Dir[File.dirname(__FILE__) + '/sapphire/DSL/Configuration/*.rb'].each {|file| require file }
@@ -32,6 +35,7 @@ module Sapphire
32
35
  include Configuration::AttrMethods
33
36
  include Configuration
34
37
  include DataAbstractions
38
+ include JobAbstractions
35
39
  include WebAbstractions
36
40
  include Testing
37
41
  end
@@ -0,0 +1,10 @@
1
+ module Sapphire
2
+ module DSL
3
+ module Browser
4
+ def Accept(browser)
5
+ x = browser.new
6
+ x.Accept
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,20 @@
1
+ module Sapphire
2
+ module DSL
3
+ module Browser
4
+ class AlertBox
5
+ def Accept
6
+ alert = $browser.switch_to.alert
7
+ alert.accept()
8
+ $browser.switch_to.window($browser.window_handles[0])
9
+ end
10
+
11
+ def Set(text)
12
+ alert = $browser.switch_to.alert
13
+ alert.send_keys(text)
14
+ alert.accept()
15
+ $browser.switch_to.window($browser.window_handles[0])
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -2,8 +2,14 @@ module Sapphire
2
2
  module DSL
3
3
  module Browser
4
4
  def Set(hash)
5
- ExecuteHashAgainstControl(hash, @page) do |control, arg|
6
- control.Set arg
5
+ if(hash.keys.first == AlertBox)
6
+ text = hash[item.keys.first]
7
+ klass = hash.keys.first.new
8
+ klass.Set text
9
+ else
10
+ ExecuteHashAgainstControl(hash, @page) do |control, arg|
11
+ control.Set arg
12
+ end
7
13
  end
8
14
  end
9
15
  end
@@ -18,6 +18,7 @@ class And
18
18
  end
19
19
 
20
20
  def AddResult(result)
21
+ result.item = self
21
22
  @results << result
22
23
  self.parent.results.last.AddChild(result)
23
24
  end
@@ -16,6 +16,7 @@ class Background
16
16
  end
17
17
 
18
18
  def AddResult(result)
19
+ result.item = self
19
20
  @results << result
20
21
  self.parent.result.AddChild(result)
21
22
  end
@@ -50,8 +50,8 @@ module Sapphire
50
50
 
51
51
  def Scenario(text, &block)
52
52
  Runner.instance.add_scenario(Scenario.new(text, &block))
53
- #Runner.instance.last_scenario.block.call
54
- Runner.instance.last_scenario.execute 1
53
+ Runner.instance.last_scenario.block.call
54
+ #Runner.instance.last_scenario.execute 1
55
55
  end
56
56
  end
57
57
  end
@@ -26,7 +26,9 @@ class Finally
26
26
  end
27
27
 
28
28
  def AddResult(result)
29
+ result.item = self
29
30
  @results << result
31
+ self.parent.AddResult(result)
30
32
  end
31
33
 
32
34
  end
@@ -45,11 +45,12 @@ class Given
45
45
  if(self.value.is_a? Pending)
46
46
  self.and << And.new(self, Pending.new(pre + text), &block)
47
47
  else
48
- self.and << And.new(self, text, &block)
48
+ self.and << And.new(self, pre + text, &block)
49
49
  end
50
50
  end
51
51
 
52
52
  def AddResult(result)
53
+ result.item = self
53
54
  @results << result
54
55
  self.parent.result.AddChild(result)
55
56
  end
@@ -2,7 +2,7 @@ module Sapphire
2
2
  module DSL
3
3
  module Scenarios
4
4
  class Scenario
5
- include Sapphire::Testing::RSpecRunner
5
+ include Sapphire::Testing::TestRunnerAdapter
6
6
 
7
7
  attr_reader :block
8
8
  attr_reader :value
@@ -27,6 +27,7 @@ class Then
27
27
  end
28
28
 
29
29
  def AddResult(result)
30
+ result.item = self
30
31
  @results << result
31
32
  self.parent.results.last.AddChild(result)
32
33
  end
@@ -40,6 +40,7 @@ class When
40
40
  end
41
41
 
42
42
  def AddResult(result)
43
+ result.item = self
43
44
  @results << result
44
45
  self.parent.results.last.AddChild(result)
45
46
  end
@@ -5,6 +5,9 @@ module Sapphire
5
5
  def Handle(item)
6
6
  x = AppConfig.Current.SpecsPath || ""
7
7
  require File.expand_path(x + item, __FILE__)
8
+ $stdout.puts item + ": "
9
+ Runner.instance.last_scenario.execute 1
10
+ $stdout.puts ""
8
11
  end
9
12
  end
10
13
  end
@@ -4,7 +4,12 @@ module Sapphire
4
4
  class PathHandler
5
5
  def Handle(item)
6
6
  x = AppConfig.Current.SpecsPath || ""
7
- Dir[x + item + '*.rb'].each do |file| require file end
7
+ Dir[x + item + '*.rb'].each do |file|
8
+ require file
9
+ $stdout.puts file + ": "
10
+ Runner.instance.last_scenario.execute 1
11
+ $stdout.puts ""
12
+ end
8
13
  end
9
14
  end
10
15
  end
@@ -2,7 +2,14 @@ module Sapphire
2
2
  module DSL
3
3
  module TestPlans
4
4
  def Run(item)
5
- Runner.instance.last_test_plan.Add(item)
5
+ if(item.is_a? Class)
6
+ if(item.new.is_a? Job)
7
+ job = item.new
8
+ system(job.PsExecPath + " " + job.Server + " -u " + job.User + " -p " + job.Password + " " + job.Path)
9
+ end
10
+ else
11
+ Runner.instance.last_test_plan.Add(item)
12
+ end
6
13
  end
7
14
  end
8
15
  end
@@ -47,7 +47,9 @@ module Sapphire
47
47
  end
48
48
 
49
49
  def execute
50
+ $stdout.puts ""
50
51
  @block.call
52
+ ConsoleReporter.new().OutputResults
51
53
  end
52
54
  end
53
55
  end
@@ -0,0 +1,28 @@
1
+ module Sapphire
2
+ module JobAbstractions
3
+ class Job
4
+
5
+ def Server
6
+ return ""
7
+ end
8
+
9
+ def User
10
+ return ""
11
+ end
12
+
13
+ def Password
14
+ return ""
15
+ end
16
+
17
+ def Path
18
+ return ""
19
+ end
20
+
21
+ def PsExecPath
22
+ return ""
23
+ end
24
+
25
+ end
26
+ end
27
+ end
28
+
@@ -22,7 +22,6 @@ module Sapphire
22
22
  return control.Equals(arg)
23
23
  end
24
24
  end
25
-
26
25
  end
27
26
  end
28
27
  end
@@ -0,0 +1,53 @@
1
+ module Sapphire
2
+ module Testing
3
+ class ConsoleReporter < Reporter
4
+
5
+ def PrintItem(result, depth)
6
+ Indent(depth)
7
+
8
+ if result.type == 'pass'
9
+ $stdout.puts result.text.green
10
+ elsif result.type == 'pending'
11
+ $stdout.puts result.text.yellow
12
+ Indent(depth+1)
13
+ $stdout.puts " ## Not Yet Implemented ##"
14
+ else
15
+ $stdout.puts result.text.red
16
+ Indent(depth+1)
17
+ $stdout.puts result.message
18
+ end
19
+ end
20
+
21
+ def PrintHeader()
22
+ $stdout.puts ""
23
+ end
24
+
25
+ def PrintFooter()
26
+ $stdout.puts ""
27
+ $stdout.puts "Finished in " + self.time.round(2).to_s + " seconds."
28
+ $stdout.puts "Test Count: " + self.test_count.to_s
29
+ $stdout.puts "Passing: " + self.passing_count.to_s.green
30
+ $stdout.puts "Failing: " + self.failing_count.to_s.red
31
+ $stdout.puts "Pending: " + self.pending_count.to_s.yellow
32
+ end
33
+
34
+ def Indent(depth)
35
+ (1..depth).each do
36
+ $stdout.print "\t"
37
+ end
38
+ end
39
+
40
+ def InsertLineBreak()
41
+ $stdout.puts ""
42
+ end
43
+
44
+ def PrePrint
45
+
46
+ end
47
+
48
+ def PostPrint
49
+
50
+ end
51
+ end
52
+ end
53
+ end
@@ -5,22 +5,21 @@ module Sapphire
5
5
  start = Time.now
6
6
  begin
7
7
  if(self.value.is_a? Pending)
8
- self.AddResult(ResultTree.new(self.text, TestResult.new("pending", self.text, "Pending", "", Time.now - start, id)))
9
- puts "(pending) " + self.text + " (" + (Time.now - start).to_s + "s)"
8
+ self.AddResult(ResultTree.new(self.text, TestResult.new("pending", self, "Pending", "", Time.now - start, id)))
9
+ $stdout.print "*".yellow
10
10
  return
11
11
  end
12
12
  self.block.call
13
- self.AddResult(ResultTree.new(self.text, TestResult.new("pass", self.text, "Success", "", Time.now - start, id)))
14
- puts "(pass) " + self.text + " (" + (Time.now - start).to_s + "s)"
13
+ self.AddResult(ResultTree.new(self.text, TestResult.new("pass", self, "Success", "", Time.now - start, id)))
14
+ $stdout.print ".".green
15
15
  rescue => msg
16
-
17
16
  stack = ""
18
17
  msg.backtrace.each do |line|
19
18
  stack += "\r\n" + line
20
19
  end
21
20
 
22
- self.AddResult(ResultTree.new(self.text, TestResult.new("fail", self.text, msg.message, stack, Time.now - start, id)))
23
- puts "(fail) " + self.text + ": " + msg.message + " (" + (Time.now - start).to_s + "s)"
21
+ self.AddResult(ResultTree.new(self.text, TestResult.new("fail", self, msg.message, stack, Time.now - start, id)))
22
+ $stdout.print "F".red
24
23
  end
25
24
  end
26
25
  @result
@@ -0,0 +1,31 @@
1
+ module Sapphire
2
+ module Testing
3
+ class HtmlReporter < Reporter
4
+
5
+ def PrintHeader()
6
+
7
+ end
8
+
9
+ def Output(result, tabber)
10
+
11
+ end
12
+
13
+ def PrePrint
14
+
15
+ end
16
+
17
+ def PrintResult(entry)
18
+
19
+ end
20
+
21
+ def PostPrint
22
+
23
+ end
24
+
25
+ def PrintFooter()
26
+
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -78,6 +78,8 @@ module Sapphire
78
78
  end
79
79
  end
80
80
  end
81
+
82
+ true
81
83
  end
82
84
  end
83
85
  end
@@ -0,0 +1,140 @@
1
+ module Sapphire
2
+ module Testing
3
+ class Reporter
4
+
5
+ attr_reader :test_count
6
+ attr_reader :passing_count
7
+ attr_reader :failing_count
8
+ attr_reader :pending_count
9
+ attr_reader :time
10
+
11
+ def OutputResults()
12
+
13
+ self.GatherResults
14
+
15
+ not_passing = self.GetNotPassing
16
+
17
+ self.PrintHeader
18
+
19
+ not_passing.keys.each do |key|
20
+ self.PrePrint
21
+ self.PrintResult not_passing[key]
22
+ self.PostPrint
23
+ end
24
+
25
+ self.PrintFooter
26
+
27
+ end
28
+
29
+ def Output(result, depth)
30
+
31
+ self.PrintItem(result, depth)
32
+
33
+ result.results.each do |sub_result|
34
+ self.Output(sub_result, depth+1)
35
+ end
36
+ end
37
+
38
+ def PrintResult(entry)
39
+ if entry.item.is_a? Given or entry.item.is_a? Background
40
+ self.InsertLineBreak
41
+ self.Output entry, 0
42
+ self.InsertLineBreak
43
+ elsif entry.item.is_a? When
44
+ self.PrintItem entry.parent, 0
45
+ self.Output entry, 1
46
+ self.InsertLineBreak
47
+ else
48
+ self.PrintResult entry.parent
49
+ end
50
+ end
51
+
52
+ def GatherResults()
53
+ @test_count = 0
54
+ @passing_count = 0
55
+ @failing_count = 0
56
+ @pending_count = 0
57
+ @time = 0
58
+
59
+ Runner.instance.scenarios.each do |scenario|
60
+ item = scenario.result
61
+ @time += item.time
62
+ item.results.each do |result|
63
+ output = self.Count result
64
+ @test_count += output[:tests]
65
+ @passing_count += output[:passing]
66
+ @failing_count += output[:failing]
67
+ @pending_count += output[:pending]
68
+ @time += result.time
69
+ end
70
+ end
71
+ end
72
+
73
+ def Count(result)
74
+ test_count = 1
75
+ passing_count = 0
76
+ failing_count = 0
77
+ pending_count = 0
78
+
79
+ if result.type == 'pass'
80
+ passing_count += 1
81
+ elsif result.type == 'pending'
82
+ pending_count += 1
83
+ else
84
+ failing_count += 1
85
+ end
86
+
87
+ result.results.each do |sub_result|
88
+ output = self.Count(sub_result)
89
+
90
+ test_count += output[:tests]
91
+ passing_count += output[:passing]
92
+ failing_count += output[:failing]
93
+ pending_count += output[:pending]
94
+ end
95
+
96
+ { :tests => test_count, :passing => passing_count, :failing => failing_count, :pending => pending_count }
97
+ end
98
+
99
+ def GetNotPassing
100
+
101
+ results = {}
102
+
103
+ Runner.instance.scenarios.each do |scenario|
104
+ results = results.merge(self.RecurseResult(scenario.result))
105
+ end
106
+
107
+ results
108
+ end
109
+
110
+ def RecurseResult(result)
111
+ results = {}
112
+
113
+ result.results.each do |r|
114
+ result_passes = r.type == "pass"
115
+ if !result_passes and (r.item.is_a? Given or r.item.is_a? When or r.item.is_a? Background)
116
+ results = results.merge({ r => r })
117
+ next
118
+ elsif !result_passes and (r.item.is_a? And or r.item.parent.is_a? Given)
119
+ results = results.merge({ r.parent => r.parent })
120
+ next
121
+ elsif !result_passes and (r.item.is_a? And and r.parent.item.is_a? When)
122
+ results = results.merge({ r.parent => r.parent })
123
+ next
124
+ elsif !result_passes and (r.item.is_a? Then)
125
+ results = results.merge({ r.parent => r.parent })
126
+ next
127
+ elsif !result_passes and (r.item.is_a? And and r.parent.item.is_a? Then)
128
+ results = results.merge({ r.parent.parent => r.parent.parent })
129
+ next
130
+ else
131
+ results = results.merge(self.RecurseResult(r))
132
+ end
133
+ end
134
+
135
+ results
136
+ end
137
+
138
+ end
139
+ end
140
+ end
@@ -4,18 +4,24 @@ module Sapphire
4
4
 
5
5
  attr_accessor :results
6
6
  attr_accessor :type
7
+ attr_accessor :text
8
+ attr_accessor :message
9
+ attr_accessor :stack
10
+ attr_accessor :time
11
+ attr_accessor :parent
12
+ attr_accessor :item
7
13
 
8
14
  def initialize(text, result)
9
15
 
10
16
  @type = 'pass'
11
- @execution_time = 0
17
+ @time = 0
12
18
 
13
19
  if(result != nil)
14
20
  self.type = result.type
15
21
  @iconCls = "accept" if result.type == "pass"
16
22
  @iconCls = "delete" if result.type == "fail"
17
23
  @iconCls = "error" if result.type == "pending"
18
- @execution_time = result.execution_time
24
+ @time = result.execution_time
19
25
  @expanded = true
20
26
  @message = result.message
21
27
  @stack = result.stack
@@ -27,6 +33,7 @@ module Sapphire
27
33
  end
28
34
 
29
35
  def AddChild(node)
36
+ node.parent = self
30
37
  @results << node
31
38
  @leaf = false
32
39
  end
@@ -5,6 +5,8 @@ module Sapphire
5
5
  attr_reader :results
6
6
  attr_accessor :myId
7
7
  attr_reader :type
8
+ attr_reader :time
9
+ attr_reader :parent
8
10
 
9
11
  def initialize(text)
10
12
  @text = text
@@ -12,10 +14,13 @@ module Sapphire
12
14
  @leaf = true
13
15
  @myId = -1
14
16
  @type = 'pass'
17
+ @time = 0
15
18
  end
16
19
 
17
20
  def AddChild(result)
21
+ result.parent = self
18
22
  @results << result
23
+ @time += result.time
19
24
  end
20
25
 
21
26
  def set_id(id)
@@ -5,14 +5,14 @@ module Sapphire
5
5
  attr_reader :execution_time
6
6
  attr_reader :message
7
7
  attr_reader :myId
8
- attr_reader :text
8
+ attr_reader :item
9
9
  attr_reader :type
10
10
  attr_reader :stack
11
11
  attr_reader :message
12
12
 
13
- def initialize(type, text, message, stack, execution_time, id)
13
+ def initialize(type, item, message, stack, execution_time, id)
14
14
  @myId = id
15
- @text = text
15
+ @item = item
16
16
  @execution_time = execution_time
17
17
  @message = message
18
18
  @stack = stack
@@ -3,6 +3,9 @@ module Sapphire
3
3
  module TestRunnerAdapter
4
4
 
5
5
  def execute(id)
6
+ @failures = []
7
+ @pendings = []
8
+ @success = []
6
9
 
7
10
  self.backgrounds.each do |b|
8
11
 
@@ -53,7 +56,6 @@ module Sapphire
53
56
  end
54
57
 
55
58
  end
56
-
57
59
  end
58
60
  end
59
61
  end
@@ -1,3 +1,3 @@
1
1
  module Sapphire
2
- VERSION = "0.2.2"
2
+ VERSION = "0.3.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sapphire
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-09-01 00:00:00.000000000Z
12
+ date: 2011-09-22 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: albacore
16
- requirement: &9316620 !ruby/object:Gem::Requirement
15
+ name: selenium-webdriver
16
+ requirement: &9278508 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *9316620
24
+ version_requirements: *9278508
25
25
  - !ruby/object:Gem::Dependency
26
- name: selenium-webdriver
27
- requirement: &9316344 !ruby/object:Gem::Requirement
26
+ name: colorize
27
+ requirement: &9278172 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *9316344
35
+ version_requirements: *9278172
36
36
  - !ruby/object:Gem::Dependency
37
- name: activesupport
38
- requirement: &9316080 !ruby/object:Gem::Requirement
37
+ name: win32console
38
+ requirement: &9277848 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,12 +43,13 @@ dependencies:
43
43
  version: '0'
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *9316080
46
+ version_requirements: *9277848
47
47
  description: An automated web acceptance test framework for non-technical resources
48
48
  using selenium-wedriver.
49
49
  email:
50
50
  - marcus.bratton@gmail.com
51
- executables: []
51
+ executables:
52
+ - sapphire
52
53
  extensions: []
53
54
  extra_rdoc_files: []
54
55
  files:
@@ -58,6 +59,8 @@ files:
58
59
  - lib/sapphire/DataAbstractions/Database.rb
59
60
  - lib/sapphire/DataAbstractions/NonQuery.rb
60
61
  - lib/sapphire/DataAbstractions/Query.rb
62
+ - lib/sapphire/DSL/Browser/Accept.rb
63
+ - lib/sapphire/DSL/Browser/AlertBox.rb
61
64
  - lib/sapphire/DSL/Browser/Browser.rb
62
65
  - lib/sapphire/DSL/Browser/Check.rb
63
66
  - lib/sapphire/DSL/Browser/Clear.rb
@@ -115,6 +118,7 @@ files:
115
118
  - lib/sapphire/DSL/TestPlans/PathHandler.rb
116
119
  - lib/sapphire/DSL/TestPlans/Run.rb
117
120
  - lib/sapphire/DSL/TestPlans/TestPlan.rb
121
+ - lib/sapphire/JobAbstractions/Job.rb
118
122
  - lib/sapphire/Strategies/ClassStrategy.rb
119
123
  - lib/sapphire/Strategies/DefaultStrategy.rb
120
124
  - lib/sapphire/Strategies/Evaluation.rb
@@ -122,8 +126,11 @@ files:
122
126
  - lib/sapphire/Strategies/NullModifier.rb
123
127
  - lib/sapphire/Strategies/Strategy.rb
124
128
  - lib/sapphire/Strategies/SymbolStrategy.rb
129
+ - lib/sapphire/Testing/ConsoleReporter.rb
125
130
  - lib/sapphire/Testing/Executable.rb
131
+ - lib/sapphire/Testing/HtmlReporter.rb
126
132
  - lib/sapphire/Testing/RakeTask.rb
133
+ - lib/sapphire/Testing/Reporter.rb
127
134
  - lib/sapphire/Testing/ResultList.rb
128
135
  - lib/sapphire/Testing/ResultTree.rb
129
136
  - lib/sapphire/Testing/RSpecRunner.rb
@@ -148,6 +155,7 @@ files:
148
155
  - lib/sapphire/WebAbstractions/Controls/TextBox.rb
149
156
  - lib/sapphire/WebAbstractions/Controls/Title.rb
150
157
  - lib/sapphire.rb
158
+ - bin/sapphire
151
159
  homepage: http://github.com/MarcusTheBold/Sapphire/
152
160
  licenses: []
153
161
  post_install_message: