wlog 1.0.0 → 1.0.5

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