tiqbi 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: