working_class 0.1.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.
- 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
|