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 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: