wlog 1.1.1 → 1.1.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/README.md +33 -48
- data/Rakefile +1 -0
- data/lib/wlog/commands/archive_finished_issues.rb +2 -2
- data/lib/wlog/commands/archive_issues.rb +1 -1
- data/lib/wlog/commands/bootstrap_templates.rb +37 -0
- data/lib/wlog/commands/concat_description.rb +7 -11
- data/lib/wlog/commands/delete_issue.rb +6 -5
- data/lib/wlog/commands/innit_db.rb +62 -6
- data/lib/wlog/commands/make_csv.rb +2 -6
- data/lib/wlog/commands/new_entry.rb +10 -7
- data/lib/wlog/commands/replace_pattern.rb +7 -5
- data/lib/wlog/domain/attachment.rb +50 -75
- data/lib/wlog/domain/invoice.rb +8 -2
- data/lib/wlog/domain/issue.rb +32 -132
- data/lib/wlog/domain/key_value.rb +10 -35
- data/lib/wlog/domain/log_entry.rb +18 -95
- data/lib/wlog/domain/static_configurations.rb +6 -0
- data/lib/wlog/domain/sys_config.rb +2 -5
- data/lib/wlog/migrations/make_standard_tables.rb +59 -0
- data/lib/wlog/ui/cli_interface.rb +55 -44
- data/lib/wlog/ui/commands/create_issue.rb +4 -8
- data/lib/wlog/ui/edit_handler.rb +74 -0
- data/lib/wlog/ui/invoice_ui.rb +133 -0
- data/lib/wlog/ui/issue_ui.rb +21 -77
- data/lib/wlog/ui/template_ui.rb +66 -0
- data/lib/wlog/version.rb +1 -1
- data/spec/domain/attachment_spec.rb +58 -59
- data/spec/domain/commands/concat_desc_spec.rb +10 -13
- data/spec/domain/commands/new_entry_spec.rb +10 -13
- data/spec/domain/commands/replace_pattern_spec.rb +11 -12
- data/spec/domain/issue_spec.rb +35 -38
- data/spec/domain/key_value_spec.rb +5 -8
- data/spec/domain/log_entry_spec.rb +20 -31
- data/spec/domain/sys_config_spec.rb +4 -7
- data/spec/make_db.rb +30 -4
- data/spec/tech/wlog_string_spec.rb +14 -14
- data/wlog.gemspec +2 -1
- metadata +26 -13
- data/lib/wlog/db_registry.rb +0 -38
- data/lib/wlog/domain/sql_modules/attachment_sql.rb +0 -21
- data/lib/wlog/domain/sql_modules/issue_sql.rb +0 -37
- data/lib/wlog/domain/sql_modules/key_value_sql.rb +0 -20
- data/lib/wlog/domain/sql_modules/log_entry_sql.rb +0 -35
- data/lib/wlog/ui/commands/attach_to_issue.rb +0 -0
data/lib/wlog/domain/issue.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
require '
|
2
|
-
|
1
|
+
require 'active_record'
|
2
|
+
|
3
3
|
require 'wlog/domain/log_entry'
|
4
4
|
require 'wlog/domain/timelog_helper'
|
5
5
|
require 'wlog/domain/sys_config'
|
@@ -9,164 +9,64 @@ module Wlog
|
|
9
9
|
# This aggregates log entries. The total time spent on this issue is
|
10
10
|
# calculated from checking out said log entries.
|
11
11
|
# @author Simon Symeonidis
|
12
|
-
class Issue
|
13
|
-
include IssueSql
|
14
|
-
|
15
|
-
def initialize(db_handle)
|
16
|
-
@reported_date = Time.now
|
17
|
-
@log_entries = Array.new
|
18
|
-
@status = @seconds = 0
|
19
|
-
@db = db_handle
|
20
|
-
@strmaker = SysConfig.string_decorator
|
21
|
-
end
|
22
|
-
|
23
|
-
# Calculate the total time that someone has wasted on all the
|
24
|
-
# issues in the current database
|
25
|
-
def self.total_time
|
26
|
-
# issues = Issue.find_all
|
27
|
-
end
|
28
|
-
|
29
|
-
def self.find(db, id)
|
30
|
-
issue = Issue.new(db)
|
31
|
-
ret = db.execute(SelectSql, id).first
|
32
|
-
if ret.nil? || ret.empty?
|
33
|
-
issue = nil
|
34
|
-
else
|
35
|
-
issue.quick_assign! ret
|
36
|
-
end
|
37
|
-
issue end
|
38
|
-
|
39
|
-
def self.find_all(db)
|
40
|
-
self.generic_find_all(db, SelectAllSql)
|
41
|
-
end
|
42
|
-
|
43
|
-
def self.find_all_finished(db)
|
44
|
-
self.generic_find_all(db, SelectFinishedSql)
|
45
|
-
end
|
46
|
-
|
47
|
-
def self.find_in_time_range(db, from, to)
|
48
|
-
arr = Array.new
|
49
|
-
db.execute(SelectTimeRange, from, to).each do |row|
|
50
|
-
tmp = Issue.new(@db)
|
51
|
-
tmp.quick_assign!(row)
|
52
|
-
arr.push tmp
|
53
|
-
end
|
54
|
-
arr end
|
55
|
-
|
56
|
-
def self.delete_by_id(db, id); db.execute(DeleteSql, id) end
|
57
|
-
|
58
|
-
# inserts the entry into the database if it has not been stored before.
|
59
|
-
def insert
|
60
|
-
unless @id
|
61
|
-
@db.execute(InsertSql, @description,
|
62
|
-
@reported_date.to_i, @due_date.to_i, @status, @long_description)
|
63
|
-
@id = @db.last_row_from(TableName).first[0]
|
64
|
-
end
|
65
|
-
end
|
12
|
+
class Issue < ActiveRecord::Base
|
66
13
|
|
67
|
-
|
14
|
+
has_many :log_entries, dependent: :delete_all
|
68
15
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
# Add a log entry object to the issue
|
75
|
-
# TODO this does nothing / used for nothing yet
|
76
|
-
def add_log_entry(le)
|
77
|
-
@log_entries.push le
|
78
|
-
end
|
16
|
+
StatusNew = 0
|
17
|
+
StatusStarted = 1
|
18
|
+
StatusFinished = 2
|
19
|
+
StatusArchived = 3
|
79
20
|
|
80
|
-
|
81
|
-
|
82
|
-
@long_description =\
|
83
|
-
row[0], row[1], Time.at(row[2]), Time.at(row[3]), row[4],
|
84
|
-
row[5] || 0, row[6]
|
85
|
-
nil end
|
21
|
+
# Anything which is not archived (eg: new, started work, finished)
|
22
|
+
def self.find_not_archived; where("status NOT IN (?)", StatusArchived) end
|
86
23
|
|
87
24
|
# Log the seconds into the issue
|
88
|
-
def log_time(
|
89
|
-
|
90
|
-
|
25
|
+
def log_time(sec)
|
26
|
+
self.timelog += sec
|
27
|
+
save
|
91
28
|
end
|
92
29
|
|
93
30
|
def to_s
|
94
|
-
|
95
|
-
|
96
|
-
"
|
97
|
-
" #{@strmaker.blue('
|
98
|
-
" #{@strmaker.blue('
|
99
|
-
" #{@strmaker.blue('
|
31
|
+
le_count = self.log_entries.count
|
32
|
+
@strmaker = SysConfig.string_decorator
|
33
|
+
"#{@strmaker.yellow('Issue')} ##{id}#{$/}"\
|
34
|
+
" #{@strmaker.blue('Reported')} : #{created_at.asctime}#{$/}"\
|
35
|
+
" #{@strmaker.blue('Due')} : #{due_date.asctime}#{$/}"\
|
36
|
+
" #{@strmaker.blue('Entries')} : #{le_count} #{$/}"\
|
37
|
+
" #{@strmaker.blue('Status')} : #{Statuses[status]}#{$/}"\
|
38
|
+
" #{@strmaker.blue('Time')} : #{TimelogHelper.time_to_s(timelog)}#{$/}"\
|
100
39
|
"#{$/}"\
|
101
40
|
"#{@strmaker.yellow('Summary')} #{$/}"\
|
102
|
-
" #{
|
41
|
+
" #{description}#{$/ + $/}"\
|
103
42
|
"#{@strmaker.yellow('Description')} #{$/}"\
|
104
|
-
" #{Helpers.break_string(
|
43
|
+
" #{Helpers.break_string(long_description, 80)}#{$/ + $/}"
|
105
44
|
end
|
106
45
|
|
107
46
|
# Mark issue as started
|
108
|
-
def mark_started!;
|
47
|
+
def mark_started!; self.status = 0 end
|
109
48
|
|
110
49
|
# Mark the issue as working
|
111
|
-
def mark_working!;
|
50
|
+
def mark_working!; self.status = 1 end
|
112
51
|
|
113
52
|
# Mark the issue as finished
|
114
|
-
def mark_finished!;
|
53
|
+
def mark_finished!; self.status = 2 end
|
115
54
|
|
116
55
|
# Archive the issue
|
117
|
-
def archive!;
|
56
|
+
def archive!; self.status = 3 end
|
118
57
|
|
119
58
|
# Get the status as a string
|
120
|
-
def status_s; Statuses[
|
121
|
-
|
122
|
-
# [String] Description of the issue at hand
|
123
|
-
attr_accessor :description
|
124
|
-
|
125
|
-
# A longer description that can provide more details as opposed to a simple
|
126
|
-
# title as suggested by @description.
|
127
|
-
attr_accessor :long_description
|
128
|
-
|
129
|
-
# [Time] The due date of the issue
|
130
|
-
attr_accessor :due_date
|
131
|
-
|
132
|
-
# [Time] The reported date of the issue
|
133
|
-
attr_accessor :reported_date
|
134
|
-
|
135
|
-
# [Array<LogEntry>] an array containing the log entries that are specific
|
136
|
-
# to this issue
|
137
|
-
attr_accessor :log_entries
|
138
|
-
|
139
|
-
# [Fixnum] is the identifier of this object
|
140
|
-
attr_accessor :id
|
141
|
-
|
142
|
-
# [Fixnum] Status of the current issue (0 is for not started, 1 working on,
|
143
|
-
# 2 for finished)
|
144
|
-
attr_accessor :status
|
145
|
-
|
146
|
-
# The seconds that you have wasted your life on in order to get something
|
147
|
-
# done
|
148
|
-
attr_accessor :seconds
|
149
|
-
|
150
|
-
# The database handle for this AR
|
151
|
-
attr_accessor :db
|
59
|
+
def status_s; Statuses[status] end
|
152
60
|
|
153
61
|
private
|
154
|
-
|
155
62
|
Statuses = {
|
156
|
-
|
157
|
-
|
63
|
+
StatusNew => "new",
|
64
|
+
StatusStarted => "started work",
|
65
|
+
StatusFinished => "finished",
|
66
|
+
StatusArchived => "archived"}
|
158
67
|
|
159
68
|
private_class_method
|
160
69
|
|
161
|
-
def self.generic_find_all(db, sql)
|
162
|
-
arr = Array.new
|
163
|
-
db.execute(sql).each do |row|
|
164
|
-
issue = Issue.new(db)
|
165
|
-
issue.quick_assign! row
|
166
|
-
arr.push issue
|
167
|
-
end
|
168
|
-
arr end
|
169
|
-
|
170
70
|
end # class Issue
|
171
71
|
end # module Wlog
|
172
72
|
|
@@ -1,5 +1,4 @@
|
|
1
|
-
require '
|
2
|
-
require 'wlog/domain/sql_modules/key_value_sql'
|
1
|
+
require 'active_record'
|
3
2
|
module Wlog
|
4
3
|
# An active record that stores keys with values. Keys and values are strings.
|
5
4
|
# convert as you need them.
|
@@ -9,51 +8,27 @@ module Wlog
|
|
9
8
|
# '42', when looking up 'jon' you will retrieve only the latter (42).
|
10
9
|
#
|
11
10
|
# @author Simon Symeonidis
|
12
|
-
class KeyValue
|
13
|
-
include KeyValueSql
|
14
|
-
|
15
|
-
def initialize(db_handle)
|
16
|
-
@db = db_handle
|
17
|
-
end
|
11
|
+
class KeyValue < ActiveRecord::Base
|
18
12
|
|
19
13
|
# Insert a key in the storage. If exists, replace the value with new one
|
20
14
|
# @return nil
|
21
|
-
def put!(key, value)
|
22
|
-
if
|
23
|
-
|
15
|
+
def self.put!(key, value)
|
16
|
+
if ret = KeyValue.find_by_key(key)
|
17
|
+
ret.value = value
|
24
18
|
else
|
25
|
-
|
19
|
+
ret = KeyValue.new(:key => key, :value => value)
|
26
20
|
end
|
21
|
+
ret.save
|
27
22
|
end
|
28
23
|
|
29
24
|
# Get a certain value by key
|
30
25
|
# @return the value given the key. nil if not found
|
31
|
-
def get(key)
|
32
|
-
ret =
|
33
|
-
ret = ret
|
26
|
+
def self.get(key)
|
27
|
+
ret = find_by_key(key)
|
28
|
+
ret = ret ? ret.value : nil
|
34
29
|
end
|
35
30
|
|
36
|
-
# destroy by key
|
37
|
-
def destroy!(key)
|
38
|
-
@db.execute(DeleteSql, key)
|
39
|
-
nil end
|
40
|
-
|
41
|
-
# The db handle
|
42
|
-
attr_accessor :db
|
43
|
-
|
44
31
|
private
|
45
|
-
|
46
|
-
# We want to provide a simple interface to the kv store, so the user should
|
47
|
-
# not really care about updates
|
48
|
-
def update(key,value)
|
49
|
-
@db.execute(UpdateSql, value, key)
|
50
|
-
end
|
51
|
-
|
52
|
-
# Create a pair... ;)
|
53
|
-
def create!(key, value)
|
54
|
-
@db.execute(InsertSql, key, value)
|
55
|
-
end
|
56
|
-
|
57
32
|
end
|
58
33
|
end # module Wlog
|
59
34
|
|
@@ -1,114 +1,37 @@
|
|
1
|
-
require '
|
1
|
+
require 'active_record'
|
2
|
+
|
2
3
|
require 'wlog/domain/helpers'
|
3
4
|
require 'wlog/domain/attachment'
|
4
|
-
require 'wlog/domain/sql_modules/log_entry_sql'
|
5
5
|
|
6
6
|
module Wlog
|
7
7
|
# Author :: Simon Symeonidis
|
8
8
|
# Active Record Domain object for a log entry
|
9
|
-
class LogEntry
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
end
|
24
|
-
le end
|
25
|
-
|
26
|
-
def self.find_all(db)
|
27
|
-
self.generic_find_all(db, SelectAll)
|
28
|
-
end
|
29
|
-
|
30
|
-
def self.find_all_by_issue_id(db, id)
|
31
|
-
self.generic_find_all(db, SelectAllByIssue, id)
|
32
|
-
end
|
33
|
-
|
34
|
-
# Delete a log entry with a given id.
|
35
|
-
# @example Simple usage
|
36
|
-
# # Since this is a class method:
|
37
|
-
# LogEntry.delete(12)
|
38
|
-
def self.delete_by_id(db, id)
|
39
|
-
db.execute(DeleteSql,id)
|
40
|
-
end
|
41
|
-
|
42
|
-
# update the entry
|
43
|
-
def update
|
44
|
-
@db.execute(UpdateSql,@description,@id)
|
45
|
-
end
|
46
|
-
|
47
|
-
# Search by string to find a matching description with 'LIKE'.
|
48
|
-
def self.search_descriptions(db, term)
|
49
|
-
all = Array.new
|
50
|
-
db.execute(SelectDescriptionLike,"%#{term}%").each do |row|
|
51
|
-
le = LogEntry.new
|
52
|
-
le.quick_assign!(row[0], row[1], Time.at(row[2]))
|
53
|
-
all.push le
|
54
|
-
end
|
55
|
-
all end
|
56
|
-
|
57
|
-
def insert
|
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
|
63
|
-
end
|
64
|
-
|
65
|
-
# Delete the loaded log entry currently in memory, by passing its id
|
66
|
-
def delete
|
67
|
-
if @id
|
68
|
-
LogEntry.delete_by_id(@db, @id)
|
69
|
-
@id = nil
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
def quick_assign!(id,desc,date,issue_id)
|
74
|
-
@id, @description, @date, @issue_id = id, desc, date, issue_id
|
75
|
-
end
|
76
|
-
|
9
|
+
class LogEntry < ActiveRecord::Base
|
10
|
+
|
11
|
+
belongs_to :issue
|
12
|
+
|
13
|
+
# Search by string to find a matching description with 'LIKE'.
|
14
|
+
# def self.search_descriptions(db, term)
|
15
|
+
# all = Array.new
|
16
|
+
# db.execute(SelectDescriptionLike,"%#{term}%").each do |row|
|
17
|
+
# le = LogEntry.new
|
18
|
+
# le.quick_assign!(row[0], row[1], Time.at(row[2]))
|
19
|
+
# all.push le
|
20
|
+
# end
|
21
|
+
# all end
|
22
|
+
|
77
23
|
# Print things nicely formmated no more than 80 cars (well, unless you stick
|
78
24
|
# the time in the end which is not counted for).
|
79
25
|
def to_s
|
80
|
-
str = "[#{
|
81
|
-
tmp = "#{
|
26
|
+
str = "[#{id}] "
|
27
|
+
tmp = "#{description} [#{created_at.strftime("%H:%M:%S")}]"
|
82
28
|
desc = Helpers.break_string(tmp,80)
|
83
29
|
indent = " " * (id.to_s.split('').count + 5)
|
84
30
|
desc.gsub!(/#{$/}/, "#{$/}#{indent}")
|
85
31
|
str.concat(desc)
|
86
32
|
str end
|
87
33
|
|
88
|
-
# The identity field for the log entry DO
|
89
|
-
attr_accessor :id
|
90
|
-
|
91
|
-
# Text description for the log entry
|
92
|
-
attr_accessor :description
|
93
|
-
|
94
|
-
# Date the entry was created
|
95
|
-
attr_accessor :date
|
96
|
-
|
97
|
-
# The issue id (parent of this log entry)
|
98
|
-
attr_accessor :issue_id
|
99
|
-
|
100
|
-
# The db handle
|
101
|
-
attr_accessor :db
|
102
|
-
|
103
34
|
private
|
104
|
-
def self.generic_find_all(db, sql, *params)
|
105
|
-
all = Array.new
|
106
|
-
db.execute(sql, *params).each do |row|
|
107
|
-
le = LogEntry.new(db)
|
108
|
-
le.quick_assign!(row[0], row[1], Time.at(row[2]), row[3])
|
109
|
-
all.push le
|
110
|
-
end
|
111
|
-
all end
|
112
35
|
end
|
113
36
|
end # module Wlog
|
114
37
|
|
@@ -20,6 +20,12 @@ module StaticConfigurations
|
|
20
20
|
# Where the template files exist
|
21
21
|
TemplateDir = "#{AppDirectory}templates/"
|
22
22
|
|
23
|
+
# Sample file to provide the user with
|
24
|
+
TemplateSampleFile = "#{TemplateDir}/default.erb"
|
25
|
+
|
26
|
+
# In the future if someone wants to code an alternative, go ahead
|
27
|
+
TemplateOutputDir = "#{ENV['HOME']}/Documents/wlog/"
|
28
|
+
|
23
29
|
# Default database name (when unspecified)
|
24
30
|
DefaultDb = "default"
|
25
31
|
|
@@ -8,9 +8,8 @@ module Wlog
|
|
8
8
|
# @author Simon Symeonidis
|
9
9
|
class SysConfig
|
10
10
|
|
11
|
-
def initialize
|
12
|
-
@
|
13
|
-
@key_value = KeyValue.new(@db)
|
11
|
+
def initialize
|
12
|
+
@key_value = KeyValue
|
14
13
|
end
|
15
14
|
|
16
15
|
# load the last focused issue
|
@@ -59,8 +58,6 @@ class SysConfig
|
|
59
58
|
end
|
60
59
|
end
|
61
60
|
|
62
|
-
attr_accessor :db
|
63
|
-
|
64
61
|
# terms is a hash -> {:a => :b, :c => :d}
|
65
62
|
# write each key value to a file like this:
|
66
63
|
# a:b
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
module Wlog
|
4
|
+
# Migrations to replace the raw sql from turntables
|
5
|
+
# @author Simon Symeonidis
|
6
|
+
class MakeStandardTables < ActiveRecord::Migration
|
7
|
+
def change
|
8
|
+
create_table :issues do |t|
|
9
|
+
t.text :description
|
10
|
+
t.datetime :due_date
|
11
|
+
t.integer :status
|
12
|
+
t.integer :timelog, :limit => 8
|
13
|
+
t.text :long_description
|
14
|
+
t.datetime :created_at
|
15
|
+
t.datetime :updated_at
|
16
|
+
end
|
17
|
+
|
18
|
+
create_table :log_entries do |t|
|
19
|
+
t.text :description
|
20
|
+
t.integer :issue_id
|
21
|
+
t.datetime :created_at
|
22
|
+
t.datetime :updated_at
|
23
|
+
end
|
24
|
+
|
25
|
+
create_table :attachments do |t|
|
26
|
+
t.text :filename
|
27
|
+
t.text :given_name
|
28
|
+
t.text :data
|
29
|
+
t.datetime :created_at
|
30
|
+
t.datetime :updated_at
|
31
|
+
end
|
32
|
+
|
33
|
+
create_table :key_values do |t|
|
34
|
+
t.text :key
|
35
|
+
t.text :value
|
36
|
+
end
|
37
|
+
|
38
|
+
create_table :invoices do |t|
|
39
|
+
t.datetime :from
|
40
|
+
t.datetime :to
|
41
|
+
t.text :description
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
=begin
|
49
|
+
TODO
|
50
|
+
-- A polymorphic relationship for attachments. So pretty much anything that
|
51
|
+
-- wants to have something attached, uses the discriminator in order to
|
52
|
+
-- specify itself, as well as its id.
|
53
|
+
CREATE TABLE polymorphic_attachments (
|
54
|
+
discriminator TEXT,
|
55
|
+
discriminator_id INTEGER,
|
56
|
+
attachment_id INTEGER
|
57
|
+
);
|
58
|
+
|
59
|
+
=end
|