mastodon 0.1.0
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/README.mkd +9 -0
- data/Rakefile +55 -0
- data/VERSION.yml +5 -0
- data/lib/mastodon.rb +82 -0
- data/lib/mastodon/todo.rb +27 -0
- data/lib/mastodon/version.rb +5 -0
- data/test/run_tests.rb +2 -0
- data/test/test_helper.rb +14 -0
- data/test/test_mastodon.rb +37 -0
- data/test/todo.txt +17 -0
- metadata +69 -0
data/README.mkd
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
begin
|
2
|
+
require 'jeweler'
|
3
|
+
rescue LoadError
|
4
|
+
puts "Jeweler not available, but is required. Install it with: sudo gem install jeweler"
|
5
|
+
exit 1
|
6
|
+
end
|
7
|
+
|
8
|
+
@jtasks = Jeweler::Tasks.new do |gemspec|
|
9
|
+
gemspec.name = "mastodon"
|
10
|
+
gemspec.summary = "A ruby parser for todo.txt files"
|
11
|
+
gemspec.description = "Mastodon: A ruby parser for todo.txt files.\n\nAnd the mastodon, like plain text, isn't extinct! (Yet.)"
|
12
|
+
gemspec.email = "colin@evaryont.me"
|
13
|
+
gemspec.homepage = "http://github.com/evaryont/mastodon/"
|
14
|
+
gemspec.authors = ["Colin Shea"]
|
15
|
+
end
|
16
|
+
Jeweler::GemcutterTasks.new
|
17
|
+
@jeweler = @jtasks.jeweler
|
18
|
+
|
19
|
+
namespace :version do
|
20
|
+
desc "DON'T CALL. Write the version to lib/#{@jeweler.gemspec_helper.spec.name}/version.rb"
|
21
|
+
task :ruby do
|
22
|
+
# TODO: This is hard coded but it should be dynamic, given
|
23
|
+
# the gem spec
|
24
|
+
file = File.open("lib/#{@jeweler.gemspec_helper.spec.name}/version.rb", "w")
|
25
|
+
# TODO: and so should this class template
|
26
|
+
file.write <<-CLASS
|
27
|
+
# Automatically generated by `rake version:ruby' - use version:bump to change
|
28
|
+
# this, do not edit this directly, as it will be overwritten!
|
29
|
+
class #{@jeweler.gemspec_helper.spec.name.capitalize}
|
30
|
+
VERSION = [#{@jeweler.major_version}, #{@jeweler.minor_version}, #{@jeweler.patch_version}]
|
31
|
+
end
|
32
|
+
CLASS
|
33
|
+
system("git commit --file='.git/COMMIT_EDITMSG' --amend -q -o 'lib/#{@jeweler.gemspec_helper.spec.name}/version.rb'")
|
34
|
+
end
|
35
|
+
|
36
|
+
# Take advantage of Rake not overwriting tasks.
|
37
|
+
#
|
38
|
+
# After the 'real' task has been called, write version.rb as well.
|
39
|
+
namespace :bump do
|
40
|
+
task :major do
|
41
|
+
Rake::Task["version:ruby"].execute
|
42
|
+
end
|
43
|
+
task :minor do
|
44
|
+
Rake::Task["version:ruby"].execute
|
45
|
+
end
|
46
|
+
task :patch do
|
47
|
+
Rake::Task["version:ruby"].execute
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
desc "Run the nanotest suite"
|
53
|
+
task :test do
|
54
|
+
system("ruby -I'lib:test' test/run_tests.rb")
|
55
|
+
end
|
data/VERSION.yml
ADDED
data/lib/mastodon.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'pp'
|
3
|
+
|
4
|
+
require 'mastodon/todo'
|
5
|
+
require 'mastodon/version'
|
6
|
+
|
7
|
+
class Mastodon
|
8
|
+
# A context is: An at-sign (@) followed by one or more non-whitespace characters.
|
9
|
+
Context_Regex = /(?<!\+)\B@(\S+)/
|
10
|
+
# A project is: An plus sign (+) followed by one or more non-whitespace characters.
|
11
|
+
Project_Regex = /(?<!@)\B\+(\S+)/
|
12
|
+
# A priority is: A capital letter (A-Z) surrounded by parentheses, at the beginning of the line.
|
13
|
+
Priority_Regex = /^\(([A-Z])\)/
|
14
|
+
|
15
|
+
attr_reader :contexts, :projects, :todos
|
16
|
+
|
17
|
+
# +todo+: An array of strings (each being an individual todo item)
|
18
|
+
def initialize(todos)
|
19
|
+
parse! todos.map{|line| line.strip}
|
20
|
+
end
|
21
|
+
|
22
|
+
def parse!(todos)
|
23
|
+
@contexts = Set.new
|
24
|
+
@projects = Set.new
|
25
|
+
@todos = []
|
26
|
+
|
27
|
+
todos.each do |todo|
|
28
|
+
# Looping through the string, find the metadata (context, project,
|
29
|
+
# priority). Store it in a temporary variable, then clear it from
|
30
|
+
# the original string. Strip all remaining whitespace at the end.
|
31
|
+
|
32
|
+
current_contexts = []
|
33
|
+
current_projects = []
|
34
|
+
current_priority, priority = nil, nil
|
35
|
+
|
36
|
+
until ((context = todo[Context_Regex]).nil?)
|
37
|
+
index = todo.index(context)
|
38
|
+
todo[index..(index+context.length)] = ""
|
39
|
+
current_contexts << context.match(Context_Regex)[1]
|
40
|
+
contexts << context.match(Context_Regex)[1]
|
41
|
+
end
|
42
|
+
|
43
|
+
until ((project = todo[Project_Regex]).nil?)
|
44
|
+
index = todo.index(project)
|
45
|
+
todo[index..(index+project.length)] = ""
|
46
|
+
current_projects << project.match(Project_Regex)[1]
|
47
|
+
projects << project.match(Project_Regex)[1]
|
48
|
+
end
|
49
|
+
|
50
|
+
priority = todo[Priority_Regex]
|
51
|
+
unless priority.nil?
|
52
|
+
index = todo.index(priority)
|
53
|
+
todo[index..(index+priority.length)] = ""
|
54
|
+
current_priority = priority.match(Priority_Regex)[1]
|
55
|
+
end
|
56
|
+
|
57
|
+
todo.strip!
|
58
|
+
@todos << Mastodon::Todo.new(todo, current_contexts, current_projects, current_priority)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# How many todos there are.
|
63
|
+
def size
|
64
|
+
@todos.size
|
65
|
+
end
|
66
|
+
|
67
|
+
# Get an individual todo. The id and line number are the same. 0 index, not
|
68
|
+
# 1, as files are numbered. So line 3 is id 2. XXX: Change?
|
69
|
+
def [](id)
|
70
|
+
@todos[id]
|
71
|
+
end
|
72
|
+
|
73
|
+
# Find all todos that have the context @+context+
|
74
|
+
def find_context(context)
|
75
|
+
@todos.select { |todo| todo.contexts.include? context }
|
76
|
+
end
|
77
|
+
|
78
|
+
# Find all todos that have the project ++project+
|
79
|
+
def find_project(project)
|
80
|
+
@todos.select { |todo| todo.projects.include? project }
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class Mastodon
|
2
|
+
class Todo < Struct.new(:text, :contexts, :projects, :priority)
|
3
|
+
include Comparable
|
4
|
+
|
5
|
+
def to_s
|
6
|
+
pri = priority ? "(#{priority})" : ""
|
7
|
+
"#{pri} #{text} @#{contexts.join(' @')} +#{projects.join(' +')}".strip
|
8
|
+
end
|
9
|
+
|
10
|
+
def inspect
|
11
|
+
"#<Mastodon::Todo \"#{to_s}\">"
|
12
|
+
end
|
13
|
+
|
14
|
+
# Comparison operator: Sort todos by their priority first, then sort
|
15
|
+
# them by the text.
|
16
|
+
def <=>(other_todo)
|
17
|
+
return -1 if (priority.nil? and !other_todo.priority.nil?)
|
18
|
+
|
19
|
+
pri = (priority <=> other_todo.priority)
|
20
|
+
if pri == 0
|
21
|
+
return text <=> other_todo.text
|
22
|
+
else
|
23
|
+
return pri
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/test/run_tests.rb
ADDED
data/test/test_helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'nanotest'
|
2
|
+
|
3
|
+
begin # Optional dependencies
|
4
|
+
require 'nanotest/stats' # Gets some nice speed stats.
|
5
|
+
require 'redgreen' # Pretty colors!
|
6
|
+
rescue LoadError
|
7
|
+
end
|
8
|
+
include Nanotest
|
9
|
+
|
10
|
+
# Include the library
|
11
|
+
require File.dirname(__FILE__) + "/../lib/mastodon.rb"
|
12
|
+
|
13
|
+
SAMPLE_TODOS = File.readlines(File.join(File.dirname(__FILE__), "todo.txt")) unless Kernel.const_defined? :SAMPLE_TODOS
|
14
|
+
@mast = Mastodon.new(SAMPLE_TODOS)
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
# Test searching and caching.
|
4
|
+
assert { @mast.projects.to_a == ["proj", "proj2", "proj+phone", "proj/foo", "p_r_o_j", "p-r-o-j", "+proj", "@proj"] }
|
5
|
+
assert { @mast.contexts.to_a == ["con", "con2", "con@phone", "con/foo", "c_o_n", "c-o-n", "@con", "+con"] }
|
6
|
+
|
7
|
+
# Finding existing items
|
8
|
+
assert { @mast.find_context("con2").size > 0 }
|
9
|
+
assert { @mast.find_context("con2").first.text == "Multiple contexts" }
|
10
|
+
assert { @mast.find_project("proj2").size > 0 }
|
11
|
+
assert { @mast.find_project("proj2").first.text == "Multiple projects" }
|
12
|
+
|
13
|
+
# Finding non-existing items
|
14
|
+
assert { @mast.find_context("nosuchthing") == [] }
|
15
|
+
assert { @mast.find_project("thisprojectdoesntexist") == [] }
|
16
|
+
|
17
|
+
# General utility methods.
|
18
|
+
assert { @mast.size == SAMPLE_TODOS.size }
|
19
|
+
# While I can't guarantee a 1:1 match, nothing should be lost. Just possibly rearranged.
|
20
|
+
assert { (@mast[0].to_s.chars.to_a == SAMPLE_TODOS[0].strip.chars.to_a) and (@mast[0].to_s.length == SAMPLE_TODOS[0].strip.length) }
|
21
|
+
|
22
|
+
# Test if correct classes are returned
|
23
|
+
assert { @mast.projects.is_a? Set }
|
24
|
+
assert { @mast.contexts.is_a? Set }
|
25
|
+
assert { @mast[0].is_a? Mastodon::Todo }
|
26
|
+
|
27
|
+
# Test if matching at the beginning of a line works
|
28
|
+
assert { @mast[13].contexts = ["con"] }
|
29
|
+
assert { @mast[14].projects = ["proj"] }
|
30
|
+
|
31
|
+
# Test priority detection
|
32
|
+
assert { @mast[3].priority.nil? }
|
33
|
+
assert { @mast[4].priority.nil? }
|
34
|
+
assert { @mast[5].priority = "A" }
|
35
|
+
assert { @mast[6].priority = "Z" }
|
36
|
+
assert { @mast[7].priority = "Q" }
|
37
|
+
assert { @mast[8].priority.nil? }
|
data/test/todo.txt
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
Todo @con +proj
|
2
|
+
left @ right @con
|
3
|
+
user@server email
|
4
|
+
Multiple projects +proj +proj2
|
5
|
+
Multiple contexts @con @con2
|
6
|
+
(A) Priority
|
7
|
+
(Z) All the way to Z
|
8
|
+
(Q) All mixed together @con +proj
|
9
|
+
Not a priority: (A)
|
10
|
+
Advanced contexts @con@phone @con/foo @c_o_n @c-o-n @@con
|
11
|
+
Advanced projects +proj+phone +proj/foo +p_r_o_j +p-r-o-j ++proj
|
12
|
+
Mixed symbols: +@proj @+con
|
13
|
+
@con Leading with a context
|
14
|
+
+proj Leading with a project
|
15
|
+
Google+Yahoo+Microsoft == monopoly
|
16
|
+
Google + Yahoo + Microsoft == monopoly
|
17
|
+
Ready to (ab)use
|
metadata
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mastodon
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Colin Shea
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-12-23 00:00:00 -05:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: |-
|
17
|
+
Mastodon: A ruby parser for todo.txt files.
|
18
|
+
|
19
|
+
And the mastodon, like plain text, isn't extinct! (Yet.)
|
20
|
+
email: colin@evaryont.me
|
21
|
+
executables: []
|
22
|
+
|
23
|
+
extensions: []
|
24
|
+
|
25
|
+
extra_rdoc_files:
|
26
|
+
- README.mkd
|
27
|
+
files:
|
28
|
+
- Rakefile
|
29
|
+
- VERSION.yml
|
30
|
+
- lib/mastodon.rb
|
31
|
+
- lib/mastodon/todo.rb
|
32
|
+
- lib/mastodon/version.rb
|
33
|
+
- test/run_tests.rb
|
34
|
+
- test/test_helper.rb
|
35
|
+
- test/test_mastodon.rb
|
36
|
+
- test/todo.txt
|
37
|
+
- README.mkd
|
38
|
+
has_rdoc: true
|
39
|
+
homepage: http://github.com/evaryont/mastodon/
|
40
|
+
licenses: []
|
41
|
+
|
42
|
+
post_install_message:
|
43
|
+
rdoc_options:
|
44
|
+
- --charset=UTF-8
|
45
|
+
require_paths:
|
46
|
+
- lib
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: "0"
|
52
|
+
version:
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: "0"
|
58
|
+
version:
|
59
|
+
requirements: []
|
60
|
+
|
61
|
+
rubyforge_project:
|
62
|
+
rubygems_version: 1.3.5
|
63
|
+
signing_key:
|
64
|
+
specification_version: 3
|
65
|
+
summary: A ruby parser for todo.txt files
|
66
|
+
test_files:
|
67
|
+
- test/run_tests.rb
|
68
|
+
- test/test_helper.rb
|
69
|
+
- test/test_mastodon.rb
|