ballmer 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +1 -0
  5. data/Gemfile +4 -0
  6. data/Guardfile +11 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +54 -0
  9. data/Rakefile +8 -0
  10. data/ballmer.gemspec +29 -0
  11. data/bin/ballmer +10 -0
  12. data/bin/ballmer-console +9 -0
  13. data/bin/ballmer-duplicator +30 -0
  14. data/bin/ballmer-pack +19 -0
  15. data/bin/ballmer-unpack +14 -0
  16. data/lib/ballmer/document/archive.rb +89 -0
  17. data/lib/ballmer/document/content_types.rb +43 -0
  18. data/lib/ballmer/document/part.rb +26 -0
  19. data/lib/ballmer/document/rels.rb +79 -0
  20. data/lib/ballmer/document/xml_part.rb +29 -0
  21. data/lib/ballmer/document.rb +52 -0
  22. data/lib/ballmer/presentation/notes.rb +32 -0
  23. data/lib/ballmer/presentation/notes_parser.rb +57 -0
  24. data/lib/ballmer/presentation/slide.rb +19 -0
  25. data/lib/ballmer/presentation/slides.rb +172 -0
  26. data/lib/ballmer/presentation/tags.rb +50 -0
  27. data/lib/ballmer/presentation/tags.xml +3 -0
  28. data/lib/ballmer/presentation.rb +20 -0
  29. data/lib/ballmer/version.rb +3 -0
  30. data/lib/ballmer.rb +7 -0
  31. data/spec/fixtures/notes.pptx +0 -0
  32. data/spec/fixtures/presentation1.pptx +0 -0
  33. data/spec/fixtures/presentation2.pptx +0 -0
  34. data/spec/fixtures/presentation3.pptx +0 -0
  35. data/spec/lib/ballmer/document/content_types_spec.rb +35 -0
  36. data/spec/lib/ballmer/document/rels_spec.rb +43 -0
  37. data/spec/lib/ballmer/document_spec.rb +12 -0
  38. data/spec/lib/ballmer/presentation/notes_parser_spec.rb +19 -0
  39. data/spec/lib/ballmer/presentation/tags_spec.rb +22 -0
  40. data/spec/lib/ballmer/presentation_spec.rb +118 -0
  41. data/spec/spec_helper.rb +38 -0
  42. metadata +198 -0
@@ -0,0 +1,19 @@
1
+ module Ballmer
2
+ class Presentation
3
+ # Load a slide up in thar.
4
+ class Slide < Document::XMLPart
5
+ # Key used to look up slides from [Content-Types].xml.
6
+ CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.slide+xml".freeze
7
+
8
+ # Key used to look up slides from .xml.rel documents
9
+ REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide".freeze
10
+
11
+ def notes
12
+ # TODO - Move a type caster into rels based on content type like
13
+ # rels[Notes::REL_TYPE].first
14
+ notes_path = rels.targets(Notes::REL_TYPE).first.expand_path(@path.dirname)
15
+ Notes.new(@doc, notes_path)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,172 @@
1
+ module Ballmer
2
+ class Presentation
3
+ # Manages concerns around keeping slide and notesSlides files in
4
+ # sync with an array of slides. These basically needs to trasnact
5
+ # the slide\d+ and slideNote\d+ numbers to be in sync with an array.
6
+ # Its a big, ugly ass complicated beast. Send your thank you cards to Bill Gates.
7
+ class Slides
8
+ include Enumerable
9
+
10
+ def initialize(doc)
11
+ @doc = doc
12
+ end
13
+
14
+ def each(&block)
15
+ # TODO - Do NOT read content-types, but read Rels instead (and move this type casting in there.)
16
+ @doc.content_types[Slide::CONTENT_TYPE].each { |path| block.call slide path }
17
+ end
18
+
19
+ # This method is crazy because it has to manipulate a ton of files within the PPTX. Most of
20
+ # what happens in here I figured out by diff-ing PPTX files that had copies of identical slides, but
21
+ # a different number of slides.
22
+ def push(slide)
23
+ n = to_a.size + 1
24
+ # Paths within the zip file of new files we have to write.
25
+ slide_path = Pathname.new("/ppt/slides/slide#{n}.xml")
26
+ slide_rels_path = Pathname.new("/ppt/slides/_rels/slide#{n}.xml.rels")
27
+ slide_notes_path = Pathname.new("/ppt/notesSlides/notesSlide#{n}.xml")
28
+ slide_notes_rels_path = Pathname.new("/ppt/notesSlides/_rels/notesSlide#{n}.xml.rels")
29
+
30
+ # Update ./ppt
31
+ # !!! CREATE !!!
32
+ # ./slides
33
+ # Create new files
34
+ # ./slide(\d+).xml file
35
+ @doc.copy slide_path, slide.path
36
+ # ./_rels/slide(\d+).xml.rels
37
+ @doc.copy slide_rels_path, slide.rels.path
38
+ # ./notesSlides
39
+ # Create new files
40
+ # ./notesSlide(\d+).xml file
41
+ @doc.copy slide_notes_path, slide.notes.path
42
+ # ./_rels/notesSlide(\d+).xml.rels
43
+ @doc.copy slide_notes_rels_path, slide.notes.rels.path
44
+
45
+ # !!! UPDATES !!!
46
+ # Update the notes in the new slide to point at the new notes
47
+ @doc.edit_xml slide_rels_path do |xml|
48
+ # TODO - Move this rel logic into the parts so that we don't have to repeat ourselves when calculating this stuff out.
49
+ xml.at_xpath("//xmlns:Relationship[@Type='#{Notes::REL_TYPE}']")['Target'] = slide_notes_path.relative_path_from(slide_path.dirname)
50
+ end
51
+
52
+ # Update teh slideNotes reference to point at the new slide
53
+ @doc.edit_xml slide_notes_rels_path do |xml|
54
+ xml.at_xpath("//xmlns:Relationship[@Type='#{Slide::REL_TYPE}']")['Target'] = slide_path.relative_path_from(slide_notes_path.dirname)
55
+ end
56
+
57
+ # ./_rels/presentation.xml.rels
58
+ # Update Relationship ids
59
+ # Insert a new one slideRef
60
+ @doc.edit_xml @doc.presentation.rels.path do |xml|
61
+ # Calucate the next id
62
+ next_id = xml.xpath('//xmlns:Relationship[@Id]').map{ |n| n['Id'] }.sort.last.succ
63
+ # TODO - Figure out how to make this more MS idiomatic up 9->10 instead of incrementing
64
+ # the character....
65
+ # Insert that into the slide and crakc open the presentation.xml file
66
+ types = xml.at_xpath('/xmlns:Relationships')
67
+ types << Nokogiri::XML::Node.new("Relationship", xml).tap do |n|
68
+ n['Id'] = next_id
69
+ n['Type'] = Slide::REL_TYPE
70
+ n['Target'] = slide_path.relative_path_from(@doc.presentation.path.dirname)
71
+ end
72
+ # ./presentation.xml
73
+ # Update attr
74
+ # p:notesMasterId
75
+ # Insert attr
76
+ # p:sldId, increment, etc.
77
+ @doc.edit_xml '/ppt/presentation.xml' do |xml|
78
+ slides = xml.at_xpath('/p:presentation/p:sldIdLst')
79
+ next_slide_id = slides.xpath('//p:sldId[@id]').map{ |n| n['id'] }.sort.last.succ
80
+ slides << Nokogiri::XML::Node.new("p:sldId", xml).tap do |n|
81
+ # TODO - Fix the ID that's jacked up.
82
+ n['id'] = next_slide_id
83
+ n['r:id'] = next_id
84
+ end
85
+ end
86
+ end
87
+
88
+ # Update ./[Content-Types].xml with new slide link and slideNotes link
89
+ @doc.edit_xml @doc.content_types.path do |xml|
90
+ types = xml.at_xpath('/xmlns:Types')
91
+ types << Nokogiri::XML::Node.new("Override", xml).tap do |n|
92
+ n['PartName'] = slide_path
93
+ n['ContentType'] = Slide::CONTENT_TYPE
94
+ end
95
+ types << Nokogiri::XML::Node.new("Override", xml).tap do |n|
96
+ n['PartName'] = slide_notes_path
97
+ n['ContentType'] = Notes::CONTENT_TYPE
98
+ end
99
+ end
100
+
101
+ # Great, that's all done, so lets return the slide eh?
102
+ slide slide_path
103
+ end
104
+
105
+ # Removes a slide from the slides collection
106
+ def delete(slide)
107
+ # ./_rels/presentation.xml.rels
108
+ # Update Relationship ids
109
+ # Insert a new one slideRef
110
+ @doc.edit_xml @doc.presentation.rels.path do |xml|
111
+ # Calucate the next id
112
+ # next_id = xml.xpath('//xmlns:Relationship[@Id]').map{ |n| n['Id'] }.sort.last.succ
113
+ # TODO - Figure out how to make this more MS idiomatic up 9->10 instead of incrementing
114
+ # the character....
115
+ # Insert that into the slide and crakc open the presentation.xml file
116
+
117
+ target = slide.path.relative_path_from(@doc.presentation.path.dirname)
118
+ relationship = xml.at_xpath("/xmlns:Relationships/xmlns:Relationship[@Type='#{Slide::REL_TYPE}' and @Target='#{target}']")
119
+ # ./presentation.xml
120
+ # Update attr
121
+ # p:notesMasterId
122
+ # Insert attr
123
+ # p:sldId, increment, etc.
124
+ @doc.edit_xml '/ppt/presentation.xml' do |xml|
125
+ xml.at_xpath("/p:presentation/p:sldIdLst/p:sldId[@r:id='#{relationship['Id']}']").remove
126
+ end
127
+ relationship.remove
128
+ end
129
+
130
+ # Delete slide link and slideNotes link from ./[Content-Types].xml
131
+ @doc.edit_xml @doc.content_types.path do |xml|
132
+ xml.at_xpath("/xmlns:Types/xmlns:Override[@ContentType='#{Slide::CONTENT_TYPE}' and @PartName='#{slide.path}']").remove
133
+ xml.at_xpath("/xmlns:Types/xmlns:Override[@ContentType='#{Notes::CONTENT_TYPE}' and @PartName='#{slide.notes.path}']").remove
134
+ end
135
+
136
+ # Update ./ppt
137
+ # !!! DESTROY !!!
138
+ # ./slides
139
+ # Delete files
140
+ # ./_rels/notesSlide(\d+).xml.rels
141
+ @doc.delete slide.notes.rels.path
142
+ # ./notesSlide(\d+).xml file
143
+ @doc.delete slide.notes.path
144
+ # ./_rels/slide(\d+).xml.rels
145
+ @doc.delete slide.rels.path
146
+ # ./slide(\d+).xml file
147
+ @doc.delete slide.path
148
+ # ./notesSlides
149
+ # Delete files
150
+
151
+ # Hooray! We're done! Ummm, what should we return though? can't be the slide since
152
+ # its destroyed and there's no practical way to keep it around in memory.
153
+ end
154
+
155
+ private
156
+ def slide(path)
157
+ Slide.new(@doc, path)
158
+ end
159
+ # Reads from the [Content_Types].xml file the paths for the slide
160
+ # <Override PartName="/ppt/slides/slide1.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slide+xml"/>
161
+ def parts
162
+ @doc.content_types.parts Slide::CONTENT_TYPE
163
+ end
164
+
165
+ # Microsoft decided it would be cool to start at 1 instead of 0
166
+ # for the part indices, so this deals with that seperatly
167
+ def next_number
168
+ self.to_a.size + 1
169
+ end
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,50 @@
1
+ module Ballmer
2
+ class Presentation
3
+ class Tags < Document::XMLPart
4
+ # TODO, there are three types of notes. We need to figure out
5
+ # how to resolve the slide number, notes, and whatever the hell else
6
+ # the first note type is.
7
+
8
+ # Key used to look up notes from [Content-Types].xml.
9
+ CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.tags+xml".freeze
10
+
11
+ # Key used to look up notes from .xml.rel documents
12
+ REL_TYPE = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/tags'.freeze
13
+
14
+ # Read tag
15
+ def [](key)
16
+ if tag = tag(key)
17
+ tag['val']
18
+ end
19
+ end
20
+
21
+ # If the tag exists, create it -- otherwise update.
22
+ # <p:tag name="" val=""/>
23
+ def []=(key, value)
24
+ unless tag key # Don't add the tag if it already exists.
25
+ tag_list << Nokogiri::XML::Node.new("p:tag", @xml) do |tag|
26
+ tag['name'] = key
27
+ tag['val'] = value
28
+ end
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def tag(key)
35
+ tag_list.at_xpath("./p:tag[@name='#{key}']")
36
+ end
37
+
38
+ def tag_list
39
+ xml.at_xpath('p:tagLst')
40
+ end
41
+
42
+ def self.build(doc, path)
43
+ # Write the template
44
+ doc.write path, File.read(File.join(File.dirname(__FILE__), 'tags.xml'))
45
+ # K, now lets read it and decorate with the part.
46
+ new doc, path
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,3 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2
+ <p:tagLst xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
3
+ </p:tagLst>
@@ -0,0 +1,20 @@
1
+ module Ballmer
2
+ # Represents a presentation that has many slides.
3
+ class Presentation < Document
4
+ autoload :Slides, 'ballmer/presentation/slides'
5
+ autoload :Slide, 'ballmer/presentation/slide'
6
+ autoload :Notes, 'ballmer/presentation/notes'
7
+ autoload :NotesParser, 'ballmer/presentation/notes_parser'
8
+ autoload :Tags, 'ballmer/presentation/tags'
9
+
10
+ # Return an array of slides.
11
+ def slides
12
+ @slides ||= Slides.new(self)
13
+ end
14
+
15
+ # Presentation XML file.
16
+ def presentation
17
+ XMLPart.new(self, '/ppt/presentation.xml')
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,3 @@
1
+ module Ballmer
2
+ VERSION = "0.0.1"
3
+ end
data/lib/ballmer.rb ADDED
@@ -0,0 +1,7 @@
1
+ require "ballmer/version"
2
+ require "nokogiri"
3
+
4
+ module Ballmer
5
+ autoload :Presentation, 'ballmer/presentation'
6
+ autoload :Document, 'ballmer/document'
7
+ end
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ballmer::Document::ContentTypes do
4
+ subject { read_presentation('presentation3.pptx') }
5
+ let(:tags) { Ballmer::Presentation::Tags.new(subject, 'tags/tag1.xml') }
6
+
7
+ describe "#exists?" do
8
+ it "should detect if part is in XML" do
9
+ subject.content_types.should exist(subject.slides.first)
10
+ end
11
+
12
+ it "should detect if part is not in XML" do
13
+ subject.content_types.should_not exist(tags)
14
+ end
15
+ end
16
+
17
+ describe "#append" do
18
+ before(:each) do
19
+ subject.content_types.append tags
20
+ end
21
+
22
+ let(:nodes) do
23
+ subject.content_types.xml.xpath("//xmlns:Override[@ContentType='#{tags.class::CONTENT_TYPE}' and @PartName='#{tags.path}']")
24
+ end
25
+
26
+ it "should write part to XML" do
27
+ nodes.should have(1).item
28
+ end
29
+
30
+ it "should not write duplicate parts to XML" do
31
+ subject.content_types.append tags
32
+ nodes.should have(1).item
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ballmer::Document::Rels do
4
+ subject { read_presentation('presentation3.pptx') }
5
+ let(:tags) { Ballmer::Presentation::Tags.new(subject, '/ppt/tags/tag1.xml') }
6
+ let(:slide) { subject.slides.first }
7
+
8
+ describe "#exists?" do
9
+ it "should detect if part is in XML" do
10
+ subject.presentation.rels.should exist(subject.slides.first)
11
+ end
12
+
13
+ it "should detect if part is not in XML" do
14
+ subject.presentation.rels.should_not exist(tags)
15
+ end
16
+ end
17
+
18
+ describe "#append" do
19
+ before(:each) do
20
+ slide.rels.append tags
21
+ end
22
+
23
+ let(:nodes) do
24
+ slide.rels.xml.xpath("//xmlns:Relationship[@Type='#{tags.class::REL_TYPE}' and @Target='#{slide.relative_path_from(tags)}']")
25
+ end
26
+
27
+ it "should write part to XML" do
28
+ nodes.should have(1).item
29
+ end
30
+
31
+ it "should not write duplicate parts to XML" do
32
+ slide.rels.append tags
33
+ nodes.should have(1).item
34
+ end
35
+ end
36
+
37
+ describe "#id" do
38
+ it "should get id" do
39
+ # I just pulled this value out of the template.
40
+ subject.presentation.rels.id(subject.slides.first).should == 'rId2'
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ballmer::Document do
4
+ subject { read_presentation('presentation3.pptx') }
5
+
6
+ context "rels" do
7
+ it "should resolve"
8
+ end
9
+
10
+ context "content_types"
11
+ context "zip"
12
+ end
@@ -0,0 +1,19 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Ballmer::Presentation::NotesParser do
6
+ # Fixture contains a complex note with several different formats.
7
+ subject { read_presentation('notes.pptx').slides.first.notes }
8
+
9
+ it "should have notes" do
10
+ subject.body.should == %[Some crazy notes are here. Some words are in bold, others are italic.
11
+
12
+ Funny thing about notes. When there’s a mispullin’ it gets underlined and tokenized.]
13
+ end
14
+
15
+ it "should edit notes" do
16
+ subject.body = "Hi\bpig"
17
+ subject.body.should == "Hi\bpig"
18
+ end
19
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ballmer::Presentation::Tags do
4
+ let(:presentation) { read_presentation('presentation1.pptx') }
5
+ subject { Ballmer::Presentation::Tags.build(presentation, 'ppt/tags/tags1.xml') }
6
+
7
+ it "should set key" do
8
+ subject['foo'] = 'bar'
9
+ subject.xml.at_xpath("//p:tag[@name='foo']")['val'].should == 'bar'
10
+ end
11
+
12
+ it "should read key" do
13
+ subject['foo'] = 'bar'
14
+ subject['foo'].should == 'bar'
15
+ end
16
+
17
+ it "should not write duplicate keys" do
18
+ subject['foo'] = 'bar'
19
+ subject['foo'] = 'bar'
20
+ subject.xml.xpath("//p:tag[@name='foo']").should have(1).item
21
+ end
22
+ end
@@ -0,0 +1,118 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ballmer::Presentation do
4
+ subject { read_presentation('presentation3.pptx') }
5
+
6
+ context "presentation" do
7
+ it "should have slides" do
8
+ subject.slides.should have(3).items
9
+ end
10
+ end
11
+
12
+ context "slides" do
13
+ let(:original) { subject.slides.to_a.first }
14
+ let(:copy) { subject.slides.push original }
15
+
16
+ # Explicitly call copy before each test in case
17
+ # we don't call "copy" from a spec below since
18
+ # we don't make assertions on "copy" per test.
19
+ before(:each){ copy }
20
+
21
+ context "#push" do
22
+ it "should copy slide" do
23
+ original.notes.body.should == copy.notes.body
24
+ end
25
+
26
+ context "slide.xml" do
27
+ let(:slide) { copy }
28
+
29
+ it "should append" do
30
+ slide.path.to_s.should == '/ppt/slides/slide4.xml'
31
+ end
32
+
33
+ context "rels" do
34
+ it "should append ./_rels/slide.xml" do
35
+ slide.rels.path.to_s.should == '/ppt/slides/_rels/slide4.xml.rels'
36
+ end
37
+
38
+ it "should reference notesSlides.xml" do
39
+ rels = slide.rels.targets(Ballmer::Presentation::Notes::REL_TYPE)
40
+ rels.should have(1).item
41
+ rels.first.to_s.should == '../notesSlides/notesSlide4.xml'
42
+ end
43
+ end
44
+ end
45
+
46
+ context "notesSlides.xml" do
47
+ let(:notes) { copy.notes }
48
+
49
+ it "should append" do
50
+ notes.path.to_s.should == '/ppt/notesSlides/notesSlide4.xml'
51
+ end
52
+
53
+ context "rels" do
54
+ it "should append ./_rels/notesSlides.xml" do
55
+ notes.rels.path.to_s.should == '/ppt/notesSlides/_rels/notesSlide4.xml.rels'
56
+ end
57
+
58
+ it "should reference slides.xml" do
59
+ rels = notes.rels.targets(Ballmer::Presentation::Slide::REL_TYPE)
60
+ rels.should have(1).item
61
+ rels.first.to_s.should == '../slides/slide4.xml'
62
+ end
63
+ end
64
+ end
65
+
66
+ context "[Content-Type].xml" do
67
+ it "should add slide Override" do
68
+ subject.content_types[Ballmer::Presentation::Slide::CONTENT_TYPE].should have(4).items
69
+ end
70
+ end
71
+
72
+ context "presentation.xml" do
73
+ it "should add slide"
74
+ it "should add slideId"
75
+
76
+ context "_rels" do
77
+ it "should add slide"
78
+ it "should add "
79
+ end
80
+ end
81
+ end
82
+
83
+ context "#delete" do
84
+ before(:each) do
85
+ subject.slides.delete subject.slides.first
86
+ end
87
+
88
+ it "should remove slide" do
89
+ subject.should have(3).slides
90
+ end
91
+
92
+ context "[Content-Type].xml" do
93
+ it "should remove slide"
94
+ end
95
+
96
+ context "presentation.xml" do
97
+ it "should remove slide"
98
+ context "_rels" do
99
+ it "should remove slide"
100
+ end
101
+ end
102
+
103
+ context "slide.xml" do
104
+ it "should delete"
105
+ context "rels" do
106
+ it "should delete"
107
+ end
108
+ end
109
+
110
+ context "noteSlide.xml" do
111
+ it "should delete"
112
+ context "rels" do
113
+ it "should delete"
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,38 @@
1
+ require "ballmer"
2
+
3
+ # Helpers
4
+ module Ballmer
5
+ module Helpers
6
+ # Open a presentation, k?
7
+ def read_presentation(file)
8
+ Presentation.read(read(file))
9
+ end
10
+
11
+ private
12
+ # Read the bytes of a document.
13
+ def read(file)
14
+ File.read(File.join(File.dirname(__FILE__), 'fixtures', file))
15
+ end
16
+ end
17
+ end
18
+
19
+ # This file was generated by the `rspec --init` command. Conventionally, all
20
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
21
+ # Require this file using `require "spec_helper"` to ensure that it is only
22
+ # loaded once.
23
+ #
24
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
25
+ RSpec.configure do |config|
26
+ config.treat_symbols_as_metadata_keys_with_true_values = true
27
+ config.run_all_when_everything_filtered = true
28
+ config.filter_run :focus
29
+
30
+ # Run specs in random order to surface order dependencies. If you find an
31
+ # order dependency and want to debug it, you can fix the order by providing
32
+ # the seed, which is printed after each run.
33
+ # --seed 1234
34
+ config.order = 'random'
35
+
36
+ # Include the helpers module
37
+ config.include Ballmer::Helpers
38
+ end