syc-task 0.0.7 → 0.1.15

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+