ptf 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,67 @@
1
+ require 'ptf/commands/task/create'
2
+ require 'ptf/commands/task/edit'
3
+
4
+ module Ptf
5
+ module Commands
6
+ module Task
7
+ class << self
8
+
9
+ def show(id)
10
+ if !Ptf::Utilities::FileSystem.id_exist?(id.to_i)
11
+ puts "Task #{id} does not exist."
12
+ return
13
+ end
14
+
15
+ metadata_file = Ptf::Utilities::FileSystem.find_file id.to_i
16
+
17
+ if metadata_file.nil?
18
+ puts "Task #{id} has been closed. Reopen it with ptf task reopen ###."
19
+ return
20
+ end
21
+
22
+ info = Ptf::Data::MetadataFile.new(metadata_file)
23
+
24
+ data_file = File.join(Ptf::Utilities::FileSystem.data_dir, info.hash)
25
+ puts `cat #{data_file}`
26
+ end
27
+
28
+ def close(id)
29
+ if !Ptf::Utilities::FileSystem.id_exist?(id.to_i)
30
+ puts "Task #{id} does not exist."
31
+ return
32
+ end
33
+
34
+ metadata_file = Ptf::Utilities::FileSystem.find_file id.to_i
35
+
36
+ if metadata_file.nil?
37
+ puts "Task #{id} has been closed. Reopen it with ptf task reopen ###."
38
+ return
39
+ end
40
+
41
+ info = Ptf::Data::MetadataFile.create_from_file(metadata_file)
42
+
43
+ info.complete_now
44
+ end
45
+
46
+ def reopen(id)
47
+ if !Ptf::Utilities::FileSystem.id_exist?(id.to_i)
48
+ puts "Task #{id} does not exist."
49
+ return
50
+ end
51
+
52
+ metadata_file = Ptf::Utilities::FileSystem.find_file id.to_i, false
53
+
54
+ if metadata_file.nil?
55
+ puts "Task #{id} is already open. Close it with ptf task close ###."
56
+ return
57
+ end
58
+
59
+ info = Ptf::Data::MetadataFile.create_from_file(metadata_file)
60
+
61
+ info.reopen
62
+ end
63
+
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,13 @@
1
+ require 'ptf/commands/init'
2
+ require 'ptf/commands/list'
3
+ require 'ptf/commands/task'
4
+ require 'ptf/commands/group'
5
+
6
+ module Ptf
7
+ module Commands
8
+ class << self
9
+
10
+ end
11
+ end
12
+ end
13
+
data/lib/ptf/config.rb ADDED
@@ -0,0 +1,42 @@
1
+ require 'yaml'
2
+
3
+ module Ptf
4
+ module Config
5
+ YAML_LOCATION = File.join(Dir.home, ".ptfconfig")
6
+
7
+ EDITABLE = {
8
+ :default_group => "unassigned:NONE",
9
+ :base_dir => File.join(Dir.home, ".ptf")
10
+ }
11
+
12
+ STATIC = {
13
+ :file_permission => 0700,
14
+ :id_counter_start => "1",
15
+ :task_metadata_dir => "info",
16
+ :task_data_dir => "data",
17
+ :in_progress_dir => "open",
18
+ :completed_dir => "closed",
19
+ :tmp_dir => "tmp",
20
+ :task_counter_file => "id_counter",
21
+ :group_list_file => "group_list"
22
+ }
23
+
24
+ def self.get_config
25
+ yaml_settings = {}
26
+
27
+ yaml_settings = YAML.load(File.read(YAML_LOCATION)) if File.exist?(YAML_LOCATION)
28
+
29
+ EDITABLE.merge(yaml_settings).merge(STATIC)
30
+ end
31
+
32
+ def self.write_config(config)
33
+ new_config = EDITABLE.merge config.select { |k| EDITABLE.keys.include? k }
34
+
35
+ yaml_file = File.new(YAML_LOCATION, "w")
36
+ yaml_file.puts new_config.to_yaml
37
+ yaml_file.close
38
+ end
39
+
40
+ end
41
+ end
42
+
@@ -0,0 +1,42 @@
1
+ module Ptf
2
+ module Data
3
+ class DataFile
4
+
5
+ DIR_PATH = Ptf::Utilities::FileSystem.data_dir
6
+
7
+ # Initialize a new DataFile.
8
+ #
9
+ # @param metadata_file [Ptf::Data::MetadataFile] the associated metadata file.
10
+ #
11
+ # @raise [ArgumntError] if a MetadataFile is not given.
12
+ def initialize(metadata_file)
13
+ raise ArgumentError, "Metadata file not given." unless metadata_file.is_a? Ptf::Data::MetadataFile
14
+
15
+ @metadata_file = metadata_file
16
+ end
17
+
18
+ # Returns the full path to the data file.
19
+ #
20
+ # @return [String] the full path to the data file.
21
+ def data_file_path
22
+ File.join(DIR_PATH, @metadata_file.hash)
23
+ end
24
+
25
+ # Returns the header string for the file.
26
+ #
27
+ # @return [String] the header string for the data file.
28
+ def header_string
29
+ "##{@metadata_file.group.abbreviation}--#{@metadata_file.id} #{@metadata_file.title}\n#Due: #{@metadata_file.due_date_str}\n#Estimate: #{@metadata_file.estimate}\n\n\n"
30
+ end
31
+
32
+ # Write the header string to the data file. Overwrites any data file for the same task.
33
+ def write_to_file
34
+ File.open(data_file_path, "w") do |f|
35
+ f.write header_string
36
+ end
37
+ end
38
+
39
+ end
40
+ end
41
+ end
42
+
@@ -0,0 +1,107 @@
1
+ module Ptf
2
+ module Data
3
+ class Group
4
+
5
+ def initialize(name, abbrev)
6
+ @name = name
7
+ @abbreviation = abbrev
8
+ end
9
+
10
+ def name
11
+ @name
12
+ end
13
+
14
+ def abbreviation
15
+ @abbreviation
16
+ end
17
+
18
+ def to_s
19
+ "#{name}:#{abbreviation}"
20
+ end
21
+
22
+ class << self
23
+ def from_name(group_name)
24
+ group_file = Ptf::Utilities::FileSystem.group_list_file
25
+ File.open(group_file, "r") do |f|
26
+ f.each_line do |l|
27
+ name, abbrev = l.gsub(/\s+/, "").split(":")
28
+
29
+ if group_name == name
30
+ return Group.new(name, abbrev)
31
+ end
32
+
33
+ end
34
+ end
35
+
36
+ nil
37
+ end
38
+
39
+ def from_abbrev(group_abbrev)
40
+ group_file = Ptf::Utilities::FileSystem.group_list_file
41
+ File.open(group_file, "r") do |f|
42
+ f.each_line do |l|
43
+ name, abbrev = l.gsub(/\s+/, "").split(":")
44
+
45
+ if group_abbrev == abbrev
46
+ return Group.new(name, abbrev)
47
+ end
48
+
49
+ end
50
+ end
51
+
52
+ nil
53
+ end
54
+
55
+ def get_group(group)
56
+ if(group.length <= 4 && group == group.upcase)
57
+ from_abbrev(group)
58
+ else
59
+ from_name(group)
60
+ end
61
+ end
62
+
63
+ def group_exist?(g)
64
+ group_file = Ptf::Utilities::FileSystem.group_list_file
65
+
66
+ File.open(group_file, "r") do |f|
67
+ f.each_line do |l|
68
+ name, abbrev = l.gsub(/\s+/, "").split(":")
69
+
70
+ if name == g || abbrev == g
71
+ return true
72
+ end
73
+
74
+ end
75
+ end
76
+
77
+ false
78
+ end
79
+
80
+ def all_groups
81
+ group_arr = []
82
+
83
+ File.open(Ptf::Utilities::FileSystem.group_list_file, "r") do |f|
84
+ f.each_line do |l|
85
+ name, abbrev = l.gsub(/\s+/, "").split(":")
86
+
87
+ group_arr.push (Ptf::Data::Group.new(name, abbrev))
88
+ end
89
+ end
90
+
91
+ group_arr
92
+ end
93
+
94
+ def default_group
95
+ config = Ptf::Config.get_config
96
+ default_group = config[:default_group]
97
+
98
+ name, abbrev = default_group.gsub(/\s+/, "").split(":")
99
+ Ptf::Data::Group.new(name, abbrev)
100
+ end
101
+
102
+ end
103
+
104
+ end
105
+ end
106
+ end
107
+
@@ -0,0 +1,237 @@
1
+ module Ptf
2
+ module Data
3
+ # Represents a single Metadata file.
4
+ #
5
+ # @author Austin Blatt
6
+ # @since 0.1.0
7
+ class MetadataFile
8
+
9
+ # Create a MetadataFile object from the given input.
10
+ #
11
+ # @param title [String] the task's title.
12
+ # @param group [Ptf::Data::Group] the Group the task is associated with.
13
+ # @param due_date [DateTime, nil] the due date given for the task.
14
+ # @param estimate [Numeric, nil] the estimated time to complete the task.
15
+ # @param id [Integer] the unique ID number for this task.
16
+ #
17
+ # @return [Ptf::Data::MetadataFile] the metadata file for the task.
18
+ def self.create_from_input(title, group, due_date, estimate, id)
19
+ data = {
20
+ :title => title,
21
+ :group => group,
22
+ :due_date => due_date,
23
+ :estimate => estimate,
24
+ :id => id,
25
+ :created_at => DateTime.now,
26
+ :completed_at => nil
27
+ }
28
+
29
+ # Get the filepath for the infofile
30
+ open_dir = Ptf::Utilities::FileSystem.metadata_open_dir
31
+ group_dir = File.join(open_dir, group.name)
32
+ filepath = File.join(group_dir, id.to_s)
33
+
34
+ self.new(filepath, data)
35
+ end
36
+
37
+ # Create a MetadataFile object from the given filepath.
38
+ #
39
+ # @param filepath [String] the full file path to the metadata file.
40
+ #
41
+ # @raise [ArgumentError] if the given filepath does not exist.
42
+ # @raise [ArgumentError] if the given file is not a metadata file.
43
+ #
44
+ # @return [Ptf::Data::MetadataFile] the object representation of the metadata file.
45
+ def self.create_from_file(filepath)
46
+ raise ArgumentError, "File #{filepath} does not exist." unless Ptf::Utilities::FileSystem.file_exist?(filepath)
47
+
48
+ data = {}
49
+ # parse file
50
+ File.open(filepath, "r") do |f|
51
+ f.each_line do |l|
52
+ key, val = l.gsub(/\s+/, "").split(":")
53
+
54
+ if key.nil? || !(key.is_a? String)
55
+ raise ArgumentError, "Error parsing file."
56
+ end
57
+
58
+ # Convert values to appropriate types
59
+ case key
60
+ when "group"
61
+ val = Ptf::Data::Group.from_name(val)
62
+ when "due_date", "created_at", "completed_at"
63
+ val = (val.nil? ? val : Ptf::Utilities::Date.str_to_datetime(val))
64
+ when "id"
65
+ val = val.to_i
66
+ when "estimate"
67
+ val = (val.nil? ? nil : val.to_i)
68
+ end
69
+
70
+ data[key.to_sym] = val
71
+ end
72
+ end
73
+
74
+ return self.new(filepath, data)
75
+ end
76
+
77
+ # Initialize a new metadata file.
78
+ #
79
+ # @param filepath [String] the full path to the file.
80
+ # @param data [Hash] the data in the file.
81
+ # @option data [String] :title the task's title.
82
+ # @option data [Ptf::Data::Group] :group the Group the task is associated with.
83
+ # @option data [DateTime, nil] :due_date the due date given for the task.
84
+ # @option data [Integer, nil] :estimate the estimated time to complete the task.
85
+ # @option data [Integer] :id the unique ID number for this task.
86
+ # @option data [DateTime] :created_at the time the task was created.
87
+ # @option data [DateTime, nil] :completed_at the time the task was completed (nil if it has not been completed).
88
+ # @option data [String] :hash the file hash (the filepath of the data file).
89
+ def initialize(filepath, data)
90
+ @filepath = filepath
91
+ @data = data
92
+ end
93
+
94
+ # Add content to the metadata file.
95
+ #
96
+ # @param key [Symbol] the key for the new content.
97
+ # @param val [Object] the value for the new content.
98
+ #
99
+ # @raise [ArgumentError] if the key is not a symbol.
100
+ def add_content(key, val)
101
+ raise ArgumentError, "The key #{key} is not a symbol." unless key.is_a? Symbol
102
+
103
+ @data[key] = val
104
+ end
105
+
106
+ # Returns the title of the task.
107
+ #
108
+ # @return [String] the title of the task.
109
+ def title
110
+ @data[:title]
111
+ end
112
+
113
+ # Returns the group the task is associated with.
114
+ #
115
+ # @return [Ptf::Data::Group] the group the task is associated with.
116
+ def group
117
+ @data[:group]
118
+ end
119
+
120
+ # Returns the due date of the task.
121
+ #
122
+ # @return [DateTime, nil] the due date of the task or nil if no due date exists.
123
+ def due_date
124
+ @data[:due_date]
125
+ end
126
+
127
+ # Returns the due date of the task as a String.
128
+ #
129
+ # @return [String] the due date of the task as a string.
130
+ def due_date_str
131
+ return "" if due_date.nil?
132
+
133
+ Ptf::Utilities::Date.datetime_to_str(due_date)
134
+ end
135
+
136
+ # Returns the due date of the task for the ptf list command.
137
+ #
138
+ # @return [String] the due date in the form 'Mon Dec 19 - 11AM -' or 'Mon Dec 9 - 2PM -'.
139
+ def due_date_list_format
140
+ return "" if due_date.nil?
141
+
142
+ due_date.strftime("%a %b %_d - %l%p -")
143
+ end
144
+
145
+ # Returns the estimated time to complete the task.
146
+ #
147
+ # @return [Numeric] the estimated time to compelte the task.
148
+ def estimate
149
+ @data[:estimate]
150
+ end
151
+
152
+ # Returns the date and time that the task was created at.
153
+ #
154
+ # @return [DateTime] the date and time that the task was created at.
155
+ def created_at
156
+ @data[:created_at]
157
+ end
158
+
159
+ # Returns the date and time that the task was created at as a String.
160
+ #
161
+ # @return [String] the date and time represented as a string.
162
+ def created_at_str
163
+ Ptf::Utilities::Date.datetime_to_str(@data[:created_at])
164
+ end
165
+
166
+ # Return the id of the task.
167
+ #
168
+ # @return [Integer] the ID number of the task.
169
+ def id
170
+ @data[:id]
171
+ end
172
+
173
+ # Return the hash of the task.
174
+ #
175
+ # @return [String] the hash of the metadata file (the name of the data file).
176
+ def hash
177
+ @data[:hash]
178
+ end
179
+
180
+ # Completes the task.
181
+ #
182
+ # Fills in the completed_at field with the current DateTime.
183
+ # Removes the metadata file from the in progress directory and writes it to the completed directory.
184
+ def complete_now
185
+ @data[:completed_at] = DateTime.now
186
+
187
+ `rm #{@filepath}`
188
+ @filepath = File.join(File.join(Ptf::Utilities::FileSystem.metadata_closed_dir, group.name), id.to_s)
189
+
190
+ write_to_file
191
+ end
192
+
193
+ def reopen
194
+ `rm #{@filepath}`
195
+ @filepath = File.join(File.join(Ptf::Utilities::FileSystem.metadata_open_dir, group.name), id.to_s)
196
+
197
+ write_to_file
198
+ end
199
+
200
+ def key_val_to_str(key, val)
201
+ str_val = val
202
+ case key
203
+ when :group
204
+ str_val = val.name
205
+ when :due_date, :created_at, :completed_at
206
+ str_val = (val.nil? ? "" : Ptf::Utilities::Date.datetime_to_str(val))
207
+ end
208
+
209
+ "#{key}:#{str_val}"
210
+ end
211
+
212
+ # Returns the data in the file as a String.
213
+ #
214
+ # @return [String] the entire Metadata file as a String.
215
+ def file_string
216
+ return_str = ""
217
+ @data.each do |key, val|
218
+ return_str += "#{key_val_to_str(key, val)}\n"
219
+ end
220
+
221
+ return_str
222
+ end
223
+
224
+ # Writes the metadata file. Overwrites any previous metadata file for the same task.
225
+ def write_to_file
226
+ file = File.new(@filepath, "w")
227
+
228
+ @data.each do |key, val|
229
+ file.puts "#{key_val_to_str(key, val)}"
230
+ end
231
+ file.close
232
+ end
233
+
234
+ end
235
+ end
236
+ end
237
+
data/lib/ptf/data.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'ptf/data/group'
2
+ require 'ptf/data/metadata_file'
3
+ require 'ptf/data/data_file'
4
+
5
+ module Ptf
6
+ module Data
7
+ end
8
+ end
9
+
@@ -0,0 +1,63 @@
1
+ module Ptf
2
+ module Utilities
3
+ module Date
4
+ class << self
5
+
6
+ def datetime_to_str(datetime)
7
+ raise ArgumentError, "DateTime expected. Received #{datetime.class} instead." unless datetime.is_a? DateTime
8
+
9
+ datetime.strftime('%Y%m%d%H%M%S')
10
+ end
11
+
12
+ def create_datetime_from_str(str)
13
+
14
+ begin
15
+ dt = str_to_datetime(str)
16
+ return dt
17
+ rescue
18
+ end
19
+
20
+ time_regex = /([0-2]?\d):([0-5]\d):?([0-5]\d)?/
21
+ # Check if given time 12:21(:21)
22
+ match = time_regex.match(str).to_a
23
+
24
+ if !match.nil? && (match.length == 3 || match.length == 4)
25
+ now = DateTime.now
26
+ return DateTime.new(now.year, now.month, now.day, match[1].to_i, match[2].to_i, 0, now.zone)
27
+ end
28
+
29
+ # Check if given date 12/27(/[15|2015])
30
+ date_regex = /^([01]?\d)\/([0-3]?\d)\/?(\d\d\d\d|\d\d)?$/
31
+ match = date_regex.match(str)
32
+
33
+ if !match.nil? && (match.length == 3 || match.length == 4)
34
+ now = DateTime.now
35
+
36
+ if !match[3].nil? && match[3].length == 2
37
+ year = 2000 + match[3].to_i
38
+ elsif !match[3].nil?
39
+ year = match[3].to_i
40
+ end
41
+
42
+ return DateTime.new((match[3].nil? ? now.year : year), match[1].to_i, match[2].to_i, 12, 0, 0, now.zone)
43
+ end
44
+
45
+ nil
46
+ end
47
+
48
+ def str_to_datetime(str)
49
+ date_regex = /^(\d\d\d\d)([01]\d)([0-3]\d)([0-2]\d)([0-5]\d)([0-5]\d)?$/
50
+
51
+ regex_match = date_regex.match(str)
52
+
53
+ raise ArgumentError, "Improperly formatted datetime string, #{str}." unless regex_match.to_a.length == 7
54
+
55
+ _, year, month, day, hour, minute, second = regex_match.to_a
56
+
57
+ DateTime.new(year.to_i, month.to_i, day.to_i, hour.to_i, minute.to_i, second.to_i)
58
+ end
59
+
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,83 @@
1
+ module Ptf
2
+ module Utilities
3
+ module FileSystem
4
+ class << self
5
+
6
+ def file_exist?(filepath)
7
+ File.file?(filepath)
8
+ end
9
+
10
+ def config
11
+ @config = Ptf::Config.get_config if @config.nil?
12
+
13
+ @config
14
+ end
15
+
16
+ def file_permission
17
+ @config[:file_permission]
18
+ end
19
+
20
+ def base_dir
21
+ config[:base_dir]
22
+ end
23
+
24
+ def id_counter_file
25
+ File.join(base_dir, config[:task_counter_file])
26
+ end
27
+
28
+ def id_counter_starting_content
29
+ config[:id_counter_start]
30
+ end
31
+
32
+ def group_list_file
33
+ File.join(base_dir, config[:group_list_file])
34
+ end
35
+
36
+ def metadata_dir
37
+ File.join(base_dir, config[:task_metadata_dir])
38
+ end
39
+
40
+ def metadata_open_dir
41
+ File.join(metadata_dir, config[:in_progress_dir])
42
+ end
43
+
44
+ def metadata_closed_dir
45
+ File.join(metadata_dir, config[:completed_dir])
46
+ end
47
+
48
+ def data_dir
49
+ File.join(base_dir, config[:task_data_dir])
50
+ end
51
+
52
+ def tmp_dir
53
+ File.join(base_dir, config[:tmp_dir])
54
+ end
55
+
56
+ def id_exist?(id)
57
+ next_id = File.read(id_counter_file).to_i
58
+
59
+ (id < next_id && id > 0)
60
+ end
61
+
62
+ def find_file(id, open = true)
63
+ raise ArgumentError, "No task with id #{id} exists." unless id_exist?(id)
64
+
65
+ search_dir = (open ? metadata_open_dir : metadata_closed_dir)
66
+ groups = Ptf::Data::Group.all_groups
67
+
68
+ groups.each do |g|
69
+ dir = File.join(search_dir, g.name)
70
+ possible_id_file = File.join(dir, id.to_s)
71
+
72
+ if file_exist?(possible_id_file)
73
+ return possible_id_file
74
+ end
75
+ end
76
+
77
+ nil
78
+ end
79
+
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,28 @@
1
+ require 'ptf/utilities/date'
2
+ require 'ptf/utilities/file_system'
3
+
4
+ module Ptf
5
+ module Utilities
6
+ class << self
7
+
8
+ def datetime_to_str(datetime)
9
+ raise ArgumentError, "DateTime expected. Received #{datetime.class} instead." unless datetime.is_a? DateTime
10
+
11
+ datetime.strftime('%Y%m%d%H%M%S')
12
+ end
13
+
14
+ def str_to_datetime(str)
15
+ date_regex = /^([0-9][0-9][0-9][0-9])([01][0-9])([0-3][0-9])?([0-2][0-9])([0-5][0-9])([0-5][0-9])$/
16
+
17
+ regex_match = date_regex.match(str)
18
+
19
+ raise ArgumentError, "Improperly formatted datetime string." unless regex_match.to_a.length == 7
20
+
21
+ _, year, month, day, hour, minute, second = regex_match.to_a
22
+
23
+ DateTime.new(year.to_i, month.to_i, day.to_i, hour.to_i, minute.to_i, second.to_i)
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,3 @@
1
+ module Ptf
2
+ VERSION = "0.1.0"
3
+ end