#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
require 'curses'
include Curses
init_screen
noecho
curs_set(0)
# (外部テキストファイルを読み込む) -> 多次元連想配列に変換 { 項目名(Str) => { ページ(Int) => { 項(Str) => 内容(Str), ... }, ... }, ... }
module Kernel
def sortout(*mrk)
return self.chomp if mrk.nil? || mrk.empty?
h = {}
self.split(mrk[0]).each do |s|
unless s.empty?
t = s.split("\n", 2)
t[0] = t[0].to_i if /\d+/.match?(t[0])
h[t[0]] = t[1].sortout(*mrk[1..])
end
end
h
end
def flatten_hash
Hash === self ? self.flatten.map { |h| h.flatten_hash }.flatten : self
end
end
pages = File.read('my_ruby_guide.txt').sortout("####", "###", "##")
notes = File.read('my_ruby_notes.txt').sortout("####", "###", "##")
$data = (pages.to_a + notes.to_a).to_h
pages_title = pages.map { |k, *| k }.sort
notes_title = notes.map { |k, *| k }
titles = pages_title + notes_title
def display(posy = stdscr.cury, posx = stdscr.curx, str) #(Windowが一つなので、) setpos -> addstr -> refresh を一元化
setpos(posy, posx)
addstr(str.to_s)
refresh
end
module Display
def page
@page
end
def next
if @page >= @pages_all then @page = 1 else @page += 1 end
end
def prev
if @page <= 1 then @page = @pages_all else @page -= 1 end
end
def reset
@page = 1
end
def show
end
class List
include Display
def initialize(list, name = "", errmsg = "リストが空です")
@list = list
@name = name.to_s
@err_msg = errmsg
@page = 1
@pages_all = (@list.size * 0.1).ceil
end
def show(posy = 2, posx = 2)
if @list.empty?
display(posy, posx, @name + "(0〜9で選択)\t← 1/1 →\n" + "\s" * (posx + 2) + @err_msg)
else
display(posy, posx, @name + "(0〜9で選択)\t← #{@page}/#{@pages_all} →" + @list[(@page - 1) * 10 ... @page * 10]
.map { |s| "\n" + "\s" * posx + "#{@list.index(s) % 10}.\s" + s}.join)
end
end
end
class Page
include Display
NOTDONE = { 1 => { "Not Done" => "お探しのページはまだ書き途中のようです。\ns4にて最新版の更新をお待ちください。\n m(-_-)m" } }
NOTFOUND = { 1 => { "Not Found" => "お探しのページは存在しません。\nページ名が変更されたか、未作成の可能性があります。\nBキーを押してもとのページに戻ってください。\n\ns4などで知らせてくれたら、新しくページを作るかもしれません。" } }
def initialize(title)
@title = title
@data = $data.include?(@title) ? ( $data[title] == {} ? NOTDONE : $data[title] ) : NOTFOUND
@page = 1
@pages_all = @data.length
end
def show
if @page == @pages_all && @data[@page].include?("関連")
arr = @data[@page]["関連"].split("\n")
display(2, 2, @title + "\t← #{@page}/#{@pages_all} →\n\n\s<関連>\n" + arr.map { |s| " #{arr.index(s)}. " + s + "\n" }.join )
else
display(2, 2, @title + "\t← #{@page}/#{@pages_all} →\n" + @data[@page].map { |k, v| "\n\s<" + k + ">\n" + v + "\n" }.join )
end
end
end
end
include Display
List_pages = List.new(pages_title, "項目一覧")
List_notes = List.new(notes_title, "Rubyメモ一覧")
begin
history = ["menu"]
loop do
char = ""
clear
case history.last #履歴の最後(=現在のページ)
when "menu" #----------------------------------
display(0, 0, "My Ruby Guide へようこそ!
ここは自分(J2405 木村直仁)が、習ったこと・覚えたことをゆるめにデータベース?っぽくまとめた場所です。
Terminalを全画面表示にして使ってください。
Press s: 検索する
a: 項目一覧を見る
z: メモ一覧を見る
q: 終わる")
loop do
case getch
when "s"
history << "search_page"
when "a"
history << "pages_list"
List_pages.reset
when "z"
history << "notes_list"
List_notes.reset
when "q"
history << "end_guide"
else
redo
end
break
end
when "search_page" #---------------------------
display(1, 2, "検索: ")
echo
curs_set(1)
input = []
until (char = getch) == 10 #getstrより挙動がいい
case char
when 8
delch unless input.pop.nil?
else
input << char.to_s
end
end
keywords = input.join.split
noecho
curs_set(0)
matched = matched_init = matched_part = matched_text = [] #キーワードと 完全に/最初のみ/部分的に/本文中に 一致するものをそれぞれ配列に格納
keywords.each do |key|
matched |= titles.select { |s| s == key }
matched_init |= titles.select { |s| s.match?(key + ".+") }
matched_part |= titles.select { |s| s.match?(".+" + key + ".*") }
matched_text |= $data.select { |*, v| v.flatten_hash.any? { |s| String === s && s.match?(".*" + key + ".*") } }.map { |k, *| k }.sort
end
search_result = matched | matched_init | matched_part | matched_text #↑の4つを合体
list_search = List.new(search_result, "検索結果", "キーワードに一致するものが見つかりませんでした") # List に
loop do
char = ""
clear
display(0, 0, "< b: 戻る 他のキー: 再検索")
display(1, 2, "検索:\s" + keywords.join("\s"))
list_search.show(3)
case char = getch
when 27 #矢印は"^["と"["と"(A〜Dのアルファベット1字)" の三文字、Cursesでは "^["( C-[ ) は 27 になる
getch
case getch
when "C" #→
list_search.next
when "D" #←
list_search.prev
end
redo
when "b"
history.pop
else
num = 10 * (list_search.page - 1) + char.to_i
if /\d/.match?(char.to_s) && search_result.length > num
history << search_result[num]
end
end
break
end
when "pages_list" #----------------------------
loop do
char = ""
clear
display(0, 0, "< b: 戻る 0〜9: ページを表示 他のキー: 再表示")
List_pages.show
case char = getch
when 27 #矢印
getch
case getch
when "C" #→
List_pages.next
when "D" #←
List_pages.prev
end
redo
when "b"
history.pop
else
num = 10 * (List_pages.page - 1) + char.to_i
if /\d/.match?(char.to_s) && pages_title.length > num
history << pages_title[num]
end
end
break
end
when "notes_list" #----------------------------
loop do
char = ""
clear
display(0, 0, "< b: 戻る r: 再表示")
List_notes.show
case char = getch
when 27 #矢印
getch
case getch
when "C" #→
List_notes.next
when "D" #←
List_notes.prev
end
redo
when "b"
history.pop
else
num = 10 * (List_notes.page - 1) + char.to_i
if /\d/.match?(char) && notes_title.length > num
history << notes_title[num]
end
end
break
end
when "end_guide" #-----------------------------
break
else #-----------------------------------------
page = Page.new(history.last) #履歴の最後(=現在のページ)を Page のインスタンスに
loop do
clear
display(0, 0, "< b: 戻る 0〜9: 関連ページへ 他のキー: 再表示")
page.show
case char = getch #入力
when 27 #矢印
getch
case getch
when "C" #→
page.next
when "D" #←
page.prev
end
when "b"
history.pop
break
else
unless $data[history.last].nil? #存在するページだったら
curpage = $data[history.last][page.page]
if /\d/.match?(char.to_s) && curpage.map { |k, v| k }.any? { |s| /関連.*/.match?(s) } && curpage.length == 1 #入力が数字 && 項目が "関連〜" 一つのみある
arr = curpage.to_a[0][1].split("\n")
if arr.length > char.to_i
history << arr[char.to_i]
break
end
end
end
end
end
end
end
ensure
close_screen
puts "終了します。"
end