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