org_mode 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -0
- data/Gemfile +26 -0
- data/README.mkd +30 -0
- data/Rakefile +1 -0
- data/bin/org-mode +32 -0
- data/features/cmdline_agenda.feature +34 -0
- data/features/cmdline_handles_errors_cracefully.feature +10 -0
- data/features/cmdline_noparams.feature +11 -0
- data/features/cmdline_todos.feature +5 -0
- data/features/steps.rb +35 -0
- data/features/support/env.rb +0 -0
- data/features/support/org_mode_script.rb +53 -0
- data/lib/core_ext/string.rb +5 -0
- data/lib/org_mode/commands/agenda.rb +39 -0
- data/lib/org_mode/commands/update.rb +4 -0
- data/lib/org_mode/commands.rb +7 -0
- data/lib/org_mode/loader.rb +17 -0
- data/lib/org_mode/parser.rb +137 -0
- data/lib/org_mode/reporters/agenda.rb +44 -0
- data/lib/org_mode/version.rb +3 -0
- data/lib/org_mode.rb +87 -0
- data/org_mode.gemspec +28 -0
- data/spec/data/org-file-01-simple-node-structure.org +12 -0
- data/spec/lib/org_mode/commands/agenda_spec.rb +55 -0
- data/spec/lib/org_mode/parser_spec.rb +323 -0
- data/spec/lib/org_mode/reporters/agenda_spec.rb +74 -0
- data/spec/lib/org_mode_spec.rb +52 -0
- data/spec/support/blueprints.rb +27 -0
- data/spec/support/capture_stdout.rb +12 -0
- data/spec/support/include_hash.rb +7 -0
- data/spec/support/write_into_tempfile.rb +6 -0
- metadata +166 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in org_mode.gemspec
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
gem "commander"
|
7
|
+
gem "facets"
|
8
|
+
gem "mustache"
|
9
|
+
|
10
|
+
group :development do
|
11
|
+
if RUBY_VERSION =~ /^1\.9/
|
12
|
+
gem "ruby-debug19"
|
13
|
+
else
|
14
|
+
gem "ruby-debug"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
group :test do
|
19
|
+
gem "rspec", "~> 2.8.0"
|
20
|
+
gem "cucumber"
|
21
|
+
gem "timecop"
|
22
|
+
gem "guard-cucumber"
|
23
|
+
gem "popen4"
|
24
|
+
end
|
25
|
+
|
26
|
+
|
data/README.mkd
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# Org-Mode file parser and writer
|
2
|
+
|
3
|
+
Usin Vim for a long time, and having used e-macs for a while I got hooked on
|
4
|
+
org-mode. The flexible plain-tekst planning mode.
|
5
|
+
|
6
|
+
Using org-mode was an eye-opener, brilliant idea but it turns out I am more of a VIM
|
7
|
+
person.
|
8
|
+
|
9
|
+
So this ruby-gem is result of this personal org-mode dissonance.
|
10
|
+
|
11
|
+
## Goal
|
12
|
+
|
13
|
+
Making lightweight/fast ruby org-mode executable to parse/reformat/analyse and
|
14
|
+
report on my org-files or the one in my buffer. And following the Unix philosopy
|
15
|
+
making a seperate process of it.
|
16
|
+
|
17
|
+
## What I want
|
18
|
+
|
19
|
+
* flexible parsers
|
20
|
+
* flexible writers
|
21
|
+
* database storage
|
22
|
+
* org-mode website upload
|
23
|
+
* well tested module
|
24
|
+
* integration with vim
|
25
|
+
* flexible reporting
|
26
|
+
* remember mode
|
27
|
+
|
28
|
+
## Code
|
29
|
+
|
30
|
+
See: `lib/**/*.rb` for source and `spec/**/*.rb` for specs. Binaries not available yet.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/org-mode
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
#!/usr/bin/env ruby -Ilib
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'commander/import'
|
5
|
+
require 'org_mode/version'
|
6
|
+
|
7
|
+
require 'org_mode/commands'
|
8
|
+
|
9
|
+
program :version, OrgMode::VERSION
|
10
|
+
program :description, 'Formats and extracts information out of org-mode files'
|
11
|
+
program :help_formatter, :compact
|
12
|
+
|
13
|
+
command :agenda do |c|
|
14
|
+
c.syntax = 'org-mode agenda [options]'
|
15
|
+
c.summary = ''
|
16
|
+
c.description = ''
|
17
|
+
c.example 'description', 'command example'
|
18
|
+
c.option '--some-switch', 'Some switch that does something'
|
19
|
+
c.when_called OrgMode::Commands::Agenda, :execute
|
20
|
+
end
|
21
|
+
|
22
|
+
command :update do |c|
|
23
|
+
c.syntax = 'Orgy update [options]'
|
24
|
+
c.summary = ''
|
25
|
+
c.description = ''
|
26
|
+
c.example 'description', 'command example'
|
27
|
+
c.option '--some-switch', 'Some switch that does something'
|
28
|
+
c.action do |args, options|
|
29
|
+
# Do something or c.when_called Orgy::Commands::Update
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
@@ -0,0 +1,34 @@
|
|
1
|
+
Feature: agenda
|
2
|
+
org-mode tools should extract meaningfull information
|
3
|
+
out of the parsed org-mode files and present
|
4
|
+
it in a nicely correct format.
|
5
|
+
|
6
|
+
Scenario: should present meaningfull agenda information
|
7
|
+
Given date is "1-1-2012 15:00"
|
8
|
+
And we have an org-file with the following content
|
9
|
+
"""
|
10
|
+
* TODO Scheduled task <1-1-2012 Wed 15:15>
|
11
|
+
"""
|
12
|
+
When the script is executed on the org-file
|
13
|
+
When the script is called with "agenda"
|
14
|
+
Then the output should be
|
15
|
+
"""
|
16
|
+
Agenda ()
|
17
|
+
2012-01-01
|
18
|
+
TODO Scheduled task
|
19
|
+
"""
|
20
|
+
|
21
|
+
# @focus
|
22
|
+
# Scenario: should limit restults in a day view
|
23
|
+
# Given date is "1-1-2012 15:00"
|
24
|
+
# And we have an org-file with the following content
|
25
|
+
# """
|
26
|
+
# * TODO Todays task <1-1-2012 Wed 15:15>
|
27
|
+
# * TODO Tommorrows task <2-1-2012 Wed 15:15>
|
28
|
+
# """
|
29
|
+
# When the script is called with "agenda today"
|
30
|
+
# Then the output should be
|
31
|
+
# """
|
32
|
+
# Todays activities:
|
33
|
+
# TODO Todays task
|
34
|
+
# """
|
@@ -0,0 +1,10 @@
|
|
1
|
+
Feature: Handles errors gracefully
|
2
|
+
Incomplete information or missing non-existing
|
3
|
+
files should be reported.
|
4
|
+
|
5
|
+
Scenario: non-existing file
|
6
|
+
When the script is called with "agenda /tmp/non-existent.org"
|
7
|
+
Then the output should be
|
8
|
+
"""
|
9
|
+
Encountered a little problem: No such file or directory - /tmp/non-existent.org
|
10
|
+
"""
|
@@ -0,0 +1,11 @@
|
|
1
|
+
Feature: noparams or reformat
|
2
|
+
Whenever the org-mode script is run without params
|
3
|
+
it should just display help
|
4
|
+
|
5
|
+
Scenario: script with no params should display help
|
6
|
+
When the script is called with "" should fail
|
7
|
+
Then the error should be
|
8
|
+
"""
|
9
|
+
invalid command. Use --help for more information
|
10
|
+
"""
|
11
|
+
|
data/features/steps.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'timecop'
|
2
|
+
|
3
|
+
Given /^we have an org\-file with the following content$/ do |string|
|
4
|
+
@org_file = Tempfile.new('org_file')
|
5
|
+
@org_file.write(string)
|
6
|
+
@org_file.flush
|
7
|
+
end
|
8
|
+
|
9
|
+
When /^the script is called with "([^"]*)"( should fail)?$/ do |argv, should_fail|
|
10
|
+
org_mode_args = []
|
11
|
+
org_mode_args << argv if argv
|
12
|
+
org_mode_args << @org_file.path if @org_file
|
13
|
+
|
14
|
+
begin
|
15
|
+
@stdout, = org_mode_script(*org_mode_args)
|
16
|
+
rescue OrgModeScriptError => script_error
|
17
|
+
raise unless should_fail
|
18
|
+
@script_error = script_error
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
Given /^date is "([^"]*)"$/ do |date_string|
|
23
|
+
Timecop.freeze(Date.parse(date_string))
|
24
|
+
end
|
25
|
+
|
26
|
+
When /^the script is executed on the org-file$/ do
|
27
|
+
@stdout, = org_mode_script(:agenda, @org_file.path)
|
28
|
+
end
|
29
|
+
|
30
|
+
Then /^the output should be$/ do |string|
|
31
|
+
@stdout.should == string
|
32
|
+
end
|
33
|
+
Then /^the error should be$/ do |string|
|
34
|
+
@script_error.stderr.chomp.should == string
|
35
|
+
end
|
File without changes
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
require 'ruby-debug'
|
3
|
+
require 'popen4'
|
4
|
+
|
5
|
+
class OrgModeScriptError < StandardError;
|
6
|
+
attr_accessor :stdout, :stderr, :status, :cmd
|
7
|
+
|
8
|
+
def initialize(stdout, stderr, status, cmd)
|
9
|
+
@stdout = stdout
|
10
|
+
@stderr = stderr
|
11
|
+
@status = status
|
12
|
+
@cmd = cmd
|
13
|
+
super("Failed command: #{cmd}")
|
14
|
+
end
|
15
|
+
|
16
|
+
def message
|
17
|
+
<<-eom.gsub(/^\s{4}/, '')
|
18
|
+
Failed command: #{cmd}
|
19
|
+
Stderr:
|
20
|
+
#{@stderr.empty? ? 'None' : @stderr}
|
21
|
+
Stdout:
|
22
|
+
#{@stdout.empty? ? 'None' : @stdout}
|
23
|
+
eom
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
# Private: runs org-mode binary and captures output
|
29
|
+
#
|
30
|
+
# builds command out of parameters and calls capturing
|
31
|
+
# stdout/stderr and the exit status
|
32
|
+
#
|
33
|
+
# Raises an OrgModeScriptError when status is not success
|
34
|
+
# OrgModeScriptError contains all the information on the command
|
35
|
+
#
|
36
|
+
# Returns stdout as String, stderr as String, status as Process::Status
|
37
|
+
def org_mode_script(cmd, *params)
|
38
|
+
|
39
|
+
# build command
|
40
|
+
cmd = %[bin/org-mode #{cmd} #{params * ' '}]
|
41
|
+
|
42
|
+
stdout, stderr = [ nil,nil ]
|
43
|
+
status = POpen4.popen4(cmd) do |pout,perr|
|
44
|
+
stdout = pout.read
|
45
|
+
stderr = perr.read
|
46
|
+
end
|
47
|
+
|
48
|
+
unless status.success?
|
49
|
+
raise OrgModeScriptError.new(stdout, stderr, status, cmd)
|
50
|
+
end
|
51
|
+
|
52
|
+
return [stdout.chomp, stderr, status]
|
53
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'org_mode/parser'
|
2
|
+
require 'org_mode/loader'
|
3
|
+
require 'org_mode/reporters/agenda'
|
4
|
+
|
5
|
+
require 'core_ext/string'
|
6
|
+
require 'mustache'
|
7
|
+
|
8
|
+
module OrgMode::Commands
|
9
|
+
class Agenda
|
10
|
+
# Private: executes the agenda command
|
11
|
+
# parses all the files
|
12
|
+
#
|
13
|
+
# args - list of filenames
|
14
|
+
# options - switches set by the app
|
15
|
+
#
|
16
|
+
# Returns the restult to stdout
|
17
|
+
def execute(args, options)
|
18
|
+
|
19
|
+
file_collection = OrgMode::Loader.load_and_parse_files(*args)
|
20
|
+
agenda_reporter = OrgMode::Reporters::Agenda.new(file_collection)
|
21
|
+
|
22
|
+
tmpl_vars = {}
|
23
|
+
tmpl_vars[:noi_per_date] = agenda_reporter.open_nodes_grouped_by_day
|
24
|
+
|
25
|
+
puts Mustache.render <<-eos.strip_indent(8), tmpl_vars
|
26
|
+
Agenda ()
|
27
|
+
{{#noi_per_date}}
|
28
|
+
{{date}}
|
29
|
+
{{#nodes}}
|
30
|
+
{{todo_state}}{{title}}
|
31
|
+
{{/nodes}}
|
32
|
+
{{/noi_per_date}}
|
33
|
+
eos
|
34
|
+
|
35
|
+
rescue SystemCallError => e
|
36
|
+
puts "Encountered a little problem: #{e}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class OrgMode::Loader
|
2
|
+
# Public: loads and parses multiple files
|
3
|
+
#
|
4
|
+
# Returns OrgMode::FileCollection
|
5
|
+
def self.load_and_parse_files *paths
|
6
|
+
org_mode_files = paths.map {|f| load_and_parse_file(f) }
|
7
|
+
OrgMode::FileCollection.new(org_mode_files)
|
8
|
+
end
|
9
|
+
|
10
|
+
# Public: loads and parse a file
|
11
|
+
#
|
12
|
+
# Returns OrgMode::File
|
13
|
+
def self.load_and_parse_file path
|
14
|
+
f = File.open(path)
|
15
|
+
OrgMode::FileParser.parse(f.read)
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
# Public: Org File format parser
|
2
|
+
#
|
3
|
+
# A simple regexp based parser for orgfiles. Works by simply dividng
|
4
|
+
# the file in beginning, nodes, and ending. After this it will
|
5
|
+
# parse the individual nodes extracting remaining detailed information.
|
6
|
+
#
|
7
|
+
# Parser is decoupled from object model to make it easy to write updated
|
8
|
+
# parsers or use a database to serialize an org-mode file out of.
|
9
|
+
require 'org_mode'
|
10
|
+
require 'date'
|
11
|
+
|
12
|
+
module OrgMode
|
13
|
+
class FileParser
|
14
|
+
RxNodeTitle = %r{
|
15
|
+
^ # beginning of line
|
16
|
+
(
|
17
|
+
\*+ # multiple stars
|
18
|
+
\s+ # one or more whitespace
|
19
|
+
.* # anything
|
20
|
+
)
|
21
|
+
$ # untill _end of line
|
22
|
+
}xs
|
23
|
+
|
24
|
+
class << self
|
25
|
+
|
26
|
+
# Public: parses buffer into nodes and
|
27
|
+
# collects there in a OrgMode::File object
|
28
|
+
#
|
29
|
+
# Returns OrgMode::File object containing all
|
30
|
+
# information of the file.
|
31
|
+
def parse(buffer)
|
32
|
+
b, nodes, e = parse_buffer(buffer)
|
33
|
+
|
34
|
+
parent_stack = []
|
35
|
+
nodes.map! do |title, content|
|
36
|
+
node = NodeParser.parse(title,content)
|
37
|
+
node.parent = parent_stack[node.stars - 1]
|
38
|
+
if node.parent
|
39
|
+
node.parent.children << node
|
40
|
+
end
|
41
|
+
parent_stack[node.stars] = node
|
42
|
+
end
|
43
|
+
return File.new(b,nodes,e)
|
44
|
+
end
|
45
|
+
|
46
|
+
def parse_buffer(buffer)
|
47
|
+
beginning_of_file, nodes, ending_of_file =
|
48
|
+
parse_into_tokens(buffer)
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
# Private: splits buffer into different parts
|
53
|
+
# First part is beginning of file
|
54
|
+
# Second part are the nodetitles in combination
|
55
|
+
# with the content
|
56
|
+
# Third part is the ending of the file
|
57
|
+
#
|
58
|
+
# buffer - org mode data
|
59
|
+
#
|
60
|
+
# Returns beginning_of_file, nodes, and ending
|
61
|
+
# if beginning is not present and empy string is
|
62
|
+
# returned. This function will never return nil
|
63
|
+
#
|
64
|
+
def parse_into_tokens buffer
|
65
|
+
tokens = buffer.split(RxNodeTitle).map(&:rstrip)
|
66
|
+
beginning_of_file = tokens.shift || ''
|
67
|
+
|
68
|
+
nodes = []
|
69
|
+
while !tokens.empty?
|
70
|
+
nodes << Array.new(2) { tokens.shift || '' }
|
71
|
+
end
|
72
|
+
|
73
|
+
nodes.map! { |t,c| [t,c[1..-1] || ''] }
|
74
|
+
|
75
|
+
[ beginning_of_file, nodes, "" ]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
class NodeParser
|
81
|
+
|
82
|
+
class << self
|
83
|
+
|
84
|
+
# Public: Parses a node in the org-mode file-format
|
85
|
+
#
|
86
|
+
# title - a org-mode title, can contain date, todo statusses, tags
|
87
|
+
# everything specified in the org-mod file format
|
88
|
+
# content - the content block, which can also contain org-mode format
|
89
|
+
#
|
90
|
+
# Return a OrgMode::Node containing all parsable information
|
91
|
+
def parse(title,content)
|
92
|
+
node = OrgMode::Node.new
|
93
|
+
parse_title(node, title)
|
94
|
+
parse_extract_dates(node)
|
95
|
+
parse_content(node, content)
|
96
|
+
node
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def parse_title(node,title)
|
102
|
+
matches = title.match( /^(\*+)\s+(TODO|DONE)?(.*)$/ )
|
103
|
+
node.stars = matches[1].length
|
104
|
+
node.todo_state = matches[2]
|
105
|
+
node.title = matches[3]
|
106
|
+
#node.indent = node.stars + 1
|
107
|
+
end
|
108
|
+
|
109
|
+
RxDateRegexp = /<(\d+-\d+-\d+)(?:\s(?:\w{3})?(?:\s(\d+:\d+))?)(?:-(\d+:\d+))?>/
|
110
|
+
def parse_extract_dates(node)
|
111
|
+
_, extracted_date, start_time, end_time = node.title.match(RxDateRegexp).to_a
|
112
|
+
node.title = node.title.gsub(RxDateRegexp, '')
|
113
|
+
|
114
|
+
if extracted_date
|
115
|
+
node.date_start_time = DateTime.parse("#{extracted_date} #{start_time}")
|
116
|
+
node.date_end_time = DateTime.parse("#{extracted_date} #{end_time}")
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
RxEmptyLine = /^\s*$/
|
121
|
+
def parse_content(node,content)
|
122
|
+
return unless content
|
123
|
+
|
124
|
+
minimum_indent = ( content.lines.map {|l| l =~ /\S/ }.reject(&:nil?) + [node.indent] ).min
|
125
|
+
content.gsub!(/^\s{#{minimum_indent}}/m, '')
|
126
|
+
|
127
|
+
# remove empty lines at beginning and ending
|
128
|
+
node.content = content.lines.
|
129
|
+
drop_while {|e| e =~ RxEmptyLine}.
|
130
|
+
reverse.
|
131
|
+
drop_while {|e| e =~ RxEmptyLine}.
|
132
|
+
reverse.
|
133
|
+
join
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'facets/to_hash'
|
2
|
+
|
3
|
+
module OrgMode
|
4
|
+
module Reporters
|
5
|
+
class Agenda
|
6
|
+
attr_accessor :file_collection
|
7
|
+
def initialize(file_collection)
|
8
|
+
@file_collection = file_collection
|
9
|
+
end
|
10
|
+
|
11
|
+
# Public: returns open nodes grouped by day
|
12
|
+
# ordered by date
|
13
|
+
#
|
14
|
+
# Returns an Array of Hash-es like
|
15
|
+
# [{:date => '%Y-%m-%d', :nodes => [{ .. }]}]
|
16
|
+
# ready to be used by fe Mustache
|
17
|
+
def open_nodes_grouped_by_day
|
18
|
+
# Get all nodes from all files
|
19
|
+
# extract scheduled items which are not done
|
20
|
+
# discard all DONE items
|
21
|
+
nodes_of_interest = file_collection.scheduled_nodes.select(&:open?)
|
22
|
+
|
23
|
+
# group them by date
|
24
|
+
noi_per_day = nodes_of_interest.group_by { |noi| noi.date.strftime('%Y-%m-%d') }
|
25
|
+
|
26
|
+
# build a nice orderd struct
|
27
|
+
noi_per_day.keys.sort.map do |date|
|
28
|
+
{ :date => date, :nodes => noi_per_day[date].map { |n| node_to_hash(n) } }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def node_to_hash(node)
|
35
|
+
rv = [:title, :content, :todo_state, :date, :stars].
|
36
|
+
map { |k| [ k, node.send(k) ] }.to_h
|
37
|
+
|
38
|
+
rv[:date] = rv[:date].strftime('%Y-%m-%d %H:%M') if rv[:date]
|
39
|
+
rv
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/org_mode.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
# Public: Domain objects
|
2
|
+
# describing the elements of an org-mod file
|
3
|
+
#
|
4
|
+
# OrgMode::File encapsulates the org file, with all its settings
|
5
|
+
# customizations, code blocks, TODO statusses.
|
6
|
+
#
|
7
|
+
# This domain model will be build using one of the parsers which
|
8
|
+
# you can find somewhere in lib/org_mode/parser*
|
9
|
+
#
|
10
|
+
# Writing this domain-model to a file can be done using
|
11
|
+
# one of the Writes in lib/org_mode/writers/*
|
12
|
+
#
|
13
|
+
require "org_mode/version"
|
14
|
+
|
15
|
+
module OrgMode
|
16
|
+
|
17
|
+
|
18
|
+
class Node
|
19
|
+
attr_accessor :title, :content, :stars, :date_start_time, :date_end_time, :todo_state
|
20
|
+
attr_accessor :parent, :children
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@parent = nil
|
24
|
+
@children = []
|
25
|
+
end
|
26
|
+
|
27
|
+
alias :date :date_start_time
|
28
|
+
alias :date= :date_start_time=
|
29
|
+
|
30
|
+
def indent
|
31
|
+
stars + 1
|
32
|
+
end
|
33
|
+
|
34
|
+
def root_node?
|
35
|
+
stars == 1
|
36
|
+
end
|
37
|
+
|
38
|
+
def scheduled?
|
39
|
+
!date.nil?
|
40
|
+
end
|
41
|
+
|
42
|
+
def open?
|
43
|
+
not done?
|
44
|
+
end
|
45
|
+
|
46
|
+
def done?
|
47
|
+
todo_state == 'DONE'
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
module FileInterface
|
53
|
+
def root_nodes
|
54
|
+
nodes.select(&:root_node?)
|
55
|
+
end
|
56
|
+
|
57
|
+
def scheduled_nodes
|
58
|
+
nodes.select(&:scheduled?)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class File
|
63
|
+
attr_accessor :header, :nodes, :footer
|
64
|
+
|
65
|
+
include FileInterface
|
66
|
+
|
67
|
+
def initialize(header, nodes, footer)
|
68
|
+
@header = header
|
69
|
+
@footer = footer
|
70
|
+
@nodes = nodes
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class FileCollection
|
75
|
+
attr_accessor :files
|
76
|
+
|
77
|
+
include FileInterface
|
78
|
+
|
79
|
+
def initialize(files)
|
80
|
+
@files = files
|
81
|
+
end
|
82
|
+
|
83
|
+
def nodes
|
84
|
+
files.map(&:nodes).flatten
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
data/org_mode.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "org_mode/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "org_mode"
|
7
|
+
s.version = OrgMode::VERSION
|
8
|
+
s.authors = ["Boy Maas"]
|
9
|
+
s.email = ["boy.maas@gmail.com"]
|
10
|
+
s.homepage = "https://github.com/boymaas/orgy"
|
11
|
+
s.summary = %q{Parses and does all kinds of tricks with org-mode files}
|
12
|
+
s.description = %q{Org-mode parser, presenters, and reformatters}
|
13
|
+
|
14
|
+
s.rubyforge_project = "org_mode"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
# s.add_development_dependency "rspec"
|
23
|
+
s.add_development_dependency "rake"
|
24
|
+
# s.add_runtime_dependency "rest-client"
|
25
|
+
s.add_runtime_dependency "commander"
|
26
|
+
s.add_runtime_dependency "facets"
|
27
|
+
s.add_runtime_dependency "mustache"
|
28
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
* Main
|
2
|
+
** FirstChildMain
|
3
|
+
** SecondChildMain
|
4
|
+
*** FirstChildSecondChildMain
|
5
|
+
* SecondMain
|
6
|
+
** FirstChildMain
|
7
|
+
*** SecondChildMain
|
8
|
+
** FirstChildSecondChildMain
|
9
|
+
* ThirdMain
|
10
|
+
** FirstChildMain
|
11
|
+
*** FirstChildFirstChildMain
|
12
|
+
**** FirstChildFirstChildFirstChildMain
|