fs-ticket 0.0.0
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 +7 -0
- data/LICENSE.txt +19 -0
- data/bin/fs-ticket +2 -0
- data/lib/fs-ticket.rb +86 -0
- data/lib/render.rb +42 -0
- data/lib/ticket.rb +76 -0
- data/lib/view/add-ticket-view.rb +129 -0
- data/lib/view/report-view.rb +170 -0
- data/lib/view/ticket-view.rb +80 -0
- data/lib/widget/button-widget.rb +37 -0
- data/lib/widget/dropdown-widget.rb +91 -0
- data/lib/widget/text-field-widget.rb +42 -0
- data/lib/widget/text-line-widget.rb +36 -0
- metadata +89 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 146341c184c747b654b08d7e1c7145d410a2b2e4
|
4
|
+
data.tar.gz: 083d0e74aa12dd6b04774eb0a6253cdc4e398b3e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d0f40c88ca36c2b2f5740b4e2f4eed5823043d113eac98b3569f0e482266993abcd865bc16fcc0ac2eff600b84e4bec31a17fb1d430109dd064fbb97ef8fc1be
|
7
|
+
data.tar.gz: 7736fb517c07a77a01ef7401d427fad364c1247ffd5c3e20fea11dacca7015b057da5bc4c246d27e909e69fcbf1f167335b8582ee483a14d110dd6c7a5ee17e2
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2015 Mark McCurry
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/bin/fs-ticket
ADDED
data/lib/fs-ticket.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
require "sqlite3"
|
2
|
+
require "curses"
|
3
|
+
require "pp"
|
4
|
+
|
5
|
+
require_relative "ticket.rb"
|
6
|
+
require_relative "render.rb"
|
7
|
+
FossilCheckoutName = ".fslckout"
|
8
|
+
|
9
|
+
$fossil_repo_db = nil
|
10
|
+
|
11
|
+
def find_file_prefix(fname, prefix="")
|
12
|
+
if(prefix.length > 10)
|
13
|
+
nil
|
14
|
+
else
|
15
|
+
begin
|
16
|
+
f = File.open(prefix+fname)
|
17
|
+
f.close
|
18
|
+
prefix
|
19
|
+
rescue Exception=>e
|
20
|
+
find_file_prefix(fname, prefix+"../")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
#1. Identify Fossil Checkout File
|
27
|
+
#2. Identify Main Repo File
|
28
|
+
#3. Obtain A Full Dump of Tickets
|
29
|
+
|
30
|
+
#Locate file
|
31
|
+
FilePrefix = find_file_prefix(FossilCheckoutName)
|
32
|
+
if(FilePrefix.nil?)
|
33
|
+
puts "This directory doesn't appear to be part of a fossil repo..."
|
34
|
+
puts "Please make sure that a #{FossilCheckoutName} file is visible to this tool"
|
35
|
+
exit
|
36
|
+
end
|
37
|
+
|
38
|
+
#Verify File Exists
|
39
|
+
f = File.open(FilePrefix+FossilCheckoutName)
|
40
|
+
f.close
|
41
|
+
|
42
|
+
#Grab The Real Repo
|
43
|
+
SQLite3::Database.new(FilePrefix+FossilCheckoutName) do |db|
|
44
|
+
db.execute("select value from vvar where name is \"repository\"") do |val|
|
45
|
+
$fossil_repo_db = FilePrefix+val[0]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def get_tickets
|
50
|
+
#Verify The Real Repo Exists
|
51
|
+
f = File.open($fossil_repo_db)
|
52
|
+
f.close
|
53
|
+
|
54
|
+
#Grab The Tickets
|
55
|
+
tickets = []
|
56
|
+
SQLite3::Database.new($fossil_repo_db) do |db|
|
57
|
+
db.execute("select tkt_uuid from ticket order by tkt_ctime") do |val|
|
58
|
+
tickets << val[0]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
#Display The Tickets
|
63
|
+
tkt_objs = []
|
64
|
+
display_id = 1
|
65
|
+
$db ||= SQLite3::Database.new($fossil_repo_db)
|
66
|
+
tickets.each do |id|
|
67
|
+
$db.execute("select title, status from ticket where tkt_uuid = \"#{id}\"") do |val|
|
68
|
+
if(val[1] != "Fixed")
|
69
|
+
#puts "#{display_id}. [#{val[1]}] #{val[0]}"
|
70
|
+
display_id += 1
|
71
|
+
end
|
72
|
+
tkt_objs << Ticket.new($db, id)
|
73
|
+
#puts tkt_objs[-1].title
|
74
|
+
#puts tkt_objs[-1].status
|
75
|
+
end
|
76
|
+
end
|
77
|
+
tkt_objs
|
78
|
+
end
|
79
|
+
|
80
|
+
TableRender.new{get_tickets}
|
81
|
+
$db.close
|
82
|
+
|
83
|
+
#Grab an action
|
84
|
+
# A - add [id]
|
85
|
+
# F - fix [id]
|
86
|
+
# V - view [id]
|
data/lib/render.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
#Widgets
|
2
|
+
require_relative "widget/text-line-widget.rb"
|
3
|
+
require_relative "widget/text-field-widget.rb"
|
4
|
+
require_relative "widget/dropdown-widget.rb"
|
5
|
+
require_relative "widget/button-widget.rb"
|
6
|
+
|
7
|
+
#Application Views
|
8
|
+
require_relative "view/report-view.rb"
|
9
|
+
require_relative "view/ticket-view.rb"
|
10
|
+
require_relative "view/add-ticket-view.rb"
|
11
|
+
|
12
|
+
|
13
|
+
#Curses render
|
14
|
+
class TableRender
|
15
|
+
def initialize(&block)
|
16
|
+
@tickets = block.call
|
17
|
+
@screen = nil
|
18
|
+
init_curses
|
19
|
+
view = ReportView.new(@screen, @tickets, block)
|
20
|
+
view.interact
|
21
|
+
end
|
22
|
+
|
23
|
+
# Perform the curses setup
|
24
|
+
def init_curses
|
25
|
+
# signal(SIGINT, finish)
|
26
|
+
|
27
|
+
Curses.init_screen
|
28
|
+
Curses.raw
|
29
|
+
Curses.nonl
|
30
|
+
#Curses.cbreak
|
31
|
+
Curses.noecho
|
32
|
+
Curses.curs_set(0)
|
33
|
+
Curses.ESCDELAY = 10
|
34
|
+
Curses.start_color
|
35
|
+
Curses.init_pair(1, Curses::COLOR_WHITE, Curses::COLOR_BLUE);
|
36
|
+
|
37
|
+
@screen = Curses.stdscr
|
38
|
+
|
39
|
+
@screen.scrollok(true)
|
40
|
+
@screen.keypad(true)
|
41
|
+
end
|
42
|
+
end
|
data/lib/ticket.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
class Ticket
|
2
|
+
def initialize(database, id)
|
3
|
+
@db = database
|
4
|
+
@ticketid = id
|
5
|
+
@title = nil
|
6
|
+
@status = nil
|
7
|
+
@comments = nil
|
8
|
+
end
|
9
|
+
|
10
|
+
def select(field)
|
11
|
+
result = nil
|
12
|
+
@db.execute(
|
13
|
+
"select #{field} from ticket where tkt_uuid is ?",
|
14
|
+
@ticketid.to_s) do |val|
|
15
|
+
result = val[0]
|
16
|
+
end
|
17
|
+
result
|
18
|
+
end
|
19
|
+
|
20
|
+
def title
|
21
|
+
@title ||= select("title")
|
22
|
+
end
|
23
|
+
|
24
|
+
def status
|
25
|
+
@status ||= select("status")
|
26
|
+
end
|
27
|
+
|
28
|
+
def type
|
29
|
+
@type ||= select("type")
|
30
|
+
end
|
31
|
+
|
32
|
+
def subsystem
|
33
|
+
@subsystem ||= select("subsystem")
|
34
|
+
end
|
35
|
+
|
36
|
+
def comment_id
|
37
|
+
@comment_id ||= select("tkt_id")
|
38
|
+
end
|
39
|
+
|
40
|
+
def old_comments
|
41
|
+
res = select("comment")
|
42
|
+
res ||= ""
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
def comments
|
47
|
+
if(!@comments)
|
48
|
+
old = old_comments
|
49
|
+
res = @db.execute("select icomment from ticketchng where tkt_id is ?", comment_id)
|
50
|
+
if(res)
|
51
|
+
@comments = old+res.join
|
52
|
+
else
|
53
|
+
@comments = old+""
|
54
|
+
end
|
55
|
+
@comments.gsub!(/\r\n/, "\n")
|
56
|
+
end
|
57
|
+
@comments
|
58
|
+
end
|
59
|
+
|
60
|
+
def priority
|
61
|
+
@priority ||= select("priority")
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
def resolve
|
66
|
+
if(status == "Open")
|
67
|
+
`fossil ticket set #{@ticketid} status Fixed`
|
68
|
+
@status = nil
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def match(regex)
|
73
|
+
(comments && comments.match(regex)) || (title && title.match(regex))
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
@@ -0,0 +1,129 @@
|
|
1
|
+
class AddTicketView
|
2
|
+
def initialize(screen)
|
3
|
+
@screen = screen
|
4
|
+
@props = Hash.new
|
5
|
+
@title = TextLineWidget.new(@screen, "", 11, 1, 30)
|
6
|
+
@priort = DropdownWidget.new(@screen, "Zero", 11, 2, 30)
|
7
|
+
@type = DropdownWidget.new(@screen, "Unclassified", 11, 3, 30)
|
8
|
+
@desc = TextFieldWidget.new(@screen, "", 0, 5, 78, 14)
|
9
|
+
@cancel = ButtonWidget.new(@screen, "Cancel", 20, 20)
|
10
|
+
@submit = ButtonWidget.new(@screen, "Submit", 0, 20)
|
11
|
+
@priort.options = %w{Zero Low Medium High Immediate}
|
12
|
+
@type.options = %w{Incident Code_Defect Build_Problem Documentation Feature_Request}
|
13
|
+
@widgets = [@title, @priort, @type, @desc, @submit, @cancel]
|
14
|
+
@title.active = true
|
15
|
+
@active_id = 0
|
16
|
+
end
|
17
|
+
|
18
|
+
def display
|
19
|
+
@screen.clear
|
20
|
+
@screen.setpos(0,0)
|
21
|
+
@screen.attron(Curses.color_pair(1));
|
22
|
+
@screen.addstr("Issue Creation")
|
23
|
+
@screen.attroff(Curses.color_pair(1));
|
24
|
+
|
25
|
+
|
26
|
+
@screen.setpos(1,0)
|
27
|
+
@screen.addstr("Title:")
|
28
|
+
@screen.setpos(2,0)
|
29
|
+
@screen.addstr("Priority:")
|
30
|
+
@screen.setpos(3,0)
|
31
|
+
@screen.addstr("Type:")
|
32
|
+
@screen.setpos(4,0)
|
33
|
+
@screen.addstr("Description:")
|
34
|
+
|
35
|
+
ln = 6
|
36
|
+
@widgets.each_with_index do |w, idx|
|
37
|
+
if(idx != @active_id)
|
38
|
+
w.draw
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
#Draw the active widget last as it might have an overlay
|
43
|
+
@widgets[@active_id].draw
|
44
|
+
end
|
45
|
+
|
46
|
+
def changeActive(a)
|
47
|
+
a = [a,0].max
|
48
|
+
a = [a,@widgets.length-1].min
|
49
|
+
|
50
|
+
if(@active_id != a)
|
51
|
+
@widgets[@active_id].active = false
|
52
|
+
@active_id = a
|
53
|
+
@widgets[@active_id].active = true
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def interact
|
58
|
+
while true
|
59
|
+
result = true
|
60
|
+
c = Curses.getch
|
61
|
+
case c
|
62
|
+
when 27
|
63
|
+
break
|
64
|
+
when ?\t, 9
|
65
|
+
changeActive (@active_id + 1) % @widgets.length
|
66
|
+
display
|
67
|
+
when ?e
|
68
|
+
edit
|
69
|
+
display
|
70
|
+
else
|
71
|
+
if(!@widgets[@active_id].handle(c))
|
72
|
+
if(c == Curses::KEY_UP)
|
73
|
+
changeActive @active_id - 1
|
74
|
+
display
|
75
|
+
elsif(c == Curses::KEY_DOWN)
|
76
|
+
changeActive @active_id + 1
|
77
|
+
display
|
78
|
+
else
|
79
|
+
@screen.setpos(0,0)
|
80
|
+
@screen.addstr("[unknown key `#{Curses.keyname(c)}'=#{c}] ")
|
81
|
+
end
|
82
|
+
else
|
83
|
+
if((c == "\n" || c == 13) && @submit == @widgets[@active_id])
|
84
|
+
`fossil ticket add title '#{@title.value}' priority '#{@priort.value}' type '#{@type.value}' icomment '#{@desc.value}' status Open`
|
85
|
+
return :do_refresh
|
86
|
+
elsif((c == "\n" || c == 13) && @cancel == @widgets[@active_id])
|
87
|
+
break
|
88
|
+
else
|
89
|
+
display
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
@screen.setpos(0,0)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def edit
|
98
|
+
tmp = File.open("fs-ticket-edit.yaml", "w+")
|
99
|
+
tmp.puts({"title" => @title.value,
|
100
|
+
"priority" => @priort.value,
|
101
|
+
"type" => @type.value,
|
102
|
+
"description" => @desc.value}.to_yaml)
|
103
|
+
tmp.close
|
104
|
+
|
105
|
+
system("vim fs-ticket-edit.yaml")
|
106
|
+
|
107
|
+
#Restore curses options
|
108
|
+
Curses.raw
|
109
|
+
Curses.nonl
|
110
|
+
#Curses.cbreak
|
111
|
+
Curses.noecho
|
112
|
+
Curses.curs_set(0)
|
113
|
+
@screen.scrollok(true)
|
114
|
+
@screen.keypad(true)
|
115
|
+
|
116
|
+
begin
|
117
|
+
tmp = File.open("fs-ticket-edit.yaml", "r")
|
118
|
+
yy = YAML.load(tmp.read)
|
119
|
+
tmp.close
|
120
|
+
@title.value = yy["title"]
|
121
|
+
@priort.value = yy["priority"]
|
122
|
+
@type.value = yy["type"]
|
123
|
+
@desc.value = yy["description"]
|
124
|
+
|
125
|
+
#Later these editing errors should be handled correctly
|
126
|
+
#rescue Exception=>e
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
class ReportView
|
2
|
+
def initialize(screen, tickets, block)
|
3
|
+
@screen = screen
|
4
|
+
@mode = :all
|
5
|
+
@real_tickets = tickets
|
6
|
+
@tickets = tickets
|
7
|
+
@refresh = block
|
8
|
+
@highlight = 0
|
9
|
+
display_lines
|
10
|
+
end
|
11
|
+
|
12
|
+
def refresh
|
13
|
+
@real_tickets = @refresh.call
|
14
|
+
toggle_open_tickets
|
15
|
+
toggle_open_tickets
|
16
|
+
end
|
17
|
+
|
18
|
+
def toggle_open_tickets
|
19
|
+
@highlight = 0
|
20
|
+
if(@mode == :all)
|
21
|
+
@tickets = []
|
22
|
+
@real_tickets.each do |t|
|
23
|
+
if(t.status == "Open")
|
24
|
+
@tickets << t
|
25
|
+
end
|
26
|
+
end
|
27
|
+
@mode = :open
|
28
|
+
elsif(@mode == :open || @mode == :search)
|
29
|
+
@tickets = @real_tickets
|
30
|
+
@mode = :all
|
31
|
+
end
|
32
|
+
display_lines
|
33
|
+
end
|
34
|
+
|
35
|
+
def run_search(term)
|
36
|
+
@highlight = 0
|
37
|
+
@tickets = []
|
38
|
+
@real_tickets.each do |t|
|
39
|
+
if(t.match term)
|
40
|
+
@tickets << t
|
41
|
+
end
|
42
|
+
end
|
43
|
+
@mode = :search
|
44
|
+
display_lines
|
45
|
+
end
|
46
|
+
|
47
|
+
def search
|
48
|
+
label = TextLineWidget.new(@screen, "search: ", 0, @screen.maxy-1, 40)
|
49
|
+
line = TextLineWidget.new(@screen, "", 10, @screen.maxy-1, 40)
|
50
|
+
label.draw
|
51
|
+
line.draw
|
52
|
+
while true
|
53
|
+
c = Curses.getch
|
54
|
+
if(c == "\n" || !line.handle(c))
|
55
|
+
run_search(line.value)
|
56
|
+
break
|
57
|
+
else
|
58
|
+
label.draw
|
59
|
+
line.draw
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def display_lines
|
65
|
+
@screen.clear
|
66
|
+
|
67
|
+
@screen.setpos(0,0)
|
68
|
+
@screen.attron(Curses.color_pair(1));
|
69
|
+
@screen.addstr("Issue Overview - [a]dd new issue, [t]oggle open issues, [q]uit")
|
70
|
+
@screen.attroff(Curses.color_pair(1));
|
71
|
+
|
72
|
+
start = 0
|
73
|
+
ylimit = @screen.maxy-1
|
74
|
+
upper = ylimit-1
|
75
|
+
if(@highlight >= ylimit)
|
76
|
+
start = ylimit*(@highlight/ylimit).to_i
|
77
|
+
upper = start + ylimit - 1
|
78
|
+
end
|
79
|
+
|
80
|
+
if(@tickets.empty?)
|
81
|
+
return
|
82
|
+
end
|
83
|
+
|
84
|
+
@tickets[start..upper].each_with_index{|tick, idx|
|
85
|
+
line = tick.title
|
86
|
+
line_id = idx+start
|
87
|
+
@screen.setpos(idx+1, 0)
|
88
|
+
@screen.addstr(line_id.to_s+".")
|
89
|
+
@screen.setpos(idx+1, 4)
|
90
|
+
if(tick.status)
|
91
|
+
@screen.addstr("["+tick.status[0]+"]")
|
92
|
+
end
|
93
|
+
@screen.setpos(idx+1, 9)
|
94
|
+
if(@highlight == line_id)
|
95
|
+
@screen.attron Curses::A_BOLD
|
96
|
+
@screen.addstr(line)
|
97
|
+
@screen.attroff Curses::A_BOLD
|
98
|
+
else
|
99
|
+
@screen.addstr(line)
|
100
|
+
end
|
101
|
+
}
|
102
|
+
@screen.setpos(0,0)
|
103
|
+
@screen.refresh
|
104
|
+
end
|
105
|
+
|
106
|
+
def scroll_up(draw=true)
|
107
|
+
if(@highlight > 0)
|
108
|
+
@highlight -= 1
|
109
|
+
display_lines if draw
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def scroll_down(draw=true)
|
114
|
+
if(@highlight < @tickets.length-1)
|
115
|
+
@highlight += 1
|
116
|
+
display_lines if draw
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def interact
|
121
|
+
while true
|
122
|
+
result = true
|
123
|
+
c = Curses.getch
|
124
|
+
case c
|
125
|
+
when ?t
|
126
|
+
toggle_open_tickets
|
127
|
+
when ?a
|
128
|
+
at = AddTicketView.new(@screen)
|
129
|
+
@screen.clear
|
130
|
+
at.display
|
131
|
+
ret = at.interact
|
132
|
+
if(ret == :do_refresh)
|
133
|
+
refresh
|
134
|
+
end
|
135
|
+
@screen.clear
|
136
|
+
display_lines
|
137
|
+
when Curses::KEY_DOWN, Curses::KEY_CTRL_N, ?j
|
138
|
+
result = scroll_down
|
139
|
+
when Curses::KEY_UP, Curses::KEY_CTRL_P, ?k
|
140
|
+
result = scroll_up
|
141
|
+
when Curses::KEY_NPAGE, ?\s # white space
|
142
|
+
for i in 0..(@screen.maxy - 2)
|
143
|
+
scroll_down(false)
|
144
|
+
end
|
145
|
+
display_lines
|
146
|
+
when Curses::KEY_PPAGE
|
147
|
+
for i in 0..(@screen.maxy - 2)
|
148
|
+
scroll_up(false)
|
149
|
+
end
|
150
|
+
display_lines
|
151
|
+
when Curses::KEY_RIGHT, Curses::KEY_ENTER, ?\n, 13
|
152
|
+
tv = TicketView.new(@screen, @tickets[@highlight])
|
153
|
+
@screen.clear
|
154
|
+
tv.display
|
155
|
+
ret = tv.interact
|
156
|
+
@screen.clear
|
157
|
+
display_lines
|
158
|
+
when "/"
|
159
|
+
search
|
160
|
+
when ?q
|
161
|
+
break
|
162
|
+
else
|
163
|
+
@screen.setpos(0,0)
|
164
|
+
@screen.addstr("[unknown key `#{Curses.keyname(c)}'=#{c}] ")
|
165
|
+
end
|
166
|
+
@screen.setpos(0,0)
|
167
|
+
end
|
168
|
+
Curses.close_screen
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
class TicketView
|
4
|
+
def initialize(screen, ticket)
|
5
|
+
@screen = screen
|
6
|
+
@ticket = ticket
|
7
|
+
end
|
8
|
+
|
9
|
+
def display
|
10
|
+
@screen.clear
|
11
|
+
@screen.setpos(0,0)
|
12
|
+
@screen.attron(Curses.color_pair(1));
|
13
|
+
@screen.addstr("Issue Details - [f]ix [q]uit")
|
14
|
+
@screen.attroff(Curses.color_pair(1));
|
15
|
+
|
16
|
+
|
17
|
+
@screen.setpos(1,0)
|
18
|
+
@screen.addstr("Title: #{@ticket.title}")
|
19
|
+
@screen.setpos(2,0)
|
20
|
+
@screen.addstr("Status: #{@ticket.status}")
|
21
|
+
@screen.setpos(3,0)
|
22
|
+
@screen.addstr("Priority: #{@ticket.priority}")
|
23
|
+
@screen.setpos(4,0)
|
24
|
+
@screen.addstr("Type: #{@ticket.type}")
|
25
|
+
@screen.setpos(5,0)
|
26
|
+
@screen.addstr("Subsystem: #{@ticket.subsystem}")
|
27
|
+
@screen.setpos(6,0)
|
28
|
+
@screen.addstr("Comments:")
|
29
|
+
ln = 7
|
30
|
+
@ticket.comments.split("\n").each do |l|
|
31
|
+
@screen.setpos(ln,0)
|
32
|
+
@screen.addstr(l)
|
33
|
+
ln += 1
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def interact
|
38
|
+
while true
|
39
|
+
result = true
|
40
|
+
c = Curses.getch
|
41
|
+
case c
|
42
|
+
when Curses::KEY_LEFT
|
43
|
+
break
|
44
|
+
when ?q
|
45
|
+
break
|
46
|
+
when ?f
|
47
|
+
@ticket.resolve
|
48
|
+
display
|
49
|
+
when ?e
|
50
|
+
edit
|
51
|
+
display
|
52
|
+
else
|
53
|
+
@screen.setpos(0,0)
|
54
|
+
@screen.addstr("[unknown key `#{Curses.keyname(c)}'=#{c}] ")
|
55
|
+
end
|
56
|
+
@screen.setpos(0,0)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def edit
|
61
|
+
tmp = File.open("fs-ticket-edit.txt", "w+")
|
62
|
+
tmp.puts({"title" => @ticket.title,
|
63
|
+
"status" => @ticket.status,
|
64
|
+
"priority" => @ticket.priority,
|
65
|
+
"type" => @ticket.type,
|
66
|
+
"comment" => @ticket.comments}.to_yaml)
|
67
|
+
tmp.close
|
68
|
+
|
69
|
+
system("vim fs-ticket-edit.txt")
|
70
|
+
|
71
|
+
#Restore curses options
|
72
|
+
Curses.raw
|
73
|
+
Curses.nonl
|
74
|
+
#Curses.cbreak
|
75
|
+
Curses.noecho
|
76
|
+
Curses.curs_set(0)
|
77
|
+
@screen.scrollok(true)
|
78
|
+
@screen.keypad(true)
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
|
2
|
+
class ButtonWidget
|
3
|
+
attr_accessor :active
|
4
|
+
def initialize(screen, value, x, y)
|
5
|
+
@active = false
|
6
|
+
@screen = screen
|
7
|
+
@value = value
|
8
|
+
@x = x
|
9
|
+
@y = y
|
10
|
+
end
|
11
|
+
|
12
|
+
def handle(chr)
|
13
|
+
if(chr == "\n" || chr == 13)
|
14
|
+
true
|
15
|
+
else
|
16
|
+
false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def draw
|
21
|
+
value = @value
|
22
|
+
if(value.empty?)
|
23
|
+
value = "XXXXX"
|
24
|
+
end
|
25
|
+
|
26
|
+
@screen.attron Curses::A_BOLD if @active
|
27
|
+
|
28
|
+
@screen.setpos(@y,@x)
|
29
|
+
@screen.addstr "-"*(2+value.length)
|
30
|
+
@screen.setpos(@y+1,@x)
|
31
|
+
@screen.addstr("|"+value+"|")
|
32
|
+
@screen.setpos(@y+2,@x)
|
33
|
+
@screen.addstr "-"*(2+value.length)
|
34
|
+
|
35
|
+
@screen.attroff Curses::A_BOLD if @active
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
class DropdownWidget
|
2
|
+
attr_reader :active
|
3
|
+
attr_reader :options
|
4
|
+
attr_reader :value
|
5
|
+
|
6
|
+
def active=(a)
|
7
|
+
if(!a)
|
8
|
+
@mode = :normal
|
9
|
+
end
|
10
|
+
@active = a
|
11
|
+
end
|
12
|
+
|
13
|
+
def options=(o)
|
14
|
+
@options = o
|
15
|
+
fix_sel
|
16
|
+
end
|
17
|
+
|
18
|
+
def value=(v)
|
19
|
+
@value = v
|
20
|
+
fix_sel
|
21
|
+
end
|
22
|
+
|
23
|
+
#Fix the selection when either the value or options change
|
24
|
+
def fix_sel
|
25
|
+
@options.each_with_index do |o, idx|
|
26
|
+
if(@value == o)
|
27
|
+
@sel = idx
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(screen, value, x, y, w)
|
33
|
+
@active = false
|
34
|
+
@screen = screen
|
35
|
+
@value = value
|
36
|
+
@x = x
|
37
|
+
@y = y
|
38
|
+
@w = w
|
39
|
+
@mode = :normal
|
40
|
+
@options = [value]
|
41
|
+
@sel = 0
|
42
|
+
end
|
43
|
+
|
44
|
+
def draw
|
45
|
+
if(@mode == :normal)
|
46
|
+
@screen.setpos(@y,@x)
|
47
|
+
@screen.attron Curses::A_BOLD if @active
|
48
|
+
@screen.addstr(@value)
|
49
|
+
@screen.attroff Curses::A_BOLD if @active
|
50
|
+
elsif(@mode == :selecting)
|
51
|
+
sel_width = @w-2
|
52
|
+
@screen.setpos(@y,@x)
|
53
|
+
@screen.addstr("-"*@w)
|
54
|
+
|
55
|
+
@options.each_with_index do |opt, idx|
|
56
|
+
@screen.setpos(@y+idx+1,@x)
|
57
|
+
padded_str = " "*sel_width
|
58
|
+
opt.chars.each_with_index do |chr, idx|
|
59
|
+
padded_str[idx] = chr
|
60
|
+
end
|
61
|
+
@screen.attron Curses::A_BOLD if idx == @sel
|
62
|
+
@screen.addstr "|"+padded_str+"|"
|
63
|
+
@screen.attroff Curses::A_BOLD if idx == @sel
|
64
|
+
end
|
65
|
+
|
66
|
+
@screen.setpos(@y+@options.length+1,@x)
|
67
|
+
@screen.addstr("-"*@w)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def handle(chr)
|
72
|
+
if(chr == Curses::KEY_RIGHT)
|
73
|
+
@mode = :selecting
|
74
|
+
@value = @options[@sel]
|
75
|
+
true
|
76
|
+
elsif(chr == Curses::KEY_LEFT)
|
77
|
+
@mode = :normal
|
78
|
+
true
|
79
|
+
elsif(chr == Curses::KEY_DOWN && @mode == :selecting)
|
80
|
+
@sel = [@sel+1, @options.length-1].min
|
81
|
+
@value = @options[@sel]
|
82
|
+
true
|
83
|
+
elsif(chr == Curses::KEY_UP && @mode == :selecting)
|
84
|
+
@sel = [@sel-1, 0].max
|
85
|
+
@value = @options[@sel]
|
86
|
+
true
|
87
|
+
else
|
88
|
+
false
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class TextFieldWidget
|
2
|
+
attr_accessor :active
|
3
|
+
attr_accessor :value
|
4
|
+
def initialize(screen, value, x, y, w, h)
|
5
|
+
@active = false
|
6
|
+
@screen = screen
|
7
|
+
@value = value
|
8
|
+
@x = x
|
9
|
+
@y = y
|
10
|
+
@w = w
|
11
|
+
@h = h
|
12
|
+
end
|
13
|
+
|
14
|
+
def handle(chr)
|
15
|
+
if(chr.class == String && chr.match(/[[:print:]\n]/))
|
16
|
+
@value += chr
|
17
|
+
true
|
18
|
+
elsif(chr == Curses::KEY_ENTER || chr == 13)
|
19
|
+
@value += "\n"
|
20
|
+
true
|
21
|
+
elsif(chr == Curses::KEY_BACKSPACE || chr == 127)
|
22
|
+
@value = @value[0..-2]
|
23
|
+
true
|
24
|
+
else
|
25
|
+
false
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def draw
|
30
|
+
value = @value
|
31
|
+
if(value.empty?)
|
32
|
+
(0..@h).each do |x|
|
33
|
+
value += "_"*@w+"\n"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
@screen.setpos(@y,@x)
|
38
|
+
@screen.attron Curses::A_BOLD if @active
|
39
|
+
@screen.addstr(value)
|
40
|
+
@screen.attroff Curses::A_BOLD if @active
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class TextLineWidget
|
2
|
+
attr_accessor :active
|
3
|
+
attr_accessor :value
|
4
|
+
def initialize(screen, value, x, y, w)
|
5
|
+
@active = false
|
6
|
+
@screen = screen
|
7
|
+
@value = value
|
8
|
+
@x = x
|
9
|
+
@y = y
|
10
|
+
@w = w
|
11
|
+
end
|
12
|
+
|
13
|
+
def handle(chr)
|
14
|
+
if(chr.class == String && chr.match(/[a-zA-Z\- .]/))
|
15
|
+
@value += chr
|
16
|
+
true
|
17
|
+
elsif(chr == Curses::KEY_BACKSPACE || chr == 127)
|
18
|
+
@value = @value[0..-2]
|
19
|
+
true
|
20
|
+
else
|
21
|
+
false
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def draw
|
26
|
+
value = @value
|
27
|
+
if(value.empty?)
|
28
|
+
value = "_________________"
|
29
|
+
end
|
30
|
+
|
31
|
+
@screen.setpos(@y,@x)
|
32
|
+
@screen.attron Curses::A_BOLD if @active
|
33
|
+
@screen.addstr(value)
|
34
|
+
@screen.attroff Curses::A_BOLD if @active
|
35
|
+
end
|
36
|
+
end
|
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fs-ticket
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mark McCurry
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-12-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: curses
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: sqlite3
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.3'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.3'
|
41
|
+
description: |
|
42
|
+
fs-ticket is a command line interface to issues/tickets stored in a fossil
|
43
|
+
repository with a design influienced by mutt.
|
44
|
+
This tool supports basic operations such as adding, editing, searching, and
|
45
|
+
viewing tickets in the terminal.
|
46
|
+
email: mark.d.mccurry@gmail.com
|
47
|
+
executables:
|
48
|
+
- fs-ticket
|
49
|
+
extensions: []
|
50
|
+
extra_rdoc_files: []
|
51
|
+
files:
|
52
|
+
- LICENSE.txt
|
53
|
+
- bin/fs-ticket
|
54
|
+
- lib/fs-ticket.rb
|
55
|
+
- lib/render.rb
|
56
|
+
- lib/ticket.rb
|
57
|
+
- lib/view/add-ticket-view.rb
|
58
|
+
- lib/view/report-view.rb
|
59
|
+
- lib/view/ticket-view.rb
|
60
|
+
- lib/widget/button-widget.rb
|
61
|
+
- lib/widget/dropdown-widget.rb
|
62
|
+
- lib/widget/text-field-widget.rb
|
63
|
+
- lib/widget/text-line-widget.rb
|
64
|
+
homepage: http://rubygems.org/gems/fs-ticket
|
65
|
+
licenses:
|
66
|
+
- MIT
|
67
|
+
metadata: {}
|
68
|
+
post_install_message:
|
69
|
+
rdoc_options: []
|
70
|
+
require_paths:
|
71
|
+
- lib
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
requirements: []
|
83
|
+
rubyforge_project:
|
84
|
+
rubygems_version: 2.4.5.1
|
85
|
+
signing_key:
|
86
|
+
specification_version: 4
|
87
|
+
summary: Ncurses interface to tickets stored in a fossil-scm repo
|
88
|
+
test_files: []
|
89
|
+
has_rdoc:
|