tiqbi 0.0.1

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.
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /tmp
2
+ /logs
3
+ /vendor/bundles
4
+ /cache
5
+ /.bundle
6
+ /Gemfile.lock
7
+ /pkg
8
+
9
+ !.gitkeep
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2012 Takatoshi Matsumoto <toqoz403@gmail.com>
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 furnished
8
+ to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ 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/README.md ADDED
@@ -0,0 +1,9 @@
1
+ # tiqbi(乳首)
2
+ text-mode interface for qiita. 美
3
+
4
+ # Usage
5
+
6
+ ```sh
7
+ $ gem install tiqbi
8
+ $ tiqbi
9
+ ```
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
data/bin/tiqbi ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- coding: utf-8 -*-
3
+
4
+ require "slop"
5
+ argv = ARGV.dup
6
+ slop = Slop.new(:strict => true, :help => true)
7
+ slop.banner "$ tiqbi\n"
8
+
9
+ begin
10
+ slop.parse!(argv)
11
+ rescue => e
12
+ puts e
13
+ exit!
14
+ end
15
+ options = slop.to_hash
16
+ unless options[:help]
17
+ options.delete(:help)
18
+
19
+ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
20
+ require "tiqbi"
21
+ Tiqbi.run options
22
+ end
data/lib/tiqbi.rb ADDED
@@ -0,0 +1,57 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'logger'
4
+ require 'curses'
5
+ require 'sanitize'
6
+ require 'qiita'
7
+
8
+ require 'tiqbi/version'
9
+ require 'tiqbi/utils'
10
+ require 'tiqbi/view'
11
+
12
+ module Tiqbi
13
+ extend self
14
+ extend Utils
15
+ attr_accessor :view, :options
16
+
17
+ F_YELLOW_B_BLACK = 1
18
+ F_RED_B_BLACK = 2
19
+ F_BLUE_B_BLACK = 3
20
+
21
+ def run(options)
22
+ # initialize console
23
+ self.options = options
24
+ configure_curses
25
+ # get default window
26
+ curses_screen = Curses.stdscr
27
+ self.view = View.new(curses_screen)
28
+
29
+ # event loop
30
+ begin
31
+ loop {
32
+ ch = view.current_window.getch
33
+ view.on_input(ch)
34
+ }
35
+ ensure
36
+ Curses.close_screen
37
+ end
38
+ end
39
+
40
+ def root
41
+ Pathname.new(File.dirname(__FILE__)).join("..")
42
+ end
43
+
44
+ def logger
45
+ @logger ||= Logger.new root.join('logs/app.log')
46
+ end
47
+
48
+ def configure_curses
49
+ Curses.init_screen
50
+ Curses.start_color
51
+ Curses.init_pair F_YELLOW_B_BLACK, Curses::COLOR_YELLOW, Curses::COLOR_BLACK
52
+ Curses.init_pair F_BLUE_B_BLACK, Curses::COLOR_RED, Curses::COLOR_BLACK
53
+ Curses.init_pair F_RED_B_BLACK, Curses::COLOR_BLUE, Curses::COLOR_BLACK
54
+ Curses.cbreak
55
+ Curses.noecho
56
+ end
57
+ end
@@ -0,0 +1,41 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Tiqbi
4
+ module Utils
5
+ extend self
6
+
7
+ ENTITY_MAP = {
8
+ "&lt;" => "<",
9
+ "&gt;" => ">",
10
+ }
11
+
12
+ def format_str(str, length, &block)
13
+ clean_strs = split_str_with_width(unescape_entity(Sanitize.clean(str)), length).map { |s|
14
+ s.split(/\n|\r\n/)
15
+ }.flatten
16
+ clean_strs.each(&:chomp!)
17
+ return clean_strs[0] unless block_given?
18
+ clean_strs.each do |s|
19
+ yield s
20
+ end
21
+ end
22
+
23
+ def split_str_with_width(str, width = 40)
24
+ s = i = r = 0
25
+ str.each_char.reduce([]) { |res, c|
26
+ i += c.ascii_only? ? 1 : 2
27
+ r += 1
28
+ next res if i < width
29
+
30
+ res << str[s,r]
31
+ s += r
32
+ i = r = 0
33
+ res
34
+ } << str[s,r]
35
+ end
36
+
37
+ def unescape_entity(str)
38
+ str.gsub(/#{Regexp.union(ENTITY_MAP.keys)}/o) {|key| ENTITY_MAP[key] }
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,3 @@
1
+ module Tiqbi
2
+ VERSION = "0.0.1"
3
+ end
data/lib/tiqbi/view.rb ADDED
@@ -0,0 +1,93 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'tiqbi/window/main_window'
4
+ require 'tiqbi/window/detail_window'
5
+ require 'tiqbi/window/command_window'
6
+
7
+ module Tiqbi
8
+ class View
9
+ MAIN_WINDOW = 1
10
+ DETAIL_WINDOW = 2
11
+ COMMAND_WINDOW = 3
12
+
13
+ include Utils
14
+ attr_accessor :windows
15
+
16
+ def initialize(c_screen)
17
+ # calc size
18
+ scr_x = c_screen.maxx - c_screen.begx
19
+ scr_y = c_screen.maxy - c_screen.begy
20
+ main_h = scr_y / 2
21
+ cmd_h = 1
22
+ detail_h = main_h - cmd_h
23
+ # initialize all windows
24
+ @windows = {
25
+ MAIN_WINDOW => Window::MainWindow.new(c_screen, main_h, scr_x, 0, 0),
26
+ DETAIL_WINDOW => Window::DetailWindow.new(c_screen, detail_h, scr_x, main_h, 0),
27
+ COMMAND_WINDOW => Window::CommandWindow.new(c_screen, cmd_h, scr_x, main_h + detail_h, 0)
28
+ }
29
+
30
+ items = Qiita.item("") rescue []
31
+ @windows[MAIN_WINDOW].collection = items
32
+ @windows[MAIN_WINDOW].print
33
+ end
34
+
35
+ def on_input(input)
36
+ case input
37
+ when ?j
38
+ current_window.cursor_down
39
+ when ?k
40
+ current_window.cursor_up
41
+ when 10
42
+ if current_window == window(MAIN_WINDOW)
43
+ index = current_window.cursor.y
44
+ item = current_window.collection.at(index)
45
+ return unless item
46
+ item_detail = Qiita.item(item.uuid) rescue {}
47
+ window(DETAIL_WINDOW).item_loaded item_detail
48
+ switch_to_next_window window(DETAIL_WINDOW)
49
+ end
50
+ when ?q
51
+ switch_to_previous_window
52
+ end
53
+ end
54
+
55
+ def window(window_id)
56
+ windows[window_id]
57
+ end
58
+
59
+ def current_window
60
+ @current_window ||= window(MAIN_WINDOW)
61
+ end
62
+
63
+ def current_window=(win)
64
+ @current_window = win
65
+ end
66
+
67
+ def window_history
68
+ @window_history ||= []
69
+ end
70
+
71
+ def switch_to_next_window(win)
72
+ window_history << current_window
73
+ self.current_window = win
74
+ switch_to(current_window)
75
+ end
76
+
77
+ def switch_to_previous_window
78
+ previous_win = window_history.shift
79
+ unless previous_win
80
+ exit
81
+ else
82
+ current_window.virtual_close
83
+ switch_to(previous_win)
84
+ end
85
+ end
86
+
87
+ def switch_to(win)
88
+ self.current_window = win
89
+ win.setpos(win.cursor.y, win.cursor.x)
90
+ win.refresh
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,168 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'forwardable'
4
+ require 'tiqbi/window/collection'
5
+ require 'tiqbi/window/cursor'
6
+
7
+ module Tiqbi
8
+ module Window
9
+ class Base
10
+ attr_accessor :c_window, :cursor, :top_statement
11
+ attr_reader :collection
12
+
13
+ include Utils
14
+
15
+ def initialize(curses_parent, h, w, y, x)
16
+ @c_window = curses_parent.subwin(h, w, y, x)
17
+ @cursor = Cursor.new
18
+ @collection = Collection.new
19
+ @top_statement = 0
20
+ @initial_h = h
21
+ @initial_w = w
22
+ end
23
+
24
+
25
+ def collection=(col)
26
+ unless col.is_a?(Collection)
27
+ @collection = Collection.new(col)
28
+ else
29
+ @collection = col
30
+ end
31
+ end
32
+
33
+ def in_color(color, &block)
34
+ c_window.attron(Curses.color_pair(color))
35
+ yield self
36
+ c_window.attroff(Curses::A_COLOR)
37
+ end
38
+
39
+ def print
40
+ in_pos(0, 0) {
41
+ collection.all[0..(c_window.maxy - 1)].each_with_index { |e, virtual_index|
42
+ draw_at(virtual_index)
43
+ cursor.down
44
+ }
45
+ cursor.clear
46
+ }
47
+ refresh
48
+ end
49
+
50
+ def clear_collection
51
+ self.collection.clear if collection
52
+ self.cursor.clear
53
+ self.top_statement = 0
54
+ end
55
+
56
+ def restore_initial_size!
57
+ resize!(@initial_h, @initial_w)
58
+ end
59
+
60
+ def resize!(h, w)
61
+ resize(h, w)
62
+ clear
63
+ print
64
+ end
65
+
66
+ def box!(*args)
67
+ box(*args)
68
+ refresh
69
+ end
70
+
71
+ def in_pos(y, x, &block)
72
+ setpos(y, x)
73
+ yield self
74
+ setpos(y, x)
75
+ end
76
+
77
+ def draw_at(virtual_index, color = true)
78
+ setpos(cursor.y, 0)
79
+ clrtoeol
80
+ addstr(collection.at(virtual_index))
81
+ end
82
+
83
+ def normalize_line
84
+ in_pos(cursor.y, cursor.x) { draw_at(cursor.y + top_statement) }
85
+ refresh
86
+ end
87
+
88
+ def enhansive_line
89
+ in_color(Tiqbi::F_YELLOW_B_BLACK) {
90
+ in_pos(cursor.y, cursor.x) { draw_at(cursor.y + top_statement, false) }
91
+ refresh
92
+ }
93
+ end
94
+
95
+ def change_focus_line(&block)
96
+ normalize_line
97
+ yield
98
+ enhansive_line
99
+ end
100
+
101
+ def cursor_up
102
+ if cursor.y <= 0
103
+ scroll_up
104
+ else
105
+ change_focus_line {
106
+ cursor.up
107
+ }
108
+ end
109
+ end
110
+
111
+ def cursor_down
112
+ return if cursor_on_end_of_collection?
113
+ if cursor.y >= (maxy - 1)
114
+ scroll_down
115
+ else
116
+ change_focus_line {
117
+ cursor.down
118
+ }
119
+ end
120
+ end
121
+
122
+ def cursor_on_end_of_collection?
123
+ cursor.y >= (collection.size - 1)
124
+ end
125
+
126
+ def scroll_up
127
+ if top_statement > 0
128
+ change_focus_line {
129
+ scrl(-1)
130
+ in_pos(cursor.y, 0) {
131
+ self.top_statement -= 1
132
+ }
133
+ refresh
134
+ }
135
+ end
136
+ end
137
+
138
+ def scroll_down
139
+ if top_statement + maxy < collection.size
140
+ change_focus_line {
141
+ in_pos(cursor.y, 0) {
142
+ scrl(1)
143
+ self.top_statement += 1
144
+ }
145
+ refresh
146
+ }
147
+ end
148
+ end
149
+
150
+ def virtual_close
151
+ clear_collection
152
+ resize!(0, 0)
153
+ end
154
+
155
+ def br
156
+ ''
157
+ end
158
+
159
+ def hr
160
+ '-' * (maxx - 1)
161
+ end
162
+
163
+ extend Forwardable
164
+ # delegate to @window(Curses::Window)
165
+ def_delegators :@c_window, :clear, :addstr, :maxx, :maxy, :begx, :begy, :box, :border, :subwin, :standout, :standend, :getch, :setpos, :scrl, :resize, :clrtoeol, :refresh
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,35 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Tiqbi
4
+ module Window
5
+ class Collection
6
+ attr_accessor :collection
7
+ def initialize(collection = [])
8
+ @collection = collection
9
+ end
10
+ def at(index, &block)
11
+ line = @collection[index]
12
+ return line unless block_given?
13
+ yield line
14
+ end
15
+ def push(value)
16
+ @collection << value
17
+ end
18
+ def all
19
+ @collection
20
+ end
21
+ def size
22
+ @collection.size
23
+ end
24
+ def insert(index, value)
25
+ @collection.insert(index, value)
26
+ end
27
+ def line_size
28
+ @collection.size
29
+ end
30
+ def clear
31
+ @collection = []
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,13 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require "tiqbi/window/base"
4
+
5
+ module Tiqbi
6
+ module Window
7
+ class CommandWindow < Base
8
+ def initialize(*args)
9
+ super(*args)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,35 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Tiqbi
4
+ module Window
5
+ class Cursor
6
+ attr_accessor :x, :y
7
+
8
+ def initialize
9
+ @x = 0
10
+ @y = 0
11
+ end
12
+
13
+ def clear
14
+ self.x = 0
15
+ self.y = 0
16
+ end
17
+
18
+ def up
19
+ self.y -= 1
20
+ end
21
+
22
+ def down
23
+ self.y += 1
24
+ end
25
+
26
+ def left
27
+ self.x -= 1
28
+ end
29
+
30
+ def right
31
+ self.x += 1
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,37 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require "tiqbi/window/base"
4
+
5
+ module Tiqbi
6
+ module Window
7
+ class DetailWindow < Base
8
+ include Tiqbi::Utils
9
+
10
+ def initialize(*args)
11
+ super(*args)
12
+ @c_window.scrollok true
13
+ end
14
+
15
+ def item_loaded(item)
16
+ restore_initial_size!
17
+
18
+ col = []
19
+ # Add title
20
+ [hr, 'Title', hr].each { |e| col << e }
21
+ format_str(item.title, maxx - 1) { |s| col << s }
22
+ # Add body
23
+ [hr, 'Body', hr].each { |e| col << e }
24
+ format_str(item.body, maxx - 1) { |s| col << s }
25
+ # add Comment
26
+ [hr, 'Comment', hr].each { |e| col << e }
27
+ item.comments.each do |c|
28
+ format_str(c.body, maxx - 1) { |s| col << s }
29
+ col << hr
30
+ end
31
+
32
+ self.collection = col
33
+ print
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,41 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require "tiqbi/window/base"
4
+
5
+ module Tiqbi
6
+ module Window
7
+ class MainWindow < Base
8
+ def initialize(*args)
9
+ # create subwindow
10
+ super(*args)
11
+ # scroll on
12
+ @c_window.scrollok(true)
13
+ end
14
+
15
+ def draw_at(virtual_index, color = true)
16
+ line = collection.at(virtual_index) do |item|
17
+ comment_count = "c: #{item.comment_count || 0} "
18
+ score = "s: #{item.stock_count || 0} "
19
+ title_w = maxx - "#{comment_count}#{score.size}".size - 10
20
+ title = format_str(item.title || '', title_w)
21
+ [
22
+ { color: Tiqbi::F_YELLOW_B_BLACK, value: comment_count },
23
+ { color: Tiqbi::F_RED_B_BLACK, value: score },
24
+ { color: 0, value: title }
25
+ ]
26
+ end
27
+ x = 0
28
+ clrtoeol
29
+ line.each do |e|
30
+ setpos(cursor.y, x)
31
+ if color
32
+ in_color(e[:color] || 0) { addstr(e[:value]) }
33
+ else
34
+ addstr(e[:value])
35
+ end
36
+ x += e[:value].size
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
data/tiqbi.gemspec ADDED
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/tiqbi/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Takatoshi Matsumoto"]
6
+ gem.email = ["toqoz403@gmail.com"]
7
+ gem.description = "text - mode interface for qiita"
8
+ gem.summary = "tiqbi"
9
+ gem.homepage = "https://github.com/ToQoz/tiqbi"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "tiqbi"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Tiqbi::VERSION
17
+
18
+ gem.add_dependency "slop"
19
+ gem.add_dependency "qiita", ">= 0.0.3"
20
+ gem.add_dependency "sanitize"
21
+ end
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tiqbi
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Takatoshi Matsumoto
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-10-19 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: slop
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: qiita
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: 0.0.3
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: 0.0.3
46
+ - !ruby/object:Gem::Dependency
47
+ name: sanitize
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: text - mode interface for qiita
63
+ email:
64
+ - toqoz403@gmail.com
65
+ executables:
66
+ - tiqbi
67
+ extensions: []
68
+ extra_rdoc_files: []
69
+ files:
70
+ - .gitignore
71
+ - Gemfile
72
+ - LICENSE
73
+ - README.md
74
+ - Rakefile
75
+ - bin/tiqbi
76
+ - cache/.gitkeep
77
+ - lib/tiqbi.rb
78
+ - lib/tiqbi/utils.rb
79
+ - lib/tiqbi/version.rb
80
+ - lib/tiqbi/view.rb
81
+ - lib/tiqbi/window/base.rb
82
+ - lib/tiqbi/window/collection.rb
83
+ - lib/tiqbi/window/command_window.rb
84
+ - lib/tiqbi/window/cursor.rb
85
+ - lib/tiqbi/window/detail_window.rb
86
+ - lib/tiqbi/window/main_window.rb
87
+ - tiqbi.gemspec
88
+ homepage: https://github.com/ToQoz/tiqbi
89
+ licenses: []
90
+ post_install_message:
91
+ rdoc_options: []
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ! '>='
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ none: false
102
+ requirements:
103
+ - - ! '>='
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ requirements: []
107
+ rubyforge_project:
108
+ rubygems_version: 1.8.24
109
+ signing_key:
110
+ specification_version: 3
111
+ summary: tiqbi
112
+ test_files: []
113
+ has_rdoc: