working_class 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +8 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +674 -0
- data/README.md +195 -0
- data/Rakefile +11 -0
- data/lib/working_class.rb +29 -0
- data/lib/working_class/parser.rb +165 -0
- data/lib/working_class/task.rb +82 -0
- data/lib/working_class/tasklist.rb +57 -0
- data/lib/working_class/version.rb +5 -0
- data/test/examples/example_1.txt +3 -0
- data/test/examples/example_2.txt +3 -0
- data/test/examples/example_3.txt +4 -0
- data/test/examples/example_4.txt +4 -0
- data/test/examples/example_5.txt +5 -0
- data/test/examples/example_6.txt +5 -0
- data/test/parser_test.rb +185 -0
- data/test/task_test.rb +108 -0
- data/test/tasklist_test.rb +69 -0
- data/test/test_helper.rb +2 -0
- data/test/working_class_test.rb +24 -0
- data/workingclass.gemspec +26 -0
- metadata +122 -0
data/README.md
ADDED
@@ -0,0 +1,195 @@
|
|
1
|
+
# WorkingClass
|
2
|
+
|
3
|
+
WorkingClass is an human readable syntax to write tasklists.
|
4
|
+
Besides being easy to read it's fully parseable, so you can work with the tasks
|
5
|
+
in Ruby.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'working_class'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
```sh
|
18
|
+
$ bundle
|
19
|
+
```
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
```sh
|
24
|
+
$ gem install working_class
|
25
|
+
```
|
26
|
+
|
27
|
+
## Usage
|
28
|
+
|
29
|
+
### The Syntax
|
30
|
+
|
31
|
+
Keep in mind that all dates are formatted like this: `DD.MM.YYYY` or `D.M.YY`
|
32
|
+
|
33
|
+
```
|
34
|
+
Tasklist Name
|
35
|
+
---
|
36
|
+
[ ] My first task
|
37
|
+
[X] A finished task
|
38
|
+
[ ]{1.1.15} A task with a due date
|
39
|
+
[ ]{1.1.15}(31.1.15 12:00) A task with a date and a reminder
|
40
|
+
[ ]{1.1.15}(-1 12:00) A task with a date and a »relative« reminder
|
41
|
+
[ ]{1.1.15}(12:00) A task that will remind me at 12:00 1.1.15
|
42
|
+
```
|
43
|
+
|
44
|
+
You see it's pretty easy to write tasks like this.
|
45
|
+
|
46
|
+
At the moment the order of date and reminder is mandatory.
|
47
|
+
|
48
|
+
So you **can't** write:
|
49
|
+
|
50
|
+
```
|
51
|
+
My Tasklist
|
52
|
+
---
|
53
|
+
[ ](REMINDER){DATE} My Task
|
54
|
+
```
|
55
|
+
|
56
|
+
|
57
|
+
#### Tasklist Names
|
58
|
+
|
59
|
+
A tasklist name is written like this:
|
60
|
+
|
61
|
+
The `---` is important, don't forget it.
|
62
|
+
|
63
|
+
Every tasklist should have a name.
|
64
|
+
|
65
|
+
```
|
66
|
+
Tasklist Name
|
67
|
+
---
|
68
|
+
…
|
69
|
+
```
|
70
|
+
|
71
|
+
#### Unfinished Tasks
|
72
|
+
|
73
|
+
Both tasks are equal, they are both not finished.
|
74
|
+
|
75
|
+
We recommend the `[ ]`, it looks much nicer.
|
76
|
+
|
77
|
+
```
|
78
|
+
Shopping List
|
79
|
+
---
|
80
|
+
[ ] Jeans
|
81
|
+
[] T-Shirts
|
82
|
+
```
|
83
|
+
|
84
|
+
#### Finished Tasks
|
85
|
+
|
86
|
+
```
|
87
|
+
Groceries List
|
88
|
+
---
|
89
|
+
[X] Milk
|
90
|
+
[x] Bread
|
91
|
+
```
|
92
|
+
|
93
|
+
To write a finished task you have to write a `[X]` or `[x]`. It's not important
|
94
|
+
whether you write a small x or a capital X, both characters are recognized as
|
95
|
+
a finished task.
|
96
|
+
|
97
|
+
|
98
|
+
#### Tasks with a Date
|
99
|
+
|
100
|
+
It doesn't matter if you write your dates DD.MM.YY or D.M.YY or DD.MM.YYYY
|
101
|
+
WorkingClass accepts all of those formats, as long as it is a valid date.
|
102
|
+
|
103
|
+
|
104
|
+
```
|
105
|
+
The Party List
|
106
|
+
---
|
107
|
+
[X]{6.2.2015} Birthday Party
|
108
|
+
[X]{13.2.15} Another Birthday Party
|
109
|
+
```
|
110
|
+
|
111
|
+
#### Tasks with a Reminder
|
112
|
+
|
113
|
+
Every task can have a reminder.
|
114
|
+
You have several options when adding a reminder.
|
115
|
+
|
116
|
+
All times are 24h.
|
117
|
+
|
118
|
+
You can write a full date without a time and the parser will add the default
|
119
|
+
time (9:00) automatically.
|
120
|
+
|
121
|
+
```
|
122
|
+
An even more awesome Party List
|
123
|
+
---
|
124
|
+
[ ](31.1.15) This time of year
|
125
|
+
```
|
126
|
+
|
127
|
+
If you already specified a date for the task you can use a relative reminder
|
128
|
+
by writing `-2`, this specifies that you want to be reminded 2 days earlier.
|
129
|
+
The parser will add the default time, if you didn't add one.
|
130
|
+
|
131
|
+
```
|
132
|
+
The after party
|
133
|
+
---
|
134
|
+
[ ]{2.1.15}(-2) You will have to clean up everthing.
|
135
|
+
```
|
136
|
+
|
137
|
+
**Important:** This only works if your task has already a date.
|
138
|
+
|
139
|
+
This will not work:
|
140
|
+
|
141
|
+
```
|
142
|
+
The after party
|
143
|
+
---
|
144
|
+
[ ](-2) You will have to clean up everthing.
|
145
|
+
```
|
146
|
+
|
147
|
+
So enough of that, what about the times. You can easily add a time to your
|
148
|
+
reminder
|
149
|
+
|
150
|
+
```
|
151
|
+
My Finals
|
152
|
+
---
|
153
|
+
[ ]{26.1.15}(15:00) English
|
154
|
+
```
|
155
|
+
|
156
|
+
If you don't specify a relative or absolute date you will be reminded at 15:00
|
157
|
+
on the same day.
|
158
|
+
|
159
|
+
|
160
|
+
You can also combine absolute or relative dates with a time
|
161
|
+
|
162
|
+
```
|
163
|
+
My Finals
|
164
|
+
---
|
165
|
+
[ ]{26.1.15}(24.1.15 9:00) Don't panic.
|
166
|
+
[ ]{26.1.15}(-1 15:00) English
|
167
|
+
```
|
168
|
+
|
169
|
+
### The Parser
|
170
|
+
|
171
|
+
Check out the [full documentation](http://www.rubydoc.info/github/TimKaechele/WorkingClass/master)
|
172
|
+
|
173
|
+
```ruby
|
174
|
+
require 'working_class'
|
175
|
+
|
176
|
+
string = """
|
177
|
+
My Finals
|
178
|
+
---
|
179
|
+
[ ]{26.1.15}(15:00) English
|
180
|
+
"""
|
181
|
+
|
182
|
+
WorkingClass.load(string) # => WorkingClass::Tasklist
|
183
|
+
|
184
|
+
# or if you have a file
|
185
|
+
WorkingClass.load_file('./examples/example_1.txt') # => WorkingClass::Tasklist
|
186
|
+
|
187
|
+
```
|
188
|
+
|
189
|
+
## Contributing
|
190
|
+
|
191
|
+
1. Fork it ( https://github.com/TimKaechele/workingclass/fork )
|
192
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
193
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
194
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
195
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'working_class/version'
|
2
|
+
require 'working_class/parser'
|
3
|
+
require 'working_class/task'
|
4
|
+
require 'working_class/tasklist'
|
5
|
+
|
6
|
+
# WorkingClass Module
|
7
|
+
#
|
8
|
+
module WorkingClass
|
9
|
+
|
10
|
+
# Loads the file from the path and returns a Tasklist
|
11
|
+
#
|
12
|
+
# @param path [String] the filepath
|
13
|
+
# @return [WorkingClass::Tasklist] the parsed Tasklist
|
14
|
+
#
|
15
|
+
def self.load_file(path)
|
16
|
+
string = File.open(path, 'r').read()
|
17
|
+
self.load(string)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Parses the given string and returns a Tasklist
|
21
|
+
#
|
22
|
+
# @param string [String] the WorkingClass tasklist syntax string
|
23
|
+
# @return [WorkingClass::Tasklist] the parsed Tasklist
|
24
|
+
#
|
25
|
+
def self.load(string)
|
26
|
+
Parser.new(string).to_tasklist
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module WorkingClass
|
4
|
+
# The actual syntax parser
|
5
|
+
#
|
6
|
+
class Parser
|
7
|
+
|
8
|
+
# Initializes a new Parser object with a given WorkingClass syntax string
|
9
|
+
#
|
10
|
+
# @param string [String] the raw string with the WorkingClass syntax
|
11
|
+
# @return [WorkingClass::Parser] a parser instance
|
12
|
+
#
|
13
|
+
def initialize(string)
|
14
|
+
@raw_string = string
|
15
|
+
@date_regex = /(?:\d{1,2}\.){2}\d{2,4}/
|
16
|
+
@time_regex = /\d{1,2}:\d{1,2}/
|
17
|
+
end
|
18
|
+
|
19
|
+
# Parses the syntax and returns an Hash with the result
|
20
|
+
#
|
21
|
+
# @return [Hash] a Hash representation of the parsed tasklist
|
22
|
+
#
|
23
|
+
def to_h
|
24
|
+
{
|
25
|
+
name: tasklist_name,
|
26
|
+
tasks_count: tasks.length,
|
27
|
+
tasks: tasks
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
# Parses the syntax and returns a WorkingClass::Tasklist instance
|
32
|
+
#
|
33
|
+
# @return [WorkingClass::Tasklist] a Tasklist instance of the parsed tasklist
|
34
|
+
#
|
35
|
+
def to_tasklist
|
36
|
+
tasks = Array.new
|
37
|
+
raw = self.to_h
|
38
|
+
raw[:tasks].each do |t|
|
39
|
+
task = Task.new(t[:name], t)
|
40
|
+
tasks << task
|
41
|
+
end
|
42
|
+
Tasklist.new raw[:name], tasks
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
def tasklist_name_regex
|
47
|
+
/(.*)\n---/
|
48
|
+
end
|
49
|
+
|
50
|
+
def task_regex
|
51
|
+
/\[(.*)\] *(?:\{(.*)\})? *(?:\((.*)\)){0,1} *(.*)/
|
52
|
+
end
|
53
|
+
|
54
|
+
def tasklist_name
|
55
|
+
@raw_string.match(tasklist_name_regex).captures.first
|
56
|
+
end
|
57
|
+
|
58
|
+
def tasks
|
59
|
+
tasks = []
|
60
|
+
@raw_string.scan(task_regex).each do |match|
|
61
|
+
task = Hash.new
|
62
|
+
task[:is_finished] = extract_is_finished(match[0])
|
63
|
+
task[:date] = extract_date(match[1])
|
64
|
+
task[:reminder] = extract_reminder(task[:date], match[2])
|
65
|
+
task[:name] = extract_name(match[3])
|
66
|
+
tasks << task
|
67
|
+
end
|
68
|
+
tasks
|
69
|
+
end
|
70
|
+
|
71
|
+
def extract_is_finished(match)
|
72
|
+
match = match.strip
|
73
|
+
if match == "x" or match == "X"
|
74
|
+
true
|
75
|
+
else
|
76
|
+
false
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def extract_date(match)
|
81
|
+
if match.nil?
|
82
|
+
return nil
|
83
|
+
end
|
84
|
+
|
85
|
+
if match.scan(@date_regex)
|
86
|
+
# @todo rescue the right exception
|
87
|
+
begin
|
88
|
+
normalize_date(match)
|
89
|
+
rescue
|
90
|
+
nil
|
91
|
+
end
|
92
|
+
else
|
93
|
+
nil
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
def extract_name(match)
|
99
|
+
if !match.nil?
|
100
|
+
match.strip
|
101
|
+
else
|
102
|
+
""
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def normalize_date(date_string)
|
107
|
+
parts = date_string.split(".")
|
108
|
+
if parts[2].length == 2
|
109
|
+
parts[2] = "20" + parts[2]
|
110
|
+
end
|
111
|
+
parts = parts.map { |p| p.to_i }
|
112
|
+
Date.new(parts[2], parts[1], parts[0])
|
113
|
+
end
|
114
|
+
|
115
|
+
# Shame Shame
|
116
|
+
# @todo REFACTOR!!!!!1eleven!1
|
117
|
+
def extract_reminder(task_date, match)
|
118
|
+
if match.nil?
|
119
|
+
return nil
|
120
|
+
end
|
121
|
+
|
122
|
+
if match.empty? and !task_date.nil?
|
123
|
+
return DateTime.parse(task_date.to_s + " 9:00")
|
124
|
+
elsif match.empty? and task_date.nil?
|
125
|
+
return nil
|
126
|
+
end
|
127
|
+
|
128
|
+
date = nil
|
129
|
+
time = nil
|
130
|
+
|
131
|
+
absolute_date = match.scan(/(#{@date_regex})/)
|
132
|
+
relative_date = match.scan(/(-\d{1,2})/)
|
133
|
+
match_time = match.scan(/(#{@time_regex})/)
|
134
|
+
|
135
|
+
if absolute_date.empty? and (!relative_date.empty? and !task_date.nil?)
|
136
|
+
date = task_date + relative_date.flatten.first.to_i
|
137
|
+
elsif !absolute_date.empty?
|
138
|
+
# @todo rescue the right exception
|
139
|
+
begin
|
140
|
+
date = normalize_date(absolute_date.flatten.first)
|
141
|
+
rescue
|
142
|
+
date = nil
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
if !match_time.empty?
|
147
|
+
time = match_time
|
148
|
+
end
|
149
|
+
|
150
|
+
if !date.nil? and !time.nil?
|
151
|
+
DateTime.parse(date.to_s + " #{time}")
|
152
|
+
elsif (date.nil? and !task_date.nil?) and !time.nil?
|
153
|
+
DateTime.parse(task_date.to_s + " #{time}")
|
154
|
+
elsif !date.nil? and time.nil?
|
155
|
+
DateTime.parse(date.to_s + " 9:00")
|
156
|
+
else
|
157
|
+
return nil
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
|
162
|
+
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module WorkingClass
|
2
|
+
|
3
|
+
# A basic represantation of a Task
|
4
|
+
#
|
5
|
+
# @attr_reader name [String] the task name
|
6
|
+
# @attr_reader is_finished [Boolean] true if the task is finished
|
7
|
+
# @attr_reader date [Date] the day the task is due
|
8
|
+
# @attr_reader reminder [DateTime] a DateTime with a reminder
|
9
|
+
class Task
|
10
|
+
|
11
|
+
attr_reader :name
|
12
|
+
attr_reader :date
|
13
|
+
attr_reader :reminder
|
14
|
+
attr_reader :is_finished
|
15
|
+
|
16
|
+
alias :is_finished? :is_finished
|
17
|
+
alias :finished? :is_finished
|
18
|
+
|
19
|
+
# Initializes a new task object with a name, and options
|
20
|
+
#
|
21
|
+
# @param name [String] the task name
|
22
|
+
# @param options [Hash] an options hash
|
23
|
+
#
|
24
|
+
# @option options [Boolean] :is_finished (false) true if the task is finished
|
25
|
+
# @option options [Date] :date (nil) the date when the task is due
|
26
|
+
# @option options [DateTime] :reminder (nil )a DateTime with a reminder
|
27
|
+
#
|
28
|
+
def initialize name, options = {}
|
29
|
+
options = {is_finished: false, date: nil, reminder: nil}.merge(options)
|
30
|
+
@name = name
|
31
|
+
@is_finished = options[:is_finished]
|
32
|
+
@date = options[:date]
|
33
|
+
@reminder = options[:reminder]
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns true if the task is upcoming
|
37
|
+
#
|
38
|
+
# A Task without a date is always upcoming.
|
39
|
+
# @todo add example
|
40
|
+
#
|
41
|
+
# A finished task is never upcoming.
|
42
|
+
# @todo add example
|
43
|
+
#
|
44
|
+
# @return [Boolean] true if the task is upcoming
|
45
|
+
def is_upcoming
|
46
|
+
if @is_finished
|
47
|
+
false
|
48
|
+
elsif !@date.nil?
|
49
|
+
Date.today <= @date
|
50
|
+
else
|
51
|
+
true
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
alias :is_upcoming? :is_upcoming
|
56
|
+
alias :upcoming? :is_upcoming
|
57
|
+
|
58
|
+
# Returns true if the task is due tomorrow
|
59
|
+
#
|
60
|
+
# A Task without a date is always due tomorrow.
|
61
|
+
# @todo add example
|
62
|
+
#
|
63
|
+
# A finished task is never due tomorrow.
|
64
|
+
# @todo add example
|
65
|
+
#
|
66
|
+
# @return [Boolean] true if the task is due tomorrow
|
67
|
+
def is_tomorrow
|
68
|
+
if @is_finished
|
69
|
+
false
|
70
|
+
elsif date.nil?
|
71
|
+
true
|
72
|
+
else
|
73
|
+
Date.today + 1 == @date
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
alias :is_tomorrow? :is_tomorrow
|
78
|
+
alias :tomorrow? :is_tomorrow
|
79
|
+
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|