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.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/README.md +57 -9
- data/Rakefile +24 -0
- data/bin/wlog +10 -0
- data/lib/wlog/commands/archive_finished_issues.rb +20 -0
- data/lib/wlog/commands/archive_issues.rb +22 -0
- data/lib/wlog/commands/concat_description.rb +8 -2
- data/lib/wlog/commands/delete_issue.rb +31 -0
- data/lib/wlog/commands/innit_db.rb +0 -2
- data/lib/wlog/commands/make_csv.rb +7 -1
- data/lib/wlog/commands/new_entry.rb +7 -2
- data/lib/wlog/commands/replace_pattern.rb +4 -6
- data/lib/wlog/commands/taint_setup.rb +18 -0
- data/lib/wlog/db_registry.rb +3 -5
- data/lib/wlog/domain.rb +13 -0
- data/lib/wlog/domain/attachment.rb +25 -17
- data/lib/wlog/domain/helpers.rb +4 -0
- data/lib/wlog/domain/issue.rb +77 -23
- data/lib/wlog/domain/key_value.rb +21 -17
- data/lib/wlog/domain/log_entry.rb +35 -21
- data/lib/wlog/domain/session.rb +17 -0
- data/lib/wlog/domain/sql_modules/issue_sql.rb +13 -5
- data/lib/wlog/domain/static_configurations.rb +9 -0
- data/lib/wlog/domain/sys_config.rb +76 -6
- data/lib/wlog/domain/template_engine.rb +55 -0
- data/lib/wlog/domain/timelog_helper.rb +53 -0
- data/lib/wlog/sql/seq/2.sql +4 -0
- data/lib/wlog/{domain → tech}/ansi_colors.rb +7 -6
- data/lib/wlog/tech/uncolored_string.rb +14 -0
- data/lib/wlog/tech/wlog_string.rb +23 -0
- data/lib/wlog/ui/bootstrap.rb +18 -0
- data/lib/wlog/ui/cli_interface.rb +94 -34
- data/lib/wlog/ui/commands/create_issue.rb +8 -4
- data/lib/wlog/ui/issue_ui.rb +47 -41
- data/lib/wlog/ui/setup_wizard.rb +34 -0
- data/lib/wlog/version.rb +1 -1
- data/spec/domain/attachment_spec.rb +59 -0
- data/spec/domain/commands/concat_desc_spec.rb +51 -0
- data/spec/domain/commands/new_entry_spec.rb +41 -0
- data/spec/domain/commands/replace_pattern_spec.rb +46 -0
- data/spec/domain/issue_spec.rb +127 -0
- data/spec/domain/key_value_spec.rb +42 -0
- data/spec/domain/log_entry_spec.rb +85 -0
- data/spec/domain/sys_config_spec.rb +38 -0
- data/spec/make_db.rb +8 -0
- data/spec/old-databases/README.md +6 -0
- data/spec/old-databases/default +0 -0
- data/spec/tech/uncolored_string.rb +20 -0
- data/spec/tech/wlog_string_spec.rb +46 -0
- data/wlog.gemspec +5 -3
- metadata +44 -6
- data/spec/key_vale_spec.rb +0 -9
data/lib/wlog/domain/helpers.rb
CHANGED
data/lib/wlog/domain/issue.rb
CHANGED
@@ -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
|
16
|
+
@status = @seconds = 0
|
17
|
+
@db = db_handle
|
16
18
|
end
|
17
19
|
|
18
|
-
def self.find(id)
|
19
|
-
issue = Issue.new
|
20
|
-
ret =
|
21
|
-
|
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
|
-
|
27
|
-
|
28
|
-
|
29
|
-
arr.push
|
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.
|
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
|
-
|
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;
|
58
|
+
def delete; @db.execute(DeleteSql, @id) end
|
42
59
|
|
43
60
|
def update
|
44
|
-
|
45
|
-
|
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
|
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 = {
|
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
|
18
|
-
if
|
19
|
-
|
21
|
+
def put!(key, value)
|
22
|
+
if get(key).nil?
|
23
|
+
create!(key, value)
|
20
24
|
else
|
21
|
-
|
25
|
+
update(key, value)
|
22
26
|
end
|
23
|
-
|
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
|
28
|
-
ret =
|
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
|
34
|
-
|
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
|
42
|
-
|
48
|
+
def update(key,value)
|
49
|
+
@db.execute(UpdateSql, value, key)
|
43
50
|
end
|
44
51
|
|
45
52
|
# Create a pair... ;)
|
46
|
-
def
|
47
|
-
|
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 =
|
18
|
-
le =
|
19
|
-
|
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.
|
35
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
9
|
-
|
17
|
+
def last_focus
|
18
|
+
@key_value.get('last_focus')
|
10
19
|
end
|
11
20
|
|
12
21
|
# store the last focused issue
|
13
|
-
def
|
14
|
-
|
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
|
-
|
19
|
-
|
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
|
|