org_mode 0.0.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/.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
|