syc-task 0.0.7 → 0.1.15

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.
@@ -1,4 +1,5 @@
1
1
  require_relative 'schedule.rb'
2
+ require_relative 'environment.rb'
2
3
 
3
4
  module Syctask
4
5
 
@@ -37,7 +38,7 @@ module Syctask
37
38
  ASSIGNMENT_PATTERN = /([a-zA-Z]):(\d+(?:,\d+|\d+;)*)/
38
39
 
39
40
  # Working directory
40
- WORK_DIR = File.expand_path("~/.tasks")
41
+ WORK_DIR = Syctask::SYC_DIR #File.expand_path("~/.tasks")
41
42
 
42
43
  # Creates a new TaskScheduler.
43
44
  def initialize
@@ -54,6 +55,7 @@ module Syctask
54
55
  unless sequential?(@work_time)
55
56
  raise Exception, "Begin time has to be before end time"
56
57
  end
58
+ Syctask::log_work_time("work", @work_time)
57
59
  end
58
60
 
59
61
  # Set the busy times. Raises an exception if one begin time is after start
@@ -66,12 +68,14 @@ module Syctask
66
68
  raise Exception, "Begin time has to be before end time"
67
69
  end
68
70
  end
71
+ Syctask::log_meetings("meeting", @busy_time, @meetings)
69
72
  end
70
73
 
71
74
  # Sets the titles of the meetings (busy times)
72
75
  # Invokation: set_meeting_titles("title1,title2,title3")
73
76
  def set_meeting_titles(titles)
74
77
  @meetings = titles.split(",") if titles
78
+ Syctask::log_meetings("meeting", @busy_time, @meetings)
75
79
  end
76
80
 
77
81
  # Sets the tasks for scheduling
@@ -1,4 +1,6 @@
1
+ require 'csv'
1
2
  require 'yaml'
3
+ require_relative 'environment.rb'
2
4
 
3
5
  # Syctask provides functions for managing tasks in a task list
4
6
  module Syctask
@@ -8,7 +10,7 @@ module Syctask
8
10
  class TaskService
9
11
  # Default directory where the tasks are saved to if no directory is
10
12
  # specified
11
- DEFAULT_DIR = File.expand_path("~/.tasks")
13
+ DEFAULT_DIR = Syctask::WORK_DIR #File.expand_path("~/.tasks")
12
14
 
13
15
  # Creates a new task in the specified directory, with the specified options
14
16
  # and the specified title. If the directory doesn't exist it is created.
@@ -22,15 +24,18 @@ module Syctask
22
24
  # * tags - can be used to searching tasks that belong to a certain category
23
25
  def create(dir, options, title)
24
26
  create_dir(dir)
25
- task = Task.new(options, title, create_id(dir))
27
+ task = Task.new(options, title, next_id(dir))
26
28
  save(dir, task)
29
+ Syctask::log_task("create", task)
27
30
  task.id
28
31
  end
29
32
 
30
33
  # Reads the task with given ID id located in given directory dir. If task
31
34
  # does not exist nil is returned otherwise the task is returned
32
35
  def read(dir, id)
33
- task = nil
36
+ task = read_by_id(id)
37
+ return task unless task.nil?
38
+ #task = nil
34
39
  Dir.glob("#{dir}/*.task").each do |file|
35
40
  task = YAML.load_file(file) if File.file? file
36
41
  if not task.nil? and task.class == Syctask::Task and task.id == id.to_i
@@ -40,6 +45,16 @@ module Syctask
40
45
  nil
41
46
  end
42
47
 
48
+ # Reads the task identified by ID. If no task with ID is found nil is
49
+ # returned otherwise the task
50
+ def read_by_id(id)
51
+ return nil unless File.exists? Syctask::IDS
52
+ ids = File.read(Syctask::IDS)
53
+ entry = ids.scan(/(^#{id}),(.*\n)/)[0]
54
+ return YAML.load_file(entry[1].chomp) if entry
55
+ return nil
56
+ end
57
+
43
58
  # Finds all tasks that match the given filter. The filter can be provided
44
59
  # for :id, :title, :description, :follow_up, :due, :tags and :prio.
45
60
  # id can be eather a selection of IDs ID1,ID2,ID3 or a comparison <|=|>ID.
@@ -52,7 +67,7 @@ module Syctask
52
67
  # open tasks
53
68
  def find(dir, filter={}, all=true)
54
69
  tasks = []
55
- Dir.glob("#{dir}/*").sort.each do |file|
70
+ Dir.glob("#{dir}/*.task").sort.each do |file|
56
71
  begin
57
72
  File.file?(file) ? task = YAML.load_file(file) : next
58
73
  rescue Exception => e
@@ -78,12 +93,16 @@ module Syctask
78
93
  # new value. If note and tags are provided these are added to the existing
79
94
  # values.
80
95
  def update(dir, id, options)
81
- task_file = Dir.glob("#{dir}/#{id}.task")[0]
82
- task = YAML.load_file(task_file) if task_file
96
+ task = read_by_id(id)
97
+ unless task
98
+ task_file = Dir.glob("#{dir}/#{id}.task")[0]
99
+ task = YAML.load_file(task_file) if task_file
100
+ end
83
101
  updated = false
84
102
  if task
85
103
  task.update(options)
86
- save(dir, task)
104
+ save(task.dir, task)
105
+ Syctask::log_task("update", task)
87
106
  updated = true
88
107
  end
89
108
  updated
@@ -94,7 +113,7 @@ module Syctask
94
113
  # returned
95
114
  def delete(dir, filter)
96
115
  deleted = 0
97
- Dir.glob("#{dir}/*").each do |file|
116
+ Dir.glob("#{dir}/*.task").each do |file|
98
117
  begin
99
118
  File.file?(file) ? task = YAML.load_file(file) : next
100
119
  rescue Exception => e
@@ -103,6 +122,9 @@ module Syctask
103
122
  next unless not task.nil? and task.class == Syctask::Task
104
123
  if task.matches?(filter)
105
124
  deleted += File.delete(file)
125
+ ids = File.read(Syctask::IDS)
126
+ File.write(Syctask::IDS, ids.gsub("#{task.id},#{file}",""))
127
+ Syctask::log_task("delete", task)
106
128
  end
107
129
  end
108
130
  deleted
@@ -112,7 +134,11 @@ module Syctask
112
134
  # ~/.tasks will be set.
113
135
  def save(dir, task)
114
136
  task.dir = dir.nil? ? DEFAULT_DIR : File.expand_path(dir)
115
- File.open("#{task.dir}/#{task.id}.task", 'w') {|f| YAML.dump(task, f)}
137
+ task_file = "#{task.dir}/#{task.id}.task"
138
+ unless File.exists? task_file
139
+ File.open(Syctask::IDS, 'a') {|f| f.puts "#{task.id},#{task_file}"}
140
+ end
141
+ File.open(task_file, 'w') {|f| YAML.dump(task, f)}
116
142
  end
117
143
 
118
144
  private
@@ -122,18 +148,32 @@ module Syctask
122
148
  FileUtils.mkdir_p dir unless File.exists? dir
123
149
  end
124
150
 
125
- # Creates the task's ID based on the tasks available in the task directory.
126
- # The task's file name is in the form ID.task. create_id determines
127
- # the biggest number and adds one to create the task's ID.
128
- def create_id(dir)
129
- tasks = Dir.glob("#{dir}/*")
151
+ # Checks for the next possible task's ID based on the tasks available in
152
+ # the task directory. The task's file name is in the form ID.task.
153
+ # local_ID seeks for the biggest number and adds one to determine the
154
+ # next valid task ID.
155
+ def local_id(dir)
156
+ tasks = Dir.glob("#{dir}/*.task")
130
157
  ids = []
131
158
  tasks.each do |task|
132
159
  id = File.basename(task).scan(/^\d+(?=\.task)/)[0]
133
160
  ids << id.to_i if id
134
161
  end
135
162
  ids.empty? ? 1 : ids.sort[ids.size-1] + 1
136
- end
163
+ end
164
+
165
+ # Retrieves a new unique ID for a task. If next id is less than the next
166
+ # ID in the directory a warning is printed and the higher ID is taken as
167
+ # the next ID.
168
+ def next_id(dir)
169
+ local = local_id(dir)
170
+ id = File.readlines(Syctask::ID)[0] if File.exists? Syctask::ID
171
+ id = id ? id.to_i + 1 : 1
172
+ STDERR.puts "Warning: global id < local id" if id < local
173
+ id = [id, local].max
174
+ File.open(Syctask::ID, 'w') {|f| f.puts id}
175
+ id
176
+ end
137
177
 
138
178
  end
139
179
  end
@@ -1,5 +1,8 @@
1
+ require 'yaml'
2
+ require 'fileutils'
1
3
  require_relative 'environment.rb'
2
4
  require_relative 'task_service.rb'
5
+ #require_relative '../sycutil/console_timer.rb'
3
6
 
4
7
  module Syctask
5
8
 
@@ -14,9 +17,9 @@ module Syctask
14
17
  class TaskTracker
15
18
 
16
19
  # File name of the file where the tracked files are saved to
17
- TRACKED_TASKS_FILE = Syctask::WORK_DIR + '/' + 'tracked_tasks'
20
+ TRACKED_TASKS_FILE = Syctask::TRACKED_TASK #Syctask::WORK_DIR + '/' + 'tracked_tasks'
18
21
  # File name of the task log file
19
- TASK_LOG_FILE = Syctask::WORK_DIR + '/' + 'tasks.log'
22
+ TASK_LOG_FILE = Syctask::TASKS_LOG #Syctask::WORK_DIR + '/' + 'tasks.log'
20
23
 
21
24
  # Creates a new TaskTracker
22
25
  def initialize
@@ -26,19 +29,22 @@ module Syctask
26
29
 
27
30
  # When a task is started it is saved with the start time. If a task is
28
31
  # already tracked it is stopped (see #stop). A started task will print
29
- # every 15 minutes a message to the console.
32
+ # every second a message to the console if the show parameter is true.
30
33
  # start returns
31
34
  # * [false, nil ] if the task is already tracked
32
35
  # * [true, nil ] if the task is started and no task was running.
33
36
  # * [true, task] if task is started and the previously running task stopped
34
- def start(task)
37
+ def start(task, show=true)
38
+ raise ArgumentError, "Error: Task without directory.\n"+
39
+ "--> Update task with syctask -t <dir> update "+
40
+ "#{task.id}" unless task.dir
35
41
  index = @tasks.find_index(task)
36
42
  return [false, nil] if not index.nil? and index == 0
37
43
 
38
44
  stopped_task = stop
39
45
  track = Track.new(task)
40
46
 
41
- track.start
47
+ track.start(show)
42
48
  log_task(:start, track)
43
49
 
44
50
  @tracks.insert(0,track)
@@ -59,18 +65,15 @@ module Syctask
59
65
  return nil unless @tasks[0]
60
66
 
61
67
  task = @tasks[0]
62
- if task.lead_time
63
- task.lead_time += @tracks[0].stop
64
- else
65
- task.lead_time = @tracks[0].stop
66
- end
67
-
68
+ task.update_lead_time(@tracks[0].stop)
68
69
  @service.save(task.dir, task)
70
+
69
71
  log_task(:stop, @tracks[0])
70
72
 
71
73
  @tracks.delete_at(0)
72
74
  @tasks.delete_at(0)
73
75
  save_tracks
76
+
74
77
  task
75
78
  end
76
79
 
@@ -99,7 +102,9 @@ module Syctask
99
102
  else
100
103
  @tracks ||= YAML.load_file(TRACKED_TASKS_FILE)
101
104
  @tasks = []
102
- @tracks.each { |track| @tasks << @service.read(track.dir, track.id) }
105
+ if @tracks
106
+ @tracks.each { |track| @tasks << @service.read(track.dir, track.id) }
107
+ end
103
108
  end
104
109
  end
105
110
 
@@ -108,7 +113,7 @@ module Syctask
108
113
  FileUtils.mkdir_r Syctask::WORK_DIR unless File.exists? Syctask::WORK_DIR
109
114
  File.open(TASK_LOG_FILE, 'a') do |file|
110
115
  log_entry = "#{type.to_s};"
111
- log_entry += "#{track.id}-#{track.dir};"
116
+ log_entry += "#{track.id};#{track.dir};"
112
117
  log_entry += "#{track.title};"
113
118
  log_entry += "#{track.started};"
114
119
  log_entry += "#{track.stopped}"
@@ -119,7 +124,7 @@ module Syctask
119
124
  end
120
125
 
121
126
  # A Track holds a task and stops the time the task is processed. The Track
122
- # will print every 5 minutes the elapsed time and the time left to the
127
+ # will print every second the elapsed time and the time left to the
123
128
  # specified Task#duration.
124
129
  class Track
125
130
 
@@ -139,17 +144,22 @@ module Syctask
139
144
  @dir = task.dir
140
145
  @id = task.id
141
146
  @title = task.title
147
+ @duration = task.remaining.to_i
148
+ @semaphore = "#{Syctask::SYC_DIR}/#{@id}.track"
142
149
  end
143
150
 
144
- # Starts the tracking and a timer that will print to STDOUT every 5 minutes
151
+ # Starts the tracking and a timer that will print to STDOUT every second
145
152
  # the elapsed time and the time left until Task#duration
146
- def start
153
+ def start(show)
147
154
  @started ||= Time.now
148
- # start a timer that prints title and elapsed time every 5 minutes
155
+ # start a timer that prints id and elapsed time
156
+ FileUtils.touch @semaphore
157
+ system "console_timer #{@duration} #{@id} #{@semaphore} &" if show
149
158
  end
150
159
 
151
160
  # Stops the task tracking and returns the lead time of the task
152
161
  def stop
162
+ FileUtils.rm @semaphore if @semaphore and File.exists? @semaphore
153
163
  @stopped ||= Time.now
154
164
  @stopped - @started
155
165
  end
data/lib/syctask/times.rb CHANGED
@@ -20,6 +20,35 @@ module Syctask
20
20
  @m > 0 ? @h+1 : @h
21
21
  end
22
22
 
23
+ # Returns a Time object with the current date and the hour and minute of
24
+ # this Times object
25
+ def time
26
+ now = Time.now
27
+ Time.local(now.year,now.mon,now.day,@h,@m,0)
28
+ end
29
+
30
+ # Calculates the difference between this time and the provided time. If no
31
+ # time is given the current time is used.
32
+ # Example:
33
+ # This time = 9:35
34
+ # New time = 10:20
35
+ # diff(time) = 0:45
36
+ # Will return [hour,min] in the example [0,45]
37
+ def diff(time = Time.now)
38
+ diff_minutes = (time.hour - @h) * 60 + (time.min - @m)
39
+ signum = diff_minutes == 0 ? 0 : diff_minutes / diff_minutes.abs
40
+ diff_h = diff_minutes.abs / 60
41
+ diff_m = diff_minutes.abs % 60
42
+ if signum < 0
43
+ if diff_h > 0
44
+ [signum * diff_h, diff_m]
45
+ else
46
+ [diff_h, signum * diff_m]
47
+ end
48
+ else
49
+ [diff_h, diff_m]
50
+ end
51
+ end
23
52
  end
24
53
 
25
54
  end
@@ -1,5 +1,5 @@
1
1
  # Syctask provides functions for managing tasks in a task list
2
2
  module Syctask
3
3
  #Holds the version number of syctask
4
- VERSION = '0.0.7'
4
+ VERSION = '0.1.15'
5
5
  end
@@ -1,3 +1,5 @@
1
+ require 'time'
2
+
1
3
  # Functions for time operations
2
4
  module Syctime
3
5
 
@@ -6,13 +8,13 @@ module Syctime
6
8
  def seconds_to_time(seconds)
7
9
  seconds = seconds.round
8
10
  duration = []
9
- duration << seconds % 60 # seconds
10
- duration << seconds / 60 % 60 # minutes
11
- duration << seconds / 60 / 60 % 60 # hours
12
- duration << seconds / 60 / 60 / 60 % 24 # days
13
- duration << seconds / 60 / 60 / 60 / 24 % 7 # weeks
14
- duration << seconds / 60 / 60 / 60 / 24 / 7 % 4 # months
15
- duration << seconds / 60 / 60 / 60 / 24 / 7 / 4 % 12 # years
11
+ duration << seconds % 60 # seconds
12
+ duration << seconds / 60 % 60 # minutes
13
+ duration << seconds / 60 / 60 % 24 # hours
14
+ duration << seconds / 60 / 60 / 24 % 7 # days
15
+ duration << seconds / 60 / 60 / 24 / 7 % 4 # weeks
16
+ duration << seconds / 60 / 60 / 24 / 7 / 4 % 12 # months
17
+ duration << seconds / 60 / 60 / 24 / 7 / 4 / 12 # years
16
18
  end
17
19
 
18
20
  # Translates seconds into a time string like 1 year 2 weeks 5 days 10 minutes.
@@ -27,4 +29,41 @@ module Syctime
27
29
  time_string
28
30
  end
29
31
 
32
+ # Creates a time string separating hours, minutes and seconds with the
33
+ # provided separator like 12:50:33
34
+ def separated_time_string(seconds, separator)
35
+ secs = seconds % 60
36
+ mins = seconds / 60 % 60
37
+ hours = seconds / 60 / 60
38
+ time_string = sprintf("%02d#{separator}%02d#{separator}%02d", hours, mins, secs)
39
+ end
40
+
41
+ # Translates a time in the ISO 8601 schema to a time object.
42
+ # 2013-04-09 21:45 -200
43
+ def time_for_string(time)
44
+ time = time.scan(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/)[0].sub(' ','T')
45
+ Time.xmlschema(time)
46
+ end
47
+
48
+ # Tests whether the date is between from and to. Returns true then otherwise
49
+ # false. Time, from and to are Time objects as retrieved from Time.now or
50
+ # Time.local(2013,"apr",13,10,50,0). Alternatively time strings can be
51
+ # provided in the form of "2013-04-13".
52
+ def date_between?(date, from, to)
53
+ date = date.strftime("%Y-%m-%d") if date.class == Time
54
+ from = from.strftime("%Y-%m-%d") if from.class == Time
55
+ to = to.strftime("%Y-%m-%d") if to.class == Time
56
+ time_pattern = /\d{4}-\d{2}-\d{2}/
57
+ raise ArgumentError if date.scan(time_pattern).empty?
58
+ raise ArgumentError if from.scan(time_pattern).empty?
59
+ raise ArgumentError if to.scan(time_pattern).empty?
60
+ date >= from && date <= to
61
+ end
62
+
63
+ # Checks whether the time is between from and to. Returns true then otherwise
64
+ # false. time, from and to have to be Time objects.
65
+ def time_between?(time, from, to)
66
+ time >= from && time <= to
67
+ end
68
+
30
69
  end
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'yaml'
4
+ require 'rainbow'
5
+
6
+ # ConsoleTimer prints a task and the lead time at the upper right corner of the
7
+ # schreen. Invokation example:
8
+ # * Create a semaphore like id.track
9
+ # * Console time in a new ruby process
10
+ # semaphore = File.expand_path("~/.syc/syctask/#{id}.track"
11
+ # FileUtils.touch semaphore
12
+ # system "ruby lib/sycutil/console_timer.rb 60 10 semaphore"
13
+ # This will start the ConsoleTimer with a lead time of 1 minute for task 10.
14
+ # To stop the timer the semaphore has to be deleted
15
+ # FileUtils.rm semaphore
16
+ class ConsoleTimer
17
+
18
+ # Create a new ConsoleTimer with the time to count down, the task's ID and a
19
+ # semaphore. The semaphore is a file named id.track where id is equal to the
20
+ # provided id. The semaphore is checked for existence. If the semaphore is
21
+ # deleted than ConsoleTimer is stopped.
22
+ def initialize(time, id, semaphore)
23
+ @time = time.to_i
24
+ @id = id
25
+ @start = Time.now
26
+ @semaphore = semaphore
27
+ end
28
+
29
+ # Starts the timer. The timer is run as long the semaphore is available
30
+ def start
31
+ track = true
32
+ while track
33
+ sleep 1
34
+ output
35
+ track = File.exists? @semaphore
36
+ end
37
+ exit 0
38
+ end
39
+
40
+ # Prints the id and the lead time of the currently tracked task. As long as
41
+ # the provided time is greater than 0 the time is printed in green, otherwise
42
+ # red
43
+ def output
44
+ color = :green
45
+ difference = @time - (Time.now - @start).round
46
+ if difference < 0
47
+ difference = difference.abs
48
+ color = :red
49
+ end
50
+ seconds = difference % 60
51
+ minutes = difference / 60 % 60
52
+ hours = difference / 60 / 60 % 60
53
+ count_down = sprintf("%d: %02d:%02d:%02d", @id, hours, minutes, seconds)
54
+ size = count_down.size
55
+ count_down = count_down.color(color)
56
+ command = "tput sc;"+
57
+ "tput cup 0 $(($(tput cols) - #{size}));"+
58
+ "echo #{count_down};tput rc"
59
+ system command
60
+ end
61
+
62
+ end
63
+
64
+ # Expects to receive parameters duration, id and semaphore
65
+ if ARGV.size == 3
66
+ duration = ARGV.shift
67
+ id = ARGV.shift
68
+ semaphore = ARGV.shift
69
+ timer = ConsoleTimer.new(duration, id, semaphore)
70
+ timer.start
71
+ else
72
+ exit -1
73
+ end
74
+
75
+