org_mode 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,55 @@
1
+ require 'org_mode/commands/agenda'
2
+ require 'tempfile'
3
+ require 'core_ext/string'
4
+ require 'support/capture_stdout'
5
+ require 'support/write_into_tempfile'
6
+ require 'timecop'
7
+
8
+ # Helper to execute agenda and compare the output
9
+ def execute_and_compare_stdout_with args, options, expected_output
10
+ output = capture_stdout do
11
+ @org_mode_commands_agenda.execute(args, options)
12
+ end
13
+ output.should == expected_output
14
+ end
15
+
16
+ describe OrgMode::Commands::Agenda do
17
+ before do
18
+ Timecop.freeze('2012-01-01 15:00')
19
+
20
+ @org_mode_commands_agenda = OrgMode::Commands::Agenda.new
21
+ end
22
+
23
+ context '#execute' do
24
+ context 'when loaded with one file' do
25
+ it 'extracts and displays scheduled tasks correctly' do
26
+ org_file = write_into_tempfile <<-eos.strip_indent(10)
27
+ * TODO Scheduled task <1-1-2012 Wed 15:15>
28
+ eos
29
+ execute_and_compare_stdout_with [org_file.path], stub, <<-eos.strip_indent(10)
30
+ Agenda ()
31
+ 2012-01-01
32
+ TODO Scheduled task
33
+ eos
34
+ end
35
+ end
36
+ context 'when loaded with two files' do
37
+ it 'displays both in expected format and sorted correctly' do
38
+ org_file = write_into_tempfile <<-eos.strip_indent(10)
39
+ * TODO Scheduled task on the 5th <5-1-2012 Wed 15:15>
40
+ eos
41
+ org_file2 = write_into_tempfile <<-eos.strip_indent(10)
42
+ * TODO Scheduled task on the 1th <1-1-2012 Wed 15:15>
43
+ eos
44
+ execute_and_compare_stdout_with [org_file, org_file2].map(&:path), stub,
45
+ <<-eos.strip_indent(10)
46
+ Agenda ()
47
+ 2012-01-01
48
+ TODO Scheduled task on the 1th
49
+ 2012-01-05
50
+ TODO Scheduled task on the 5th
51
+ eos
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,323 @@
1
+ require 'org_mode'
2
+ require 'org_mode/parser'
3
+
4
+ # Parser
5
+ # ------
6
+ #
7
+ # Two way parser:
8
+ # First pass
9
+ # Find file settings
10
+ # Find all nodes
11
+ # Title and content tree structure
12
+ # Second pass
13
+ # Parse all nodes
14
+ # (using information from file, ak TODO keywords)
15
+ # Parse title
16
+ # Parse content
17
+ #
18
+ # Node
19
+ # Title
20
+ # TodoStateKeyword
21
+ # Date
22
+ # Content
23
+ # CodeBlockParser
24
+ # PlainTextParser
25
+ #
26
+ # Nodes
27
+ # SettingsNode
28
+ # settings_hash
29
+ # SettingsAttachment
30
+ # PlainTextNode
31
+ # content
32
+ # CodeBlockNode
33
+ # language
34
+ # content
35
+
36
+
37
+ # Private: Loads org example
38
+ #
39
+ # name - takes part of filename
40
+ #
41
+ # Returns the contents of the file
42
+ def load_org_example name
43
+ File.open("spec/data/org-file-#{name}.org").read
44
+ end
45
+
46
+ describe OrgMode::FileParser do
47
+ context ".parse_into_tokens" do
48
+ it "should pars only one" do
49
+ org_data = <<-org.gsub(/^\s{8}/,'')
50
+ * First
51
+ org
52
+ b,n,e = OrgMode::FileParser.parse_into_tokens(org_data)
53
+ b.should be_empty
54
+ n.should == [["* First", ""]]
55
+ e.should be_empty
56
+ end
57
+ it "should pars only one without enter" do
58
+ org_data = "* First"
59
+ b,n,e = OrgMode::FileParser.parse_into_tokens(org_data)
60
+ b.should be_empty
61
+ n.should == [["* First", ""]]
62
+ e.should be_empty
63
+ end
64
+ it "should divide data up correctly" do
65
+ org_data = <<-org.gsub(/^\s{8}/,'')
66
+ * First
67
+ ** Second
68
+ org
69
+ b,n,e = OrgMode::FileParser.parse_into_tokens(org_data)
70
+ b.should be_empty
71
+ n.should == [["* First", ""], ["** Second", ""]]
72
+ e.should be_empty
73
+ end
74
+ it "should handle nodes with content" do
75
+ org_data = <<-org.gsub(/^\s{8}/,'')
76
+ * First
77
+ Content for first node
78
+ ** Second
79
+ Content for nested node
80
+ org
81
+ b,n,e = OrgMode::FileParser.parse_into_tokens(org_data)
82
+ b.should be_empty
83
+ n.should == [["* First", " Content for first node"], ["** Second", " Content for nested node"]]
84
+ e.should be_empty
85
+ end
86
+ it "should handle content with no nodes" do
87
+ org_data = <<-org.gsub(/^\s{8}/,'')
88
+ Just some textfile without any nodes
89
+ org
90
+ b,n,e = OrgMode::FileParser.parse_into_tokens(org_data)
91
+ b.should == 'Just some textfile without any nodes'
92
+ n.should == []
93
+ e.should be_empty
94
+ end
95
+ it "should take an empty string" do
96
+ org_data = <<-org.gsub(/^\s{8}/,'')
97
+ org
98
+ b,n,e = OrgMode::FileParser.parse_into_tokens(org_data)
99
+ b.should be_empty
100
+ n.should == []
101
+ e.should be_empty
102
+ end
103
+ end
104
+
105
+ context ".parse" do
106
+ let(:org_data) { load_org_example '01-simple-node-structure' }
107
+ it "should just parse the file" do
108
+ parsed = OrgMode::FileParser.parse(org_data)
109
+ end
110
+ context 'with a parsed org_file' do
111
+ let(:org_file) { OrgMode::FileParser.parse(org_data) }
112
+
113
+ it "should return an OrgMode::File" do
114
+ org_file.should be_an_instance_of( OrgMode::File )
115
+ end
116
+
117
+ it "parses the tree correctly" do
118
+ org_file.nodes.length.should == 12
119
+ end
120
+
121
+ it "has an empty beginning" do
122
+ org_file.header.should be_empty
123
+ end
124
+ it "has an empty ending" do
125
+ org_file.footer.should be_empty
126
+ end
127
+ context 'parent_child relations' do
128
+ it "root node has no parent" do
129
+ org_file.nodes[0].parent.should be_nil
130
+ end
131
+ it "root node has 2 children" do
132
+ org_file.nodes[0].children.length.should == 2
133
+ org_file.nodes[0].children[0].title.should == 'FirstChildMain'
134
+ org_file.nodes[0].children[1].title.should == 'SecondChildMain'
135
+ end
136
+ it "ThirdMain should be in business" do
137
+ org_file.nodes[8].children[0].children[0].children[0].title.should ==
138
+ 'FirstChildFirstChildFirstChildMain'
139
+ end
140
+ it "destilled the correct root nodes" do
141
+ org_file.root_nodes.map(&:title).should == %w[Main SecondMain ThirdMain]
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
147
+
148
+ describe OrgMode::NodeParser do
149
+ context ".parse" do
150
+ context "title" do
151
+ context "standard node title" do
152
+ let(:node) {OrgMode::NodeParser.parse('** Standard node title', nil)}
153
+ it "parses the title correctly" do
154
+ node.title.should == 'Standard node title'
155
+ end
156
+ it "determines the correct stars" do
157
+ node.stars.should == 2
158
+ end
159
+ it "determines the correct indent" do
160
+ node.indent.should == 3
161
+ end
162
+ end
163
+ context "level deeper node title" do
164
+ let(:node) {OrgMode::NodeParser.parse('*** Standard node title one level deeper', nil)}
165
+ it "parses the title correctly" do
166
+ node.title.should == 'Standard node title one level deeper'
167
+ end
168
+ it "determines the correct stars" do
169
+ node.stars.should == 3
170
+ end
171
+ it "determines the correct indent" do
172
+ node.indent.should == 4
173
+ end
174
+ end
175
+ context "node title with date" do
176
+ let(:node) {OrgMode::NodeParser.parse('** Date node title <2012-02-02 Wed>', nil) }
177
+ it "parses the date from the title correcty" do
178
+ node.date.strftime('%Y-%m-%d').should == '2012-02-02'
179
+ end
180
+ end
181
+ context "node title with date time" do
182
+ let(:node) {OrgMode::NodeParser.parse('** Date node title <2012-02-03 Wed 15:15>', nil)}
183
+ it "parses the date-time from the title correcty" do
184
+ node.date.strftime('%Y-%m-%d %H:%M').should == '2012-02-03 15:15'
185
+ end
186
+ end
187
+ context "node title with date-range" do
188
+ let(:node) {OrgMode::NodeParser.parse('** Date node title <2012-02-03 Wed 15:15-16:15>', nil)}
189
+ it "parses the date from the title correcty" do
190
+ node.date.strftime('%Y-%m-%d %H:%M').should == '2012-02-03 15:15'
191
+ end
192
+ it "parses the start_tiem correctly" do
193
+ node.date_start_time.strftime('%Y-%m-%d %H:%M').should == '2012-02-03 15:15'
194
+ end
195
+ it "parses the end_time correctly" do
196
+ node.date_end_time.strftime('%Y-%m-%d %H:%M').should == '2012-02-03 16:15'
197
+ end
198
+ end
199
+ context "parses TODO states correctly" do
200
+ let(:node) {OrgMode::NodeParser.parse('** TODO Date node title', nil)}
201
+ it "parses the TODO keyword correctly" do
202
+ node.todo_state.should == 'TODO'
203
+ end
204
+ context "parses DONE state correctly" do
205
+ let(:node) {OrgMode::NodeParser.parse('** DONE Date node title', nil)}
206
+ it "parses the TODO keyword correctly" do
207
+ node.todo_state.should == 'DONE'
208
+ end
209
+ end
210
+ end
211
+ end
212
+ context "content indentation" do
213
+ context "correctly indented" do
214
+ before do
215
+ org_title, *org_content = <<-eos.gsub(/^\s{10}/,'').lines.to_a
216
+ *** Title
217
+ Content belonging
218
+ at a certain indent
219
+ should be parsed correctly
220
+ eos
221
+ @node = OrgMode::NodeParser.parse(org_title, org_content.join)
222
+ end
223
+ it "parses content and removes indent" do
224
+ @node.content.should == <<-eos.gsub(/^\s{10}/,'')
225
+ Content belonging
226
+ at a certain indent
227
+ should be parsed correctly
228
+ eos
229
+ end
230
+ end
231
+ context "too far" do
232
+ before do
233
+ org_title, *org_content = <<-eos.gsub(/^\s{10}/,'').lines.to_a
234
+ *** Title
235
+ Content belonging
236
+ at a certain indent
237
+ should be parsed correctly
238
+ eos
239
+ @node = OrgMode::NodeParser.parse(org_title, org_content.join)
240
+ end
241
+ it "parses content and removes indent" do
242
+ @node.content.should == <<-eos.gsub(/^\s{10}/,'')
243
+ Content belonging
244
+ at a certain indent
245
+ should be parsed correctly
246
+ eos
247
+ end
248
+ end
249
+ context "indented in content block" do
250
+ before do
251
+ org_title, *org_content = <<-eos.gsub(/^\s{10}/,'').lines.to_a
252
+ *** Title
253
+ Content belonging
254
+ at a certain indent
255
+ should be parsed correctly
256
+ eos
257
+ @node = OrgMode::NodeParser.parse(org_title, org_content.join)
258
+ end
259
+ it "parses content and removes indent" do
260
+ @node.content.should == <<-eos.gsub(/^\s{10}/,'')
261
+ Content belonging
262
+ at a certain indent
263
+ should be parsed correctly
264
+ eos
265
+ end
266
+ end
267
+ context "one row outdented" do
268
+ before do
269
+ org_title, *org_content = <<-eos.gsub(/^\s{10}/,'').lines.to_a
270
+ *** Title
271
+ Content belonging
272
+ at a certain indent
273
+ should be parsed correctly
274
+ eos
275
+ @node = OrgMode::NodeParser.parse(org_title, org_content.join)
276
+ end
277
+ it "parses content and removes indent" do
278
+ @node.content.should == <<-eos.gsub(/^\s{10}/,'')
279
+ Content belonging
280
+ at a certain indent
281
+ should be parsed correctly
282
+ eos
283
+ end
284
+ end
285
+ end
286
+ context "content whitespace" do
287
+ it "removes whitespace at beginning" do
288
+ org_title, *org_content = <<-eos.gsub(/^\s{8}/,'').lines.to_a
289
+ *** Title
290
+
291
+
292
+
293
+ Content belonging
294
+ at a certain indent
295
+ should be parsed correctly
296
+ eos
297
+ @node = OrgMode::NodeParser.parse(org_title, org_content.join)
298
+ @node.content.should == <<-eos.gsub(/^\s{8}/,'')
299
+ Content belonging
300
+ at a certain indent
301
+ should be parsed correctly
302
+ eos
303
+ end
304
+ it "removes whitespace at ending" do
305
+ org_title, *org_content = <<-eos.gsub(/^\s{8}/,'').lines.to_a
306
+ *** Title
307
+ Content belonging
308
+ at a certain indent
309
+ should be parsed correctly
310
+
311
+
312
+
313
+ eos
314
+ @node = OrgMode::NodeParser.parse(org_title, org_content.join)
315
+ @node.content.should == <<-eos.gsub(/^\s{8}/,'')
316
+ Content belonging
317
+ at a certain indent
318
+ should be parsed correctly
319
+ eos
320
+ end
321
+ end
322
+ end
323
+ end
@@ -0,0 +1,74 @@
1
+ require 'org_mode/reporters/agenda'
2
+ require 'support/blueprints'
3
+ require 'support/include_hash'
4
+ require 'timecop'
5
+
6
+ describe OrgMode::Reporters::Agenda do
7
+ before do
8
+ Timecop.freeze('2012-02-02')
9
+ end
10
+ let(:file_collection) do
11
+ nodes = []
12
+ files = []
13
+ nodes << OrgMode::Node.make(:stars => 1, :date=>Date.parse('2012-02-01'))
14
+ nodes << OrgMode::Node.make(:stars => 2, :date=>Date.parse('2012-02-05'))
15
+ files << OrgMode::File.make(:nodes => nodes)
16
+ nodes = []
17
+ nodes << OrgMode::Node.make(:stars => 1,
18
+ :todo_state => 'DONE',
19
+ :date => Date.parse('2012-06-01'))
20
+ nodes << OrgMode::Node.make(:stars => 2, :date=>Date.parse('2012-02-03'))
21
+ nodes << OrgMode::Node.make(:stars => 3, :date=>DateTime.parse('2012-02-01 15:00'))
22
+ files << OrgMode::File.make(:nodes => nodes)
23
+ OrgMode::FileCollection.make(:files => files)
24
+ end
25
+ let(:reporter) { OrgMode::Reporters::Agenda.new(file_collection) }
26
+
27
+ context '#open_nodes_grouped_by_day' do
28
+
29
+ let (:reported) { reporter.open_nodes_grouped_by_day }
30
+
31
+ it "extracts the correct dates and in the correct order" do
32
+ dates = reported.map {|e| e[:date] }
33
+ dates.should == ["2012-02-01", "2012-02-03", "2012-02-05"]
34
+ end
35
+ it "should group the nodecounts correctly" do
36
+ nodecount_per_date = reported.map { |e| [e[:date], e[:nodes].length] }
37
+ nodecount_per_date.should == [["2012-02-01", 2], ["2012-02-03", 1], ["2012-02-05", 1]]
38
+ end
39
+ it "should result in the following subhashes" do
40
+ reported.should ==
41
+ [{:date=>"2012-02-01",
42
+ :nodes=>
43
+ [{:title=>"org-node",
44
+ :content=>"org-node content",
45
+ :todo_state=>"TODO",
46
+ :date=>"2012-02-01 00:00",
47
+ :stars=>1},
48
+ {:title=>"org-node",
49
+ :content=>"org-node content",
50
+ :todo_state=>"TODO",
51
+ :date=>"2012-02-01 15:00",
52
+ :stars=>3}]},
53
+ {:date=>"2012-02-03",
54
+ :nodes=>
55
+ [{:title=>"org-node",
56
+ :content=>"org-node content",
57
+ :todo_state=>"TODO",
58
+ :date=>"2012-02-03 00:00",
59
+ :stars=>2}]},
60
+ {:date=>"2012-02-05",
61
+ :nodes=>
62
+ [{:title=>"org-node",
63
+ :content=>"org-node content",
64
+ :todo_state=>"TODO",
65
+ :date=>"2012-02-05 00:00",
66
+ :stars=>2}]}]
67
+ end
68
+ it "it ignores the DONE task" do
69
+ dates = reported.map {|e| e[:date] }
70
+ dates.should_not include("2012-06-01")
71
+ end
72
+ end
73
+
74
+ end
@@ -0,0 +1,52 @@
1
+ require 'org_mode'
2
+ require 'support/blueprints'
3
+
4
+ describe OrgMode::File do
5
+ let(:file) do
6
+ nodes = []
7
+ files = []
8
+ nodes << OrgMode::Node.make(:stars => 1)
9
+ nodes << OrgMode::Node.make(:stars => 2, :date=>Time.now)
10
+ nodes << OrgMode::Node.make(:stars => 3)
11
+ nodes << OrgMode::Node.make(:stars => 1)
12
+ OrgMode::File.make(:nodes => nodes)
13
+ end
14
+
15
+ it "accumulates correctly" do
16
+ file.nodes.length.should == 4
17
+ end
18
+
19
+ it "detects all rootnodes correctly" do
20
+ file.root_nodes.length.should == 2
21
+ end
22
+
23
+ it "detects all scheduled nodes correctly" do
24
+ file.scheduled_nodes.length.should == 1
25
+ end
26
+ end
27
+
28
+ describe OrgMode::FileCollection do
29
+ let(:file_collection) do
30
+ nodes = []
31
+ files = []
32
+ nodes << OrgMode::Node.make(:stars => 1)
33
+ nodes << OrgMode::Node.make(:stars => 2)
34
+ files << OrgMode::File.make(:nodes => nodes)
35
+ nodes = []
36
+ nodes << OrgMode::Node.make(:stars => 1)
37
+ nodes << OrgMode::Node.make(:stars => 2, :date=>Time.now)
38
+ nodes << OrgMode::Node.make(:stars => 3)
39
+ files << OrgMode::File.make(:nodes => nodes)
40
+ OrgMode::FileCollection.make(:files => files)
41
+ end
42
+ it "accumulates correctly" do
43
+ file_collection.nodes.length.should == 5
44
+ end
45
+
46
+ it "detects all rootnodes correctly" do
47
+ file_collection.root_nodes.length.should == 2
48
+ end
49
+ it "detects all scheduled nodes correctly" do
50
+ file_collection.scheduled_nodes.length.should == 1
51
+ end
52
+ end
@@ -0,0 +1,27 @@
1
+ require 'org_mode'
2
+
3
+ class OrgMode::Node
4
+ def self.make(attrs={})
5
+ self.new.tap do |n|
6
+ n.title = attrs[:title] || "org-node"
7
+ n.stars = attrs[:stars] || rand(4)
8
+ n.content = attrs[:content] || "org-node content"
9
+ n.date = attrs[:date]
10
+ n.todo_state = attrs[:todo_state] || 'TODO'
11
+ end
12
+ end
13
+ end
14
+
15
+ class OrgMode::File
16
+ def self.make(attrs={})
17
+ nodes = attrs[:nodes] || Array.new(2) { OrgMode::Node.make }
18
+ self.new("aheader", nodes , "afooter")
19
+ end
20
+ end
21
+
22
+ class OrgMode::FileCollection
23
+ def self.make(attrs={})
24
+ files = attrs[:files] || Array.new(2) { OrgMode::File.make }
25
+ self.new( files )
26
+ end
27
+ end
@@ -0,0 +1,12 @@
1
+ require 'stringio'
2
+
3
+ def capture_stdout
4
+ out = StringIO.new
5
+ $stdout = out
6
+ yield
7
+ out.rewind
8
+ return out.read
9
+ ensure
10
+ $stdout = STDOUT
11
+ end
12
+
@@ -0,0 +1,7 @@
1
+ RSpec::Matchers.define :include_hash do |expected|
2
+
3
+ match do |actual|
4
+ actual.present? && actual.slice(*expected.keys) == expected
5
+ end
6
+
7
+ end
@@ -0,0 +1,6 @@
1
+ def write_into_tempfile string
2
+ tmpfile = Tempfile.new('org_mode_file')
3
+ tmpfile.write(string)
4
+ tmpfile.flush
5
+ tmpfile
6
+ end