wlog 1.0.0 → 1.0.5

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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/README.md +57 -9
  4. data/Rakefile +24 -0
  5. data/bin/wlog +10 -0
  6. data/lib/wlog/commands/archive_finished_issues.rb +20 -0
  7. data/lib/wlog/commands/archive_issues.rb +22 -0
  8. data/lib/wlog/commands/concat_description.rb +8 -2
  9. data/lib/wlog/commands/delete_issue.rb +31 -0
  10. data/lib/wlog/commands/innit_db.rb +0 -2
  11. data/lib/wlog/commands/make_csv.rb +7 -1
  12. data/lib/wlog/commands/new_entry.rb +7 -2
  13. data/lib/wlog/commands/replace_pattern.rb +4 -6
  14. data/lib/wlog/commands/taint_setup.rb +18 -0
  15. data/lib/wlog/db_registry.rb +3 -5
  16. data/lib/wlog/domain.rb +13 -0
  17. data/lib/wlog/domain/attachment.rb +25 -17
  18. data/lib/wlog/domain/helpers.rb +4 -0
  19. data/lib/wlog/domain/issue.rb +77 -23
  20. data/lib/wlog/domain/key_value.rb +21 -17
  21. data/lib/wlog/domain/log_entry.rb +35 -21
  22. data/lib/wlog/domain/session.rb +17 -0
  23. data/lib/wlog/domain/sql_modules/issue_sql.rb +13 -5
  24. data/lib/wlog/domain/static_configurations.rb +9 -0
  25. data/lib/wlog/domain/sys_config.rb +76 -6
  26. data/lib/wlog/domain/template_engine.rb +55 -0
  27. data/lib/wlog/domain/timelog_helper.rb +53 -0
  28. data/lib/wlog/sql/seq/2.sql +4 -0
  29. data/lib/wlog/{domain → tech}/ansi_colors.rb +7 -6
  30. data/lib/wlog/tech/uncolored_string.rb +14 -0
  31. data/lib/wlog/tech/wlog_string.rb +23 -0
  32. data/lib/wlog/ui/bootstrap.rb +18 -0
  33. data/lib/wlog/ui/cli_interface.rb +94 -34
  34. data/lib/wlog/ui/commands/create_issue.rb +8 -4
  35. data/lib/wlog/ui/issue_ui.rb +47 -41
  36. data/lib/wlog/ui/setup_wizard.rb +34 -0
  37. data/lib/wlog/version.rb +1 -1
  38. data/spec/domain/attachment_spec.rb +59 -0
  39. data/spec/domain/commands/concat_desc_spec.rb +51 -0
  40. data/spec/domain/commands/new_entry_spec.rb +41 -0
  41. data/spec/domain/commands/replace_pattern_spec.rb +46 -0
  42. data/spec/domain/issue_spec.rb +127 -0
  43. data/spec/domain/key_value_spec.rb +42 -0
  44. data/spec/domain/log_entry_spec.rb +85 -0
  45. data/spec/domain/sys_config_spec.rb +38 -0
  46. data/spec/make_db.rb +8 -0
  47. data/spec/old-databases/README.md +6 -0
  48. data/spec/old-databases/default +0 -0
  49. data/spec/tech/uncolored_string.rb +20 -0
  50. data/spec/tech/wlog_string_spec.rb +46 -0
  51. data/wlog.gemspec +5 -3
  52. metadata +44 -6
  53. data/spec/key_vale_spec.rb +0 -9
@@ -37,6 +37,10 @@ class Helpers
37
37
  FileUtils.mkdir_p DataDirectory
38
38
  end
39
39
  nil end
40
+
41
+ # Check if the application directory exists. If it does not, it's a first
42
+ # time system run.
43
+ def self.first_setup?; !File.exists? TaintFile end
40
44
  end
41
45
  end # module Wlog
42
46
 
@@ -1,6 +1,7 @@
1
1
  require 'wlog/db_registry'
2
2
  require 'wlog/domain/sql_modules/issue_sql'
3
3
  require 'wlog/domain/log_entry'
4
+ require 'wlog/domain/timelog_helper'
4
5
 
5
6
  module Wlog
6
7
  # This aggregates log entries. The total time spent on this issue is
@@ -9,66 +10,99 @@ module Wlog
9
10
  class Issue
10
11
  include IssueSql
11
12
 
12
- def initialize
13
+ def initialize(db_handle)
13
14
  @reported_date = Time.now
14
15
  @log_entries = Array.new
15
- @status = 0
16
+ @status = @seconds = 0
17
+ @db = db_handle
16
18
  end
17
19
 
18
- def self.find(id)
19
- issue = Issue.new
20
- ret = DbRegistry.instance.execute(SelectSql, id).first
21
- issue.quick_assign! ret
20
+ def self.find(db, id)
21
+ issue = Issue.new(db)
22
+ ret = db.execute(SelectSql, id).first
23
+ if ret.nil? || ret.empty?
24
+ issue = nil
25
+ else
26
+ issue.quick_assign! ret
27
+ end
22
28
  issue end
23
29
 
24
- def self.find_all
30
+ def self.find_all(db)
31
+ self.generic_find_all(db, SelectAllSql)
32
+ end
33
+
34
+ def self.find_all_finished(db)
35
+ self.generic_find_all(db, SelectFinishedSql)
36
+ end
37
+
38
+ def self.find_in_time_range(db, from, to)
25
39
  arr = Array.new
26
- DbRegistry.instance.execute(SelectAllSql).each do |row|
27
- issue = Issue.new
28
- issue.quick_assign! row
29
- arr.push issue
40
+ db.execute(SelectTimeRange, from, to).each do |row|
41
+ tmp = Issue.new(@db)
42
+ tmp.quick_assign!(row)
43
+ arr.push tmp
30
44
  end
31
45
  arr end
32
46
 
33
- def self.delete(id); DbRegistry.instance.execute(DeleteSql, id) end
47
+ def self.delete_by_id(db, id); db.execute(DeleteSql, id) end
34
48
 
49
+ # inserts the entry into the database if it has not been stored before.
35
50
  def insert
36
- DbRegistry.instance
37
- .execute(InsertSql, @description,
51
+ unless @id
52
+ @db.execute(InsertSql, @description,
38
53
  @reported_date.to_i, @due_date.to_i, @status)
54
+ @id = @db.last_row_from(TableName).first[0]
55
+ end
39
56
  end
40
57
 
41
- def delete; DbRegistry.instance.execute(DeleteSql, @id) end
58
+ def delete; @db.execute(DeleteSql, @id) end
42
59
 
43
60
  def update
44
- DbRegistry.instance
45
- .execute(UpdateSql, @description, @reported_date.to_i,
46
- @due_date.to_i, @status, @id)
61
+ @db.execute(UpdateSql, @description, @reported_date.to_i,
62
+ @due_date.to_i, @status, @seconds, @id)
47
63
  end
48
64
 
49
65
  # Add a log entry object to the issue
66
+ # TODO this does nothing / used for nothing yet
50
67
  def add_log_entry(le)
51
68
  @log_entries.push le
52
69
  end
53
70
 
54
71
  def quick_assign!(row)
55
- @id, @description, @reported_date, @due_date , @status =\
56
- row[0], row[1], Time.at(row[2]), Time.at(row[3]), row[4]
72
+ @id, @description, @reported_date, @due_date, @status, @seconds =\
73
+ row[0], row[1], Time.at(row[2]), Time.at(row[3]), row[4], row[5] || 0
57
74
  nil end
58
75
 
76
+ # Log the seconds into the issue
77
+ def log_time(seconds)
78
+ @seconds += seconds
79
+ update
80
+ end
81
+
59
82
  def to_s
60
83
  "+ Issue ##{@id}#{$/}"\
61
84
  " - Reported : #{@reported_date}#{$/}"\
62
85
  " - Due : #{@due_date}#{$/}"\
63
86
  " - Entries : #{@log_entries.count}#{$/}"\
64
- " - Status : "\
87
+ " - Status : #{Statuses[@status]}#{$/}"\
88
+ " - Time : #{TimelogHelper.time_to_s(@seconds)}#{$/}"\
65
89
  "#{$/}"\
66
- " - #{@description}"
90
+ " - #{@description}#{$/}"
67
91
  end
68
92
 
93
+ # Mark issue as started
69
94
  def mark_started!; @status = 0 end
95
+
96
+ # Mark the issue as working
70
97
  def mark_working!; @status = 1 end
98
+
99
+ # Mark the issue as finished
71
100
  def mark_finished!; @status = 2 end
101
+
102
+ # Archive the issue
103
+ def archive!; @status = 3 end
104
+
105
+ # Get the status as a string
72
106
  def status_s; Statuses[@status] end
73
107
 
74
108
  # [String] Description of the issue at hand
@@ -91,9 +125,29 @@ class Issue
91
125
  # 2 for finished)
92
126
  attr_accessor :status
93
127
 
128
+ # The seconds that you have wasted your life on in order to get something
129
+ # done
130
+ attr_accessor :seconds
131
+
132
+ # The database handle for this AR
133
+ attr_accessor :db
134
+
94
135
  private
95
136
 
96
- Statuses = {0 => "new", 1 => "started work", 2 => "finished"}
137
+ Statuses = {
138
+ 0 => "new", 1 => "started work",
139
+ 2 => "finished", 3 => "archived"}
140
+
141
+ private_class_method
142
+
143
+ def self.generic_find_all(db, sql)
144
+ arr = Array.new
145
+ db.execute(sql).each do |row|
146
+ issue = Issue.new(db)
147
+ issue.quick_assign! row
148
+ arr.push issue
149
+ end
150
+ arr end
97
151
 
98
152
  end # class Issue
99
153
  end # module Wlog
@@ -12,44 +12,48 @@ module Wlog
12
12
  class KeyValue
13
13
  include KeyValueSql
14
14
 
15
+ def initialize(db_handle)
16
+ @db = db_handle
17
+ end
18
+
15
19
  # Insert a key in the storage. If exists, replace the value with new one
16
20
  # @return nil
17
- def self.put!(key, value)
18
- if self.get(key).nil?
19
- self.create!(key, value)
21
+ def put!(key, value)
22
+ if get(key).nil?
23
+ create!(key, value)
20
24
  else
21
- self.update(key, value)
25
+ update(key, value)
22
26
  end
23
- nil end
27
+ end
24
28
 
25
29
  # Get a certain value by key
26
30
  # @return the value given the key. nil if not found
27
- def self.get(key)
28
- ret = DbRegistry.instance.execute(Select, key)
29
- ret = ret.empty? ? nil : ret.first
31
+ def get(key)
32
+ ret = @db.execute(Select, key)
33
+ ret = ret.empty? ? nil : ret.first[1]
30
34
  end
31
35
 
32
36
  # destroy by key
33
- def self.destroy!(key)
34
- DbRegistry.instance.execute(DeleteSql, key)
37
+ def destroy!(key)
38
+ @db.execute(DeleteSql, key)
35
39
  nil end
36
40
 
41
+ # The db handle
42
+ attr_accessor :db
43
+
37
44
  private
38
45
 
39
46
  # We want to provide a simple interface to the kv store, so the user should
40
47
  # not really care about updates
41
- def self.update(key,value)
42
- DbRegistry.instance.execute(UpdateSql, value, key)
48
+ def update(key,value)
49
+ @db.execute(UpdateSql, value, key)
43
50
  end
44
51
 
45
52
  # Create a pair... ;)
46
- def self.create!(key, value)
47
- DbRegistry.instance.execute(InsertSql, key, value)
53
+ def create!(key, value)
54
+ @db.execute(InsertSql, key, value)
48
55
  end
49
56
 
50
- class << self
51
- private :new, :allocate
52
- end
53
57
  end
54
58
  end # module Wlog
55
59
 
@@ -9,41 +9,45 @@ module Wlog
9
9
  class LogEntry
10
10
  include LogEntrySql
11
11
 
12
- def initialize
12
+ def initialize(db_handle)
13
13
  @date = Time.new
14
+ @db = db_handle
14
15
  end
15
16
 
16
- def self.find(id)
17
- row = DbRegistry.instance.execute(Select,id).first
18
- le = LogEntry.new
19
- le.quick_assign!(row[0], row[1], Time.at(row[2]))
17
+ def self.find(db, id)
18
+ row = db.execute(Select,id).first
19
+ le = nil
20
+ if row && !row.empty?
21
+ le = LogEntry.new(db)
22
+ le.quick_assign!(row[0], row[1], Time.at(row[2]), row[3])
23
+ end
20
24
  le end
21
25
 
22
- def self.find_all
23
- self.generic_find_all(SelectAll)
26
+ def self.find_all(db)
27
+ self.generic_find_all(db, SelectAll)
24
28
  end
25
29
 
26
- def self.find_all_by_issue_id(id)
27
- self.generic_find_all(SelectAllByIssue, id)
30
+ def self.find_all_by_issue_id(db, id)
31
+ self.generic_find_all(db, SelectAllByIssue, id)
28
32
  end
29
33
 
30
34
  # Delete a log entry with a given id.
31
35
  # @example Simple usage
32
36
  # # Since this is a class method:
33
37
  # LogEntry.delete(12)
34
- def self.delete(id)
35
- DbRegistry.instance.execute(DeleteSql,id)
38
+ def self.delete_by_id(db, id)
39
+ db.execute(DeleteSql,id)
36
40
  end
37
41
 
38
42
  # update the entry
39
43
  def update
40
- DbRegistry.instance.execute(UpdateSql,@description,@id)
44
+ @db.execute(UpdateSql,@description,@id)
41
45
  end
42
46
 
43
47
  # Search by string to find a matching description with 'LIKE'.
44
- def self.search_descriptions(term)
48
+ def self.search_descriptions(db, term)
45
49
  all = Array.new
46
- DbRegistry.instance.execute(SelectDescriptionLike,"%#{term}%").each do |row|
50
+ db.execute(SelectDescriptionLike,"%#{term}%").each do |row|
47
51
  le = LogEntry.new
48
52
  le.quick_assign!(row[0], row[1], Time.at(row[2]))
49
53
  all.push le
@@ -51,12 +55,19 @@ class LogEntry
51
55
  all end
52
56
 
53
57
  def insert
54
- DbRegistry.instance.execute(InsertSql, @description, @date.to_i, @issue_id)
58
+ raise 'Need issue_id' unless @issue_id
59
+ unless @id
60
+ @db.execute(InsertSql, @description, @date.to_i, @issue_id)
61
+ @id = @db.last_row_from(TableName).first[0].to_i
62
+ end
55
63
  end
56
64
 
57
65
  # Delete the loaded log entry currently in memory, by passing its id
58
66
  def delete
59
- self.delete(self.id)
67
+ if @id
68
+ LogEntry.delete_by_id(@db, @id)
69
+ @id = nil
70
+ end
60
71
  end
61
72
 
62
73
  def quick_assign!(id,desc,date,issue_id)
@@ -66,8 +77,8 @@ class LogEntry
66
77
  # Print things nicely formmated no more than 80 cars (well, unless you stick
67
78
  # the time in the end which is not counted for).
68
79
  def to_s
69
- str = "[#{id}] "
70
- tmp = @description + " [#{@date.strftime("%H:%M:%S")}]"
80
+ str = "[#{@id}] "
81
+ tmp = "#{@description} [#{@date.strftime("%H:%M:%S")}]"
71
82
  desc = Helpers.break_string(tmp,80)
72
83
  indent = " " * (id.to_s.split('').count + 5)
73
84
  desc.gsub!(/#{$/}/, "#{$/}#{indent}")
@@ -86,11 +97,14 @@ class LogEntry
86
97
  # The issue id (parent of this log entry)
87
98
  attr_accessor :issue_id
88
99
 
100
+ # The db handle
101
+ attr_accessor :db
102
+
89
103
  private
90
- def self.generic_find_all(sql, *params)
104
+ def self.generic_find_all(db, sql, *params)
91
105
  all = Array.new
92
- DbRegistry.instance.execute(sql, *params).each do |row|
93
- le = LogEntry.new
106
+ db.execute(sql, *params).each do |row|
107
+ le = LogEntry.new(db)
94
108
  le.quick_assign!(row[0], row[1], Time.at(row[2]), row[3])
95
109
  all.push le
96
110
  end
@@ -0,0 +1,17 @@
1
+ module Wlog
2
+ # This is the session that contains data about a particular run (for example
3
+ # what kind of database to use, etc). NOTE: this is a placeholder for now.
4
+ # Eventually this should be the object passed on sessions (for example, the
5
+ # active records should get the session object, and extract what they need from
6
+ # it - the db reference).
7
+ # @author Simon Symeonidis
8
+ class Session
9
+
10
+ def initialize(dbhandle)
11
+ @db = dbhandle
12
+ end
13
+
14
+ attr_accessor :db
15
+
16
+ end
17
+ end
@@ -8,21 +8,29 @@ module IssueSql
8
8
  # Standard insert
9
9
  InsertSql = \
10
10
  "INSERT INTO #{TableName} "\
11
- "(description, reported_date, due_date, status) "\
12
- "values (?,?,?,?);"
11
+ "(description, reported_date, due_date, status, timelog) "\
12
+ "values (?,?,?,?,0);"
13
13
  # Standard delete
14
14
  DeleteSql = "DELETE FROM #{TableName} WHERE id = ? ;"
15
15
 
16
16
  # Standard update
17
17
  UpdateSql = "UPDATE #{TableName} SET "\
18
- "description = ? , reported_date = ? , due_date = ? , status = ?"\
18
+ "description = ? , reported_date = ? , due_date = ? , status = ?, "\
19
+ "timelog = ? "\
19
20
  "WHERE id = ?;"
20
21
 
21
22
  # Select by id
22
23
  SelectSql = "SELECT * FROM #{TableName} WHERE id = ? ;"
23
24
 
24
- # Select all the issues
25
- SelectAllSql = "SELECT * FROM #{TableName}; "
25
+ # Select all the issues (which are not archived)
26
+ SelectAllSql = "SELECT * FROM #{TableName} WHERE status <> 3; "
27
+
28
+ # Select issues that are finished
29
+ SelectFinishedSql = "SELECT * FROM #{TableName} WHERE status = 2"
30
+
31
+ # Select issues given a time range
32
+ SelectTimeRange = "SELECT * FROM #{TableName}"\
33
+ " WHERE reported_date >= ? AND reported_date <= ?"
26
34
  end
27
35
  end
28
36
 
@@ -17,8 +17,17 @@ module StaticConfigurations
17
17
  # Absolute path to the data directory
18
18
  DataDirectory = "#{AppDirectory}data/"
19
19
 
20
+ # Where the template files exist
21
+ TemplateDir = "#{AppDirectory}templates/"
22
+
20
23
  # Default database name (when unspecified)
21
24
  DefaultDb = "default"
25
+
26
+ # This is used to see if it is the first setup or not
27
+ TaintFile = "#{AppDirectory}tainted"
28
+
29
+ # The configuration file
30
+ ConfigFile = "#{AppDirectory}config"
22
31
  end
23
32
  end # module Wlog
24
33
 
@@ -1,23 +1,93 @@
1
1
  require 'wlog/domain/key_value'
2
+ require 'wlog/domain/static_configurations'
3
+ require 'wlog/tech/wlog_string'
4
+ require 'wlog/tech/uncolored_string'
2
5
 
3
6
  module Wlog
4
7
  # System preferences helper, that stores last accessed stuff and other fluff.
5
8
  # @author Simon Symeonidis
6
9
  class SysConfig
10
+
11
+ def initialize(db)
12
+ @db = db
13
+ @key_value = KeyValue.new(@db)
14
+ end
15
+
7
16
  # load the last focused issue
8
- def self.last_focus
9
- KeyValue.get('last_focus')
17
+ def last_focus
18
+ @key_value.get('last_focus')
10
19
  end
11
20
 
12
21
  # store the last focused issue
13
- def self.last_focus=(issue)
14
- KeyValue.put!('last_focus', "#{issue}")
22
+ def last_focus=(issue)
23
+ @key_value.put!('last_focus', "#{issue}")
24
+ end
25
+
26
+ def self.ansi?
27
+ self.read_attributes['ansi'] == 'yes'
28
+ end
29
+
30
+ def self.not_ansi!
31
+ self.store_config('ansi', 'no')
32
+ end
33
+
34
+ def self.ansi!
35
+ self.store_config('ansi', 'yes')
36
+ end
37
+
38
+ # Store a term into the configuration file
39
+ def self.store_config(term,value)
40
+ values = self.read_attributes
41
+ values[term] = value
42
+ self.write_attributes(values)
43
+ end
44
+
45
+ # Get a term from the configuration file. Return nil if not found
46
+ def self.get_config(term)
47
+ self.read_attributes[term]
48
+ end
49
+
50
+ # Get the string decorator.
51
+ def self.string_decorator
52
+ if self.ansi?
53
+ WlogString
54
+ else
55
+ UncoloredString
56
+ end
15
57
  end
16
58
 
59
+ attr_accessor :db
60
+
17
61
  private
18
- class << self
19
- private :new, :allocate
62
+
63
+ # terms is a hash -> {:a => :b, :c => :d}
64
+ # write each key value to a file like this:
65
+ # a:b
66
+ # c:d
67
+ # ...
68
+ def self.write_attributes(terms)
69
+ include StaticConfigurations
70
+ str = terms.inject(""){|str,e| str += "#{e[0]}:#{e[1]}#{$/}"}
71
+ fh = File.open(ConfigFile, 'w')
72
+ fh.write(str)
73
+ fh.close
20
74
  end
75
+
76
+ # Load a hash from a text file.
77
+ # @see self.write_attributes
78
+ def self.read_attributes
79
+ include StaticConfigurations
80
+ FileUtils.touch ConfigFile
81
+ lines = File.open(ConfigFile, 'r').read.split(/#{$/}/)
82
+ terms = lines.map{|e| e.split(':')}
83
+ values = Hash.new(nil)
84
+ terms.each do |term_tuple| # [term, value]
85
+ values[term_tuple[0]] = term_tuple[1]
86
+ end
87
+ values end
88
+
89
+ attr :key_value
90
+
21
91
  end
22
92
  end # module Wlog
23
93