fs-ticket 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env ruby
2
+ require 'fs-ticket'
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: