__write_stderr(msg)
click to toggle source
def __write_stderr (msg)
$stderr.write(msg.gsub(FOLLOW_DIR, '{MIKUTTER_DIR}')+"\n")
end
add_muted_user(user)
click to toggle source
def add_muted_user(user)
type_strict user => User
atomic{
muted = (UserConfig[:muted_users] ||= []).melt
muted << user.idname
UserConfig[:muted_users] = muted } end
add_tab(saved_search)
click to toggle source
タブを保存する
Args
- saved_search
-
saved search
def add_tab(saved_search)
type_strict saved_search => SavedSearch
tab(saved_search.slug, saved_search.name) do
set_icon MUI::Skin.get("savedsearch.png")
timeline saved_search.slug end
rewind_timeline(saved_search)
register_cache(saved_search)
timelines[saved_search.id] = saved_search end
addsupport(cond, element_rule = {}, &block)
click to toggle source
def addsupport(cond, element_rule = {}, &block)
element_rule.freeze
if block == nil
::Gtk::TimeLine.addopenway(cond){ |shrinked_url, cancel|
url = MessageConverters.expand_url_one(shrinked_url)
Delayer.new(Delayer::NORMAL, Thread.new{ imgurlresolver(url, element_rule) }){ |url|
display(url, cancel)
}
}
else
::Gtk::TimeLine.addopenway(cond){ |shrinked_url, cancel|
url = MessageConverters.expand_url_one(shrinked_url)
Delayer.new(Delayer::NORMAL, Thread.new{
imgurlresolver(url, element_rule){ |url| block.call(url, cancel) }
}) {|url|
display(url, cancel)
}
}
end
end
already_exists_another_instance?()
click to toggle source
def already_exists_another_instance?
if FileTest.exist? Environment::PIDFILE then
open(Environment::PIDFILE){|out|
pid = out.read
if pid_exist?(pid) then
notice "process #{pid} already exist"
return true
else
File::delete(Environment::PIDFILE)
notice "pid file exist. however, process #{pid} not found"
return false
end
}
error 'pid file can\t open'
exit
else
notice 'pid file not found'
end
return false
end
appear_counter()
click to toggle source
def appear_counter
@appear_counter ||= TimeLimitedStorage.new end
appear_limit()
click to toggle source
キャッシュする出現回数のしきい値を返す
def appear_limit
UserConfig[:image_file_cache_appear_limit] || 32 end
append_message(source, messages)
click to toggle source
def append_message(source, messages)
type_strict source => String, messages => Enumerable
tabs = extract_tabs.values.select{ |r| r[:sources] && r[:sources].include?(source) }
return if tabs.empty?
messages.each{ |message|
message = message.retweet_source if message.retweet_source
table = MIKU::SymbolTable.new(nil,
:user => MIKU::Cons.new(message.idname, nil),
:body => MIKU::Cons.new(message.to_s, nil),
:source => MIKU::Cons.new(message[:sources], nil),
:message => MIKU::Cons.new(message, nil))
tabs.each{ |record|
timeline(record[:slug]) << message if miku(record[:sexp], table) } } end
argument_parser()
click to toggle source
def argument_parser()
$debug = false
$learnable = true
$daemon = false
$interactive = false
$quiet = false
ARGV.each{ |arg|
case arg
when '-i'
$interactive = true
when '--debug'
$debug = true
seterrorlevel(:notice)
when '-d'
$daemon = true
when '-l'
$learnable = false
when '-q'
$quiet = true
end
}
end
assert_hasmethods(obj, *methods)
click to toggle source
デバッグモードの場合、obj methods に指定されたメソッドをひとつでも持っていない場合、
RuntimeErrorを発生させる。 obj を返す。
def assert_hasmethods(obj, *methods)
if Mopt.debug
methods.all?{ |m|
raise RuntimeError, "#{obj.inspect} should have method #{m}" if not obj.methods.include? m
}
end
obj
end
assert_type(type, obj)
click to toggle source
デバッグモードの場合、obj が type とis_a?関係にない場合、RuntimeErrorを発生させる。
obj を返す。
def assert_type(type, obj)
if Mopt.debug and not obj.is_a?(type)
raise RuntimeError, "#{obj} should be type #{type}"
end
obj
end
atomic() { || ... }
click to toggle source
共通のMutexで処理を保護して実行する。 atomicブロックで囲まれたコードは、別々のスレッドで同時に実行されない。
def atomic
$atomic.synchronize{ yield }
end
available_lists()
click to toggle source
自分がフォローしているリストを返す
Return
自分が作成したリストの配列(TypedArray)
def available_lists
@available_lists ||= UserLists.new.freeze end
background_color()
click to toggle source
def background_color
style = ::Gtk::Style.new()
style.set_bg(::Gtk::STATE_NORMAL, 0xFF ** 2, 0xFF ** 2, 0xFF ** 2)
style end
bg_system(*args)
click to toggle source
コマンドをバックグラウンドで起動することを覗いては system() と同じ
def bg_system(*args)
Process.detach(spawn(*args))
end
biif(a, b, *procs, &last_proc)
click to toggle source
複数条件if 条件を二つ持ち、a&b,a&!b,!a&b,!a&!bの4パターンに分岐する
procs配列は前から順番に、上記の条件の順番に対応している。 評価されたブロックの戻り値を返す。ブロックがない場合はfalseを返す。
なお、ブロックはa,bを引数に取り呼び出される。 誰得すぎて自分でも使ってないけどどこかで使った気がするなぁ
def biif(a, b, *procs, &last_proc)
procs.push(last_proc)
num = 0
if not(a) then
num += 2
end
if not(b) then
num += 1
end
if(procs[num]) then
procs[num].call(a,b)
end
end
bool(val)
click to toggle source
値が真であるならtrueを返す。遅延評価オブジェクトでも正確に判断することができる。
def bool(val)
not(val.nil? or val.is_a?(FalseClass)) end
boot()
click to toggle source
def boot()
logfile(Environment::TMPDIR + Environment::ACRO)
argument_parser()
Service.new(true)
if(already_exists_another_instance?) then
error('Already exist another instance')
exit!
end
include File::Constants
if $daemon then
WEBrick::Daemon.start{
main()
}
else
main()
end
return true
end
boot!(profile)
click to toggle source
イベントの待受を開始する。 profile がtrueなら、プロファイリングした結果を一時ディレクトリに保存する
def boot!(profile)
Gtk.init_add{ Gtk.quit_add(Gtk.main_level){ SerialThreadGroup.force_exit! } }
if profile
require 'ruby-prof'
begin
notice 'start profiling'
RubyProf.start
Gtk.main
ensure
result = RubyProf.stop
printer = RubyProf::CallTreePrinter.new(result)
profile_out = File.join(File.expand_path(Environment::TMPDIR), 'profile-'+Time.new.strftime('%Y-%m-%d-%H%M%S')+'.out')
notice "profile: writing to #{profile_out}"
printer.print(File.open(profile_out, 'w'), {})
notice "profile: done."
end
else
Gtk.main end
rescue => e
into_debug_mode(e)
raise e
rescue Exception => e
e = Gtk.exception if Gtk.exception
notice e.class
raise e
end
boot_event(api, service, created, destroy, followings)
click to toggle source
def boot_event(api, service, created, destroy, followings)
unless created.empty?
Plugin.call("#{api}_created".to_sym, service, created) end
unless destroy.empty?
Plugin.call("#{api}_destroy".to_sym, service, destroy) end end
cache_expire()
click to toggle source
キャッシュの有効期限を秒単位で返す
def cache_expire
(UserConfig[:image_file_cache_expire] || 7) * 24 * 60 * 60 end
cache_it(image_url)
click to toggle source
def cache_it(image_url)
notice "cache image to #{get_local_image_name(image_url)}"
CacheWriteThread.new {
raw = Gdk::WebImageLoader.get_raw_data(image_url)
if(raw)
notice "broken image. cache failed"
j_set(image_url)
image_dir = get_local_dir_name(image_url)
FileUtils.mkdir_p(image_dir)
file_put_contents(get_local_image_name(image_url), raw) end } end
caller_util()
click to toggle source
utils.rbのメソッドを呼び出した最初のバックトレースを返す
def caller_util
caller.each{ |result|
return result unless %rutils\.rb/ === result } end
caller_util_all()
click to toggle source
def caller_util_all
aflag = false
result = []
caller.each{ |c|
aflag |= %rutils\.rb/ === c
result << c if aflag }
result end
changesize(eb, w, url)
click to toggle source
def changesize(eb, w, url)
eb.remove(eb.children.first)
@size = w.window.geometry[2,2].freeze
eb.add(::Gtk::WebIcon.new(url, *@size).show_all)
@size end
chi_fatal_alert(msg)
click to toggle source
環境や設定の不備で終了する。msgには、何が原因かを文字列で渡す。このメソッドは 処理を返さずにアボートする。
def chi_fatal_alert(msg)
require_if_exist 'gtk2'
if defined?(Gtk::MessageDialog)
dialog = Gtk::MessageDialog.new(nil,
Gtk::Dialog::DESTROY_WITH_PARENT,
Gtk::MessageDialog::ERROR,
Gtk::MessageDialog::BUTTONS_CLOSE,
"#{Environment::NAME} エラー")
dialog.secondary_text = msg.to_s
dialog.run
dialog.destroy end
puts msg.to_s
abort end
command_exist?(cmd)
click to toggle source
UNIXコマンド cmd が存在するか否かを返す。
def command_exist?(cmd)
system("which #{cmd} > /dev/null")
end
confload(file)
click to toggle source
YAMLファイルを読み込む。存在しない場合はからのハッシュを返す。 file は、IOオブジェクトかファイル名。
def confload(file)
if(file.is_a?(IO))
YAML.load(file.read)
elsif(FileTest.exist?(File.expand_path(file))) then
YAML.load(file_get_contents(file))
else
Hash.new
end
end
confroot(*path)
click to toggle source
Environment::CONFROOT内のファイル名を得る。
confroot(*path)
は
File::expand_path(File.join(Environment::CONFROOT, *path))
と等価。
def confroot(*path)
File::expand_path(File.join(Environment::CONFROOT, *path))
end
create_pane(i_pane)
click to toggle source
ペインを作成
Args
- i_pane
-
ペイン
Return
ペイン(Gtk::Notebook)
def create_pane(i_pane)
notice "create pane #{i_pane.slug.inspect}"
pane = ::Gtk::Notebook.new
@slug_dictionary.add(i_pane, pane)
pane.ssc('key_press_event'){ |widget, event|
Plugin::GUI.keypress(::Gtk::keyname([event.keyval ,event.state]), i_pane) }
pane.ssc(:destroy){
i_pane.destroy if i_pane.destroyed?
false }
pane.show_all end
create_pidfile()
click to toggle source
def create_pidfile()
begin
open(Environment::PIDFILE, WRONLY|CREAT|EXCL){ |output|
output.write(Process.pid)
}
rescue Errno::EEXIST
error('to write pid file failed.')
exit
end
end
create_tab(i_tab)
click to toggle source
タブを作成する
Args
- i_tab
-
タブ
Return
Tab(Gtk::EventBox)
def create_tab(i_tab)
notice "create tab #{i_tab.slug.inspect}"
tab = ::Gtk::EventBox.new.tooltip(i_tab.name)
@slug_dictionary.add(i_tab, tab)
tab_update_icon(i_tab)
tab.ssc(:focus_in_event) {
i_tab.active!(true, true)
false
}
tab.ssc(:key_press_event){ |widget, event|
Plugin::GUI.keypress(::Gtk::keyname([event.keyval ,event.state]), i_tab) }
tab.ssc(:button_press_event) { |this, e|
if e.button == 3
Plugin::GUI::Command.menu_pop(i_tab) end
false }
tab.ssc(:destroy){
i_tab.destroy
false }
tab.show_all end
debugging_wait()
click to toggle source
他のスレッドで#into_debug_modeが呼ばれているなら、それが終わるまでカレントスレッドをスリープさせる
def debugging_wait
if $into_debug_mode
$into_debug_mode << Thread.current
Thread.stop end end
deferred(&proc)
click to toggle source
def deferred(&proc)
Deferred.new.next(&proc) end
delete_cache(id)
click to toggle source
保存した検索の情報をキャッシュから削除
Args
- id
-
削除するID
def delete_cache(id)
cache = at(:cache, {}).melt
cache.delete(id)
store(:cache, cache) end
delete_tab(id)
click to toggle source
idに対応するタブを削除
Args
- id
-
saved search の ID
def delete_tab(id)
type_strict id => Integer
saved_search = timelines[id]
tab(saved_search.slug).destroy if saved_search.slug end
display(url, cancel = nil)
click to toggle source
def display(url, cancel = nil)
w = ::Gtk::Window.new.set_title("(読み込み中)")
w.set_size_request(320, 240)
w.set_default_size(*@size).move(*@position)
w.signal_connect(:destroy){ w.destroy }
eventbox = ::Gtk::EventBox.new
w.add(eventbox)
size = DEFAULT_SIZE
Thread.new{
url = url.value if url.is_a? Thread
if not(url) or not(url.respond_to?(:to_s))
Delayer.new{
unless w.destroyed?
if cancel
w.destroy
cancel.call
else
w.set_title("URLの取得に失敗") end end }
else
pixbuf = Gdk::WebImageLoader.loading_pixbuf(*@size)
raw = Gdk::WebImageLoader.get_raw_data(url){ |data|
if not eventbox.destroyed?
if data
begin
loader = Gdk::PixbufLoader.new
loader.write data
loader.close
pixbuf = loader.pixbuf
rescue => e
pixbuf = Gdk::WebImageLoader.notfound_pixbuf(*@size) end
else
pixbuf = Gdk::WebImageLoader.notfound_pixbuf(*@size) end
eventbox.queue_draw_area(0, 0, *eventbox.window.geometry[2,2]) end }
if raw and raw != :wait
loader = Gdk::PixbufLoader.new
loader.write raw
loader.close
pixbuf = loader.pixbuf end
Delayer.new{
unless w.destroyed?
w.set_title(url.to_s)
eventbox.signal_connect("event"){ |ev, event|
if event.is_a?(Gdk::EventButton) and (event.state.button1_mask?) and event.button == 1
w.destroy
cancel.call if cancel
end
false }
eventbox.signal_connect("expose_event"){ |ev, event|
redraw(eventbox, pixbuf)
move(w)
true }
eventbox.signal_connect(:"size-allocate"){
if w.window and size != w.window.geometry[2,2]
redraw(eventbox, pixbuf)
size = w.window.geometry[2,2] end }
redraw(eventbox, pixbuf)
eventbox end } end }
w.show_all end
entity_unescape(str)
click to toggle source
文字列をエンティティデコードする
def entity_unescape(str)
str.gsub(%r&(.{2,3});/){|s| {'gt'=>'>', 'lt'=>'<', 'amp'=>'&'}[$1] }
end
error(msg)
click to toggle source
エラーメッセージを表示する。
def error(msg)
log "error", msg if Mopt.error_level >= 1
abort if Mopt.error_level >= 4
end
everytime(&proc)
click to toggle source
毎回評価オブジェクトを作成する
def everytime(&proc)
EveryTime.new(&proc) end
fetch(t)
click to toggle source
def fetch(t)
req = URI.parse(t)
res = Net::HTTP.new(req.host).request_head(req.path)
case res
when Net::HTTPSuccess
t
when Net::HTTPRedirection
fetch(res['location'])
else
nil
end
end
fetch_list_of_service(service, cache=:keep)
click to toggle source
service が作成した全てのリストを取得する
Args
- service
-
リストのオーナーのServiceオブジェクト
- cache
-
キャッシュの利用方法
Return
deferred
def fetch_list_of_service(service, cache=:keep)
type_strict service => Service
param = {:cache => cache, :user => service.user_obj}
service.lists(param).next{ |lists|
if lists
set_available_lists(lists)
tab_reflesh{
lists.each{ |list|
tab_mark(list) } } end }.terminate
end
fib(n)
click to toggle source
def fib(n)
return n if n < 2
fib(n-1) + fib(n-2)
end
file_get_contents(fn)
click to toggle source
ファイルの内容を文字列に読み込む
def file_get_contents(fn)
open(fn, 'r:utf-8'){ |input|
input.read
}
end
file_put_contents(fn, body)
click to toggle source
文字列をファイルに書き込む
def file_put_contents(fn, body)
File.open(fn, 'w'){ |put|
put.write body
body
}
end
focus_move_to_nearest_postbox(widget)
click to toggle source
一番近い postbox にフォーカスを与える
Args
- widget
-
基準となるウィジェット
def focus_move_to_nearest_postbox(widget)
notice "called: given widget #{widget.inspect}"
if widget.is_a? Plugin::GUI::HierarchyParent
postbox = widget.children.find{ |w| w.is_a? Plugin::GUI::Postbox }
notice "found postbox: #{postbox.inspect}"
if postbox
return postbox.active! end end
if widget.is_a? Plugin::GUI::HierarchyChild
focus_move_to_nearest_postbox(widget.parent) end end
freezable?()
click to toggle source
freeze_ifn()
click to toggle source
freezeできる場合はfreezeする。selfを返す
def freeze_ifn
freeze if freezable?
self end
gc()
click to toggle source
def gc
notice "apirequest cache was deleted. "+MikuTwitter::Cache.garbage_collect.inspect
Reserver.new(3600){
gc }
end
gen_counter(count=0, increment=1)
click to toggle source
スレッドセーフなカウンタを返す。 カウンタの初期値は count で、呼び出すたびに値が increment
づつ増える。 なお、カウンタが返す値はインクリメント前の値。
def gen_counter(count=0, increment=1)
mutex = Mutex.new
lambda{
mutex.synchronize{
result = count
count += increment
result } } end
gen_relationship(api, userlist)
click to toggle source
def gen_relationship(api, userlist)
count = gen_counter
retrieve_interval = "retrieve_interval_#{api}".to_sym
retrieve_count = "retrieve_count_#{api}".to_sym
add_event_filter(api) { |list| [list + userlist.to_a] }
lambda{ |service|
relations = userlist.to_a
c = count.call
if (c % UserConfig[retrieve_interval]) == 0
service.__send__(api, cache: (c==0 ? true : :keep)).next{ |users|
users = users.select(&ret_nth).reverse!.freeze
boot_event(api, service, users - relations, relations - users, users) unless relations.empty?
users
}.terminate
end } end
gen_xml(msg)
click to toggle source
def gen_xml(msg)
xml = REXML::Document.new open('chi_timeline_cachereplies')
status = xml.root.get_elements('//statuses/status/').first
status.get_elements('text').first.add_text(msg)
return xml
end
get_local_dir_name(image_url, image_name = Digest::MD5.hexdigest(image_url))
click to toggle source
def get_local_dir_name(image_url, image_name = Digest::MD5.hexdigest(image_url))
File.expand_path(File.join(Environment::CACHE, 'icon', image_name[0], image_name[1])) end
get_local_image_name(image_url)
click to toggle source
def get_local_image_name(image_url)
image_name = Digest::MD5.hexdigest(image_url)
File.join(get_local_dir_name(image_url, image_name), image_name) end
get_tag_by_attributes(tag)
click to toggle source
def get_tag_by_attributes(tag)
attribute = {}
tag.each_matches(%r([a-zA-Z0-9]+?)=(['"])(.*?)\22//){ |pair, pos|
key, val = pair[1], pair[3]
attribute[key] = val }
attribute.freeze end
get_tagattr(dom, element_rule)
click to toggle source
def get_tagattr(dom, element_rule)
element_rule = element_rule.melt
tag_name = element_rule['tag'] or 'img'
attr_name = element_rule.has_key?('attribute') ? element_rule['attribute'] : 'src'
element_rule.delete('tag')
element_rule.delete('attribute')
if dom
attribute = {}
catch(:imgtag_match){
dom.gsub("\n", ' ').each_matches(Regexp.new("<#{tag_name}.*?>")){ |str, pos|
attr = get_tag_by_attributes(str.to_s)
if element_rule.all?{ |k, v| v === attr[k] }
attribute = attr.freeze
throw :imgtag_match end } }
unless attribute.empty?
return attr_name ? attribute[attr_name.to_s] : attribute end end
notice 'not matched'
nil end
get_window_geometry(slug)
click to toggle source
def get_window_geometry(slug)
type_strict slug => Symbol
geo = UserConfig[:windows_geometry]
if defined? geo[slug]
geo[slug]
else
size = [Gdk.screen_width/3, Gdk.screen_height*4/5]
{ size: size,
position: [Gdk.screen_width - size[0], Gdk.screen_height/2 - size[1]/2] } end end
hello(url)
click to toggle source
def hello(url)
"マスターったら、ツイッターまでみっくみくね!\n\n"+
"ログインの手順:\n下のリンクをクリックして、ユーザ名などを入れてから許可するボタンを"+
"押してください(クリックしても開かなかったら、アドレスバーにコピペだ!)。\n"+
"#{url}\n表示された数字を「暗証番号」に入力してOKボタンを押してください。\n\n"+
'すると、みっくみくにされます。'
end
imgurlresolver(url, element_rule, limit=5, &block)
click to toggle source
def imgurlresolver(url, element_rule, limit=5, &block)
return nil if limit <= 0
return block.call(url) if block != nil
res = dom = nil
begin
uri = URI.parse(url)
path = uri.path + (uri.query ? "?"+uri.query : "")
res = Net::HTTP.new(uri.host).get(path, "User-Agent" => Environment::NAME + '/' + Environment::VERSION.to_s)
case(res)
when Net::HTTPSuccess
address = get_tagattr(res.body, element_rule)
case address
when %r^https?:/
result = address
when %r^\/\//
result = "http:" + address
when %r^\//
result = uri.dup
result.path = address
else
result = uri.dup
result.merge!(address)
end
notice result.inspect
result.to_s
when Net::HTTPRedirection
return imgurlresolver(res['Location'], element_rule, limit - 1, &block)
else
warn "#{res.code} failed"
nil end
rescue Timeout::Error, StandardError => e
warn e
nil end end
into_debug_mode(exception = nil, bind = binding)
click to toggle source
–debug オプション付きで起動されている場合、インタプリタに入る。
Args
- exception
-
原因となった例外
- binding
-
インタプリタを実行するスコープ
Return
インタプリタから復帰したら(インタプリタが起動されたら)true、
デバッグモードではない、pryがインストールされていない等、起動に失敗したらfalse
def into_debug_mode(exception = nil, bind = binding)
if Mopt.debug and not Mopt.testing
require_if_exist 'pry'
if binding.respond_to?(:pry)
log "error", exception if exception
$into_debug_mode_lock.synchronize {
begin
$into_debug_mode = Set.new
bind.pry
ensure
threads = $into_debug_mode
$into_debug_mode = false
threads.each &:wakeup end }
return true end end end
is_fib?(n)
click to toggle source
def is_fib?(n)
x = 1
loop{
if(fib(x) == n)
return true
elsif(fib(x) > n)
return false end
x += 1 } end
j_delete(url)
click to toggle source
url のキャッシュの日時を削除する。
def j_delete(url)
atomic {
j_data = at(:journaling_data)
if j_data and j_data.include?(url)
j_data = j_data.melt
j_data.delete(url)
store(:journaling_data, j_data) end } end
j_get(url)
click to toggle source
url からキャッシュを取得した日時を返す。 キャッシュがなければ nil を返す。
def j_get(url)
at(:journaling_data, {})[url] end
j_include?(url)
click to toggle source
url のキャッシュがあれば真
def j_include?(url)
j_data = at(:journaling_data)
j_data.include? url if j_data end
j_set(url, time = Time.now)
click to toggle source
url のキャッシュの日時を現在に設定する。
def j_set(url, time = Time.now)
atomic {
j_data = at(:journaling_data, {}).melt
j_data[url.freeze] = time.freeze
store(:journaling_data, j_data)
Reserver.new(time + cache_expire){ j_delete(url) } } end
last_active_pane()
click to toggle source
def last_active_pane
@last_active_pane ||= {} end
lazy(&proc)
click to toggle source
遅延評価オブジェクトを作成する。ブロックを取って呼ばれた場合は Lazy のインスタンスを、
何も取らずに読んだ場合は MethodCallDelayer
のインスタンスを返す。
def lazy(&proc)
if proc
Lazy.new(&proc)
else
MethodCallDelayer.new end end
list_modify_member(list, cache=:keep)
click to toggle source
リストのメンバーを取得する
Args
- list
-
リスト
- cache
-
キャッシュの利用方法
Return
deferred
def list_modify_member(list, cache=:keep)
Service.primary.list_members( list_id: list[:id],
mode: list[:mode],
cache: false).next{ |users|
list.add_member(users) if users
Service.primary.list_statuses(:id => list[:id],
:cache => cache).next{ |res|
if res.is_a? Array
slug = timelines.keys.find{ |slug| timelines[slug] == list }
timeline(slug) << res if slug end
}.terminate
}.terminate("リスト #{list[:full_name]} (##{list[:id]}) のメンバーの取得に失敗しました") end
list_set_hide(list)
click to toggle source
リストを非表示に設定する。すでに非表示にセットされている場合は何もしない
Args
- list
-
非表示にするリスト
Return
self
def list_set_hide(list)
type_strict list => UserList
visible_lists = at(:visible_lists, [])
if visible_lists.include?(list[:id])
store(:visible_lists, visible_lists - [list[:id]]) end
visible_list_obj = at(:visible_list_obj, {}).melt
visible_list_obj.delete(list[:id])
store(:visible_list_obj, visible_list_obj)
self end
list_set_visibility(list)
click to toggle source
リストを表示可能に設定する。すでに表示可能にセットされている場合は何もしない
Args
- list
-
表示可能にするリスト
Return
self
def list_set_visibility(list)
type_strict list => UserList
visible_lists = at(:visible_lists, [])
if not visible_lists.include?(list[:id])
store(:visible_lists, (visible_lists + [list[:id]]).uniq) end
self end
list_set_visibility!(list, visibility)
click to toggle source
list の表示可否状態を visibility にして、実際に表示/非表示を切り替える
Args
- list
-
リスト
- visibility
-
trueなら表示、falseなら非表示
Return
self
def list_set_visibility!(list, visibility)
type_strict list => UserList
if visibility
list_set_visibility(list)
tab_open(list)
else
list_set_hide(list)
tab_close(list) end
self end
list_visible?(list)
click to toggle source
そのタブが表示する設定になっているかどうかを返す
Args
- list
-
リスト
Return
タブが表示する設定になっているなら真
def list_visible?(list)
type_strict list => UserList
visible_list_ids.include? list[:id] end
localfile(url)
click to toggle source
def localfile(url)
File.expand_path(File.join(Environment::CACHE, "af", "#{Digest::MD5.hexdigest(url)[0,2].upcase}.png"))
end
log(prefix, object)
click to toggle source
エラーログに記録する。 内部処理用。外部からは呼び出さないこと。
def log(prefix, object)
debugging_wait
begin
msg = "#{prefix}: #{caller_util}: #{object}"
msg += "\nfrom " + object.backtrace.join("\nfrom ") if object.is_a? Exception
unless $daemon
if msg.is_a? Exception
__write_stderr(msg.to_s)
__write_stderr(msg.backtrace.join("\n"))
else
__write_stderr(msg) end
if logfile
FileUtils.mkdir_p(File.expand_path(File.dirname(logfile + '_')))
File.open(File.expand_path("#{logfile}#{Time.now.strftime('%Y-%m-%d')}.log"), 'a'){ |wp|
wp.write("#{Time.now.to_s}: #{msg}\n") } end end
rescue Exception => e
__write_stderr("critical!: #{caller(0)}: #{e.to_s}")
end
end
logfile(fn = nil)
click to toggle source
ログファイルを取得設定
def logfile(fn = nil)
if(fn) then
$logfile = fn
end
$logfile or nil
end
main()
click to toggle source
def main()
create_pidfile
notice Environment::VERSION
if $debug then
notice '-- ロードされたプラグイン一覧'
Plugin::Ring.avail_plugins.each_pair{|name, insts|
inst = insts.map{|inst| inst.class }.join(', ')
notice "#{name}: #{inst}"
}
notice '--'
end
watch = Watch.instance
if($interactive) then
loop{
print '> '
input = STDIN.gets.chomp
case (input)
when 'q'
exit
when 'help'
puts '"q" とタイプすると終了します'
else
watch.action(Message.new(input, :user => Hash[:id, 0, :idname, 'toshi_a']))
end
}
else
loop{
watch.action
sleep 60
}
end
end
mainthread?()
click to toggle source
カレントスレッドがメインスレッドかどうかを返す
def mainthread?
Thread.main == Thread.current end
mainthread_only()
click to toggle source
メインスレッド以外で呼び出されたらThreadErrorを投げる
def mainthread_only
unless mainthread?
raise ThreadError.new('The method can calls only main thread. but called by another thread.') end end
melt()
click to toggle source
freezeされていない同じ内容のオブジェクトを作って返す。 メルト 溶けてしまいそう (実装が)dupだなんて 絶対に 言えない
def melt
if frozen? then dup else self end end
miku(node, scope=MIKU::SymbolTable.new)
click to toggle source
def miku(node, scope=MIKU::SymbolTable.new)
if(node.is_a? MIKU::Node) then
begin
node.miku_eval(scope)
rescue MIKU::MikuException => e
warn e
rescue Exception => e
warn "[MIKU Bug] fatal error on code #{node.inspect}"
raise e
end
else
node end end
miku_stream(stream, scope)
click to toggle source
def miku_stream(stream, scope)
begin
while(not stream.eof?) do
miku(MIKU.parse(stream), scope) end
rescue MIKU::EndofFile
rescue MIKU::MikuException => e
warn e end end
miquire(*args)
click to toggle source
ミクってかわいいよねぇ。 ツインテールいいよねー。 どう良いのかを書くとコードより長くなりそうだから詳しくは書かないけどいいよねぇ。
ふたりで寒い時とかに歩いてたら首にまいてくれるんだよー。 我ながらなんてわかりやすい説明なんだろう。 (訳: Miquire.miquire のエイリアス)
def miquire(*args)
Miquire.miquire(*args)
end
move(window)
click to toggle source
def move(window)
@position = window.position.freeze end
mute?(params)
click to toggle source
そのイベントをミュートするかどうかを返す(trueなら表示しない)
def mute?(params)
mute_kind = UserConfig[:activity_mute_kind]
if mute_kind.is_a? Array
return true if mute_kind.include? params[:kind].to_s end
mute_kind_related = UserConfig[:activity_mute_kind_related]
if mute_kind_related
return true if mute_kind_related.include?(params[:kind].to_s) and !params[:related] end
false end
no_mainthread()
click to toggle source
メインスレッドで呼び出されたらThreadErrorを投げる
def no_mainthread
if mainthread?
raise ThreadError.new('The method can not calls main thread. but called by main thread.') end end
notice(msg)
click to toggle source
一般メッセージを表示する。
def notice(msg)
log "notice", msg if Mopt.error_level >= 3
end
object_get_contents(fn)
click to toggle source
ファイル fn の内容からオブジェクトを読み込む。 fn は、#object_put_contents()
で保存されたファイルでなければならない。
def object_get_contents(fn)
File.open(fn, 'r'){ |input|
Marshal.load input
}
end
object_put_contents(fn, body)
click to toggle source
オブジェクト body をファイル fn に書き込む。 body
は、Marshalizeできるものでなければならない。
def object_put_contents(fn, body)
File.open(fn, 'w'){ |put|
Marshal.dump body, put
}
end
open_user(user)
click to toggle source
def open_user(user)
Plugin.call(:show_profile, Service.primary, user) end
pane_order_delete(i_pane)
click to toggle source
ペインを順序リストから削除する
Args
- i_pane
-
ペイン
def pane_order_delete(i_pane)
order = UserConfig[:ui_tab_order].melt
i_window = i_pane.parent
order[i_window.slug] = order[i_window.slug].melt
order[i_window.slug].delete(i_pane.slug)
UserConfig[:ui_tab_order] = order
end
parallel(&proc)
click to toggle source
平行評価オブジェクトを作成する
def parallel(&proc)
Parallel.new(&proc) end
pid_exist?(pid)
click to toggle source
プロセスID pid が存在するか否かを返す。
def pid_exist?(pid)
if FileTest.exist? '/proc' then
FileTest.exist? "/proc/#{pid}"
else
begin
Process.kill(0, pid.to_i)
rescue Errno::ESRCH
false
else
true end end end
profile_head(user)
click to toggle source
ユーザのプロフィールのヘッダ部を返す
Args
- user
-
表示するUser
Return
ヘッダ部を表すGtkコンテナ
def profile_head(user)
eventbox = ::Gtk::EventBox.new
eventbox.ssc('visibility-notify-event'){
eventbox.style = background_color
false }
eventbox.add(::Gtk::VBox.new(false, 0).
add(::Gtk::HBox.new(false, 16).
closeup(::Gtk::WebIcon.new(user.profile_image_url_large, 128, 128).top).
closeup(::Gtk::VBox.new.closeup(user_name(user)).closeup(profile_table(user)))))
scrolledwindow = ::Gtk::ScrolledWindow.new
scrolledwindow.height_request = 128 + 24
scrolledwindow.set_policy(::Gtk::POLICY_AUTOMATIC, ::Gtk::POLICY_NEVER)
scrolledwindow.add_with_viewport(eventbox)
end
profile_table(user)
click to toggle source
プロフィールの上のところの格子になってる奴をかえす
Args
- user
-
表示するUser
Return
プロフィールのステータス部を表すGtkコンテナ
def profile_table(user)
w_tweets = ::Gtk::Label.new(user[:statuses_count].to_s)
w_favs = ::Gtk::Label.new(user[:favourites_count].to_s)
w_faved = ::Gtk::Label.new("...")
w_followings = ::Gtk::Label.new(user[:friends_count].to_s)
w_followers = ::Gtk::Label.new(user[:followers_count].to_s)
user.count_favorite_by.next{ |favs|
w_faved.text = favs.to_s
}.terminate("ふぁぼが取得できませんでした").trap{
w_faved.text = '-' }
::Gtk::Table.new(2, 5).
attach(w_tweets.right, 0, 1, 0, 1).
attach(::Gtk::Label.new("tweets").left, 1, 2, 0, 1).
attach(w_favs.right, 0, 1, 1, 2).
attach(::Gtk::Label.new("favs").left, 1, 2, 1, 2).
attach(w_faved.right, 0, 1, 2, 3).
attach(::Gtk::Label.new("faved").left, 1, 2, 2, 3).
attach(w_followings.right, 0, 1, 3, 4).
attach(::Gtk::Label.new("followings").left, 1, 2, 3, 4).
attach(w_followers.right, 0, 1, 4, 5).
attach(::Gtk::Label.new("followers").left, 1, 2, 4, 5).
set_row_spacing(0, 4).
set_row_spacing(1, 4).
set_column_spacing(0, 16)
end
redraw(eb, pb)
click to toggle source
def redraw(eb, pb)
ew, eh = eb.window.geometry[2,2]
return if(ew == 0 or eh == 0)
pb = pb.dup
pb = pb.scale(*Gdk::WebImageLoader.calc_fitclop(pb, Gdk::Rectangle.new(0, 0, ew, eh)))
eb.window.draw_pixbuf(nil, pb, 0, 0, (ew - pb.width)/2, (eh - pb.height)/2, -1, -1, Gdk::RGB::DITHER_NORMAL, 0, 0) end
refresh(cache=:keep)
click to toggle source
saved search を取得する
Args
- cache
-
キャッシュの利用方法
Return
deferred
def refresh(cache=:keep)
Service.primary.saved_searches(cache: cache).next{ |res|
if res
saved_searches = {}
res.each{ |record|
saved_searches[record[:id]] = SavedSearch.new(record[:id], URI.decode(record[:query]), URI.decode(record[:name]), "savedsearch_#{record[:id]}".to_sym) }
new_ids, old_ids = saved_searches.keys, timelines.keys
(new_ids - old_ids).each{ |id| add_tab(saved_searches[id]) }
(old_ids - new_ids).each{ |id| delete_tab(id) } end }.terminate end
register_cache(saved_search)
click to toggle source
保存した検索の情報をキャッシュに登録する
Args
- saved_search
-
保存した検索
def register_cache(saved_search)
type_strict saved_search => SavedSearch
cache = at(:cache, {}).melt
cache[saved_search.id] = {
id: saved_search.id,
query: saved_search.query,
name: saved_search.name,
slug: saved_search.slug }
store(:cache, cache) end
relation_bar(user)
click to toggle source
フォロー関係を表示する
Args
- user
-
対象となるユーザ
Return
リレーションバーのウィジェット(Gtk::VBox)
def relation_bar(user)
icon_size = Gdk::Rectangle.new(0, 0, 32, 32)
arrow_size = Gdk::Rectangle.new(0, 0, 16, 16)
container = ::Gtk::VBox.new(false, 4)
Service.all.each{ |me|
following = followed = nil
w_following_label = ::Gtk::Label.new("関係を取得中")
w_followed_label = ::Gtk::Label.new("")
w_eventbox_image_following = ::Gtk::EventBox.new
w_eventbox_image_followed = ::Gtk::EventBox.new
relation = if me.user_obj == user
::Gtk::Label.new("それはあなたです!")
else
::Gtk::HBox.new.
closeup(w_eventbox_image_following).
closeup(w_following_label) end
relation_container = ::Gtk::HBox.new(false, icon_size.width/2)
relation_container.closeup(::Gtk::WebIcon.new(me.user_obj[:profile_image_url], icon_size).tooltip("#{me.user}(#{me.user_obj[:name]})"))
relation_container.closeup(::Gtk::VBox.new.
closeup(relation).
closeup(::Gtk::HBox.new.
closeup(w_eventbox_image_followed).
closeup(w_followed_label)))
relation_container.closeup(::Gtk::WebIcon.new(user[:profile_image_url], icon_size).tooltip("#{user.idname}(#{user[:name]})"))
if me.user_obj != user
followbutton = ::Gtk::Button.new
followbutton.sensitive = false
m_following_refresh = lambda { |new|
if not w_eventbox_image_following.destroyed?
following = new
if not w_eventbox_image_following.children.empty?
w_eventbox_image_following.remove(w_eventbox_image_following.children.first) end
w_eventbox_image_following.style = w_eventbox_image_following.parent.style
w_eventbox_image_following.add(::Gtk::WebIcon.new(MUI::Skin.get(new ? "arrow_following.png" : "arrow_notfollowing.png"), arrow_size).show_all)
w_following_label.text = new ? "フョローしている" : "フョローしていない"
followbutton.label = new ? "解除" : "フョロー" end }
m_followed_refresh = lambda { |new|
if not w_eventbox_image_followed.destroyed?
followed = new
if not w_eventbox_image_followed.children.empty?
w_eventbox_image_followed.remove(w_eventbox_image_followed.children.first) end
w_eventbox_image_followed.style = w_eventbox_image_followed.parent.style
w_eventbox_image_followed.add(::Gtk::WebIcon.new(MUI::Skin.get(new ? "arrow_followed.png" : "arrow_notfollowed.png"), arrow_size).show_all)
w_followed_label.text = new ? "フョローされている" : "フョローされていない" end }
Service.primary.friendship(target_id: user[:id], source_id: me.user_obj[:id]).next{ |rel|
if rel and not(w_eventbox_image_following.destroyed?)
m_following_refresh.call(rel[:following])
m_followed_refresh.call(rel[:followed_by])
handler_followings_created = on_followings_created do |service, dst_users|
if service == me and dst_users.include?(user)
m_following_refresh.call(true) end end
handler_followings_destroy = on_followings_destroy do |service, dst_users|
if service == me and dst_users.include?(user)
m_following_refresh.call(false) end end
followbutton.ssc(:clicked){
followbutton.sensitive = false
event = following ? :followings_destroy : :followings_created
me.__send__(following ? :unfollow : :follow, user).next{ |msg|
Plugin.call(event, me, [user])
followbutton.sensitive = true unless followbutton.destroyed? }.
terminate.trap{
followbutton.sensitive = true unless followbutton.destroyed? }
true }
followbutton.signal_connect(:destroy){
detach(:followings_created, handler_followings_created)
detach(:followings_destroy, handler_followings_destroy)
false }
followbutton.sensitive = true end
}.terminate.trap{
w_following_label.text = "取得できませんでした" } end
container.closeup(relation_container.closeup(followbutton)) }
container end
remove_muted_user(user)
click to toggle source
def remove_muted_user(user)
type_strict user => User
atomic{
muted = (UserConfig[:muted_users] ||= []).melt
muted.delete(user.idname)
UserConfig[:muted_users] = muted } end
require_if_exist(file)
click to toggle source
存在するかわからないrubyファイル file を読み込む。 ただし、file
が存在しない場合は例外を投げずにfalseを返す。
def require_if_exist(file)
begin
require file
true
rescue LoadError
notice "require-if-exist: file not found: #{file}"
false end end
reset_activity(model)
click to toggle source
アクティビティの古い通知を一定時間後に消す
def reset_activity(model)
notice "reset activity registered"
Reserver.new(60) {
Delayer.new {
if not model.destroyed?
notice "reset activity start"
iters = model.to_enum(:each).to_a
remove_count = iters.size - UserConfig[:activity_max]
notice "remove count #{remove_count}"
if remove_count > 0
iters[-remove_count, remove_count].each{ |mpi|
notice "activity deleted: #{mpi[2][ActivityView::TITLE]}"
model.remove(mpi[2]) }
else
notice "nothing to remove activity" end
reset_activity(model) end } }
end
result_strict(must, &block)
click to toggle source
blockの評価結果がチェックをパスしなかった場合にabortする
def result_strict(must, &block)
result = block.call
type_strict(result => must)
result
end
ret_nth(num=0)
click to toggle source
num 番目の引数をそのまま返す関数を返す
def ret_nth(num=0)
lambda { |*arg| arg[num] } end
rewind_timeline(saved_search)
click to toggle source
タイムラインを更新する
Args
- saved_search
-
saved search
def rewind_timeline(saved_search)
type_strict saved_search => SavedSearch
Service.primary.search(q: saved_search.query, rpp: 100).next{ |res|
timeline(saved_search.slug) << res if res.is_a? Array
}.trap{ |e|
timeline(saved_search.slug) << Message.new(message: "更新中にエラーが発生しました (#{e.to_s})", system: true) } end
scan(slug, messages)
click to toggle source
messagesの中で、タイムライン slug に入れるべきものがあれば入れる
Args
- slug
-
タイムラインスラッグ
- messages
-
入れるMessageの配列
def scan(slug, messages)
seeds = @timelines[slug]
i_timeline = timeline(slug)
if i_timeline and seeds
SerialThread.new do
messages.each{ |message|
message.each_ancestors { |cur|
if seeds.include? cur
i_timeline << message end } } end end end
set_available_lists(newlist)
click to toggle source
自分がフォローしているリストを新しく設定する
Args
- newlist
-
新しいリスト(Enumerable)
Return
newlist
def set_available_lists(newlist)
created = newlist - available_lists
deleted = available_lists - newlist
Plugin.call(:list_created, Service.primary, created.freeze) if not created.empty?
Plugin.call(:list_destroy, Service.primary, deleted.freeze) if not deleted.empty?
@available_lists = UserLists.new(newlist).freeze
Plugin.call(:list_data, Service.primary, @available_lists) if not(created.empty? and deleted.empty?)
@available_lists end
set_event(api, title)
click to toggle source
def set_event(api, title)
userlist = Gtk::UserList.new
tab(api, title) do
set_icon MUI::Skin.get("#{api}.png")
expand
nativewidget userlist
end
proc = gen_relationship(api, userlist)
onperiod{ |service|
promise = proc.call(service)
if promise
promise.next{ |res|
if res
userlist.add(res).show_all end }.terminate end }
add_event("#{api}_created".to_sym){ |service, users|
userlist.add(users).show_all }
add_event("#{api}_destroy".to_sym){ |service, users|
userlist.remove_if_exists_all(users) }
userlist.double_clicked = method(:open_user) end
setting_container()
click to toggle source
設定のGtkウィジェット
def setting_container
tab = Tab.new
tab.plugin = self
available_lists.each{ |list|
iter = tab.model.append
iter[Tab::VISIBILITY] = list_visible?(list)
iter[Tab::SLUG] = list[:full_name]
iter[Tab::LIST] = list
iter[Tab::NAME] = list[:name]
iter[Tab::DESCRIPTION] = list[:description]
iter[Tab::PUBLICITY] = list[:mode] }
Gtk::HBox.new.add(tab).closeup(tab.buttons(Gtk::VBox)).show_all end
setting_window()
click to toggle source
def setting_window
return @window if defined?(@window) and @window
record_order = UserConfig[:settings_menu_order] || ["基本設定", "入力", "表示", "通知", "ショートカットキー", "アクティビティ", "アカウント情報"]
@window = window = ::Gtk::Window.new("設定")
window.set_size_request(320, 240)
window.set_default_size(640, 480)
widgets_dict = {}
menu = menu_widget(widgets_dict)
settings = ::Gtk::VBox.new.set_no_show_all(true).show
scrolled = ::Gtk::ScrolledWindow.new.set_hscrollbar_policy(::Gtk::POLICY_NEVER)
Plugin.filtering(:defined_settings, []).first.each{ |title, definition, plugin|
iter = menu.model.append
iter[0] = title
iter[1] = (record_order.index(title) || record_order.size)
widgets_dict[title] = box = Plugin::Settings.new
box.instance_eval(&definition)
settings.closeup(box) }
window.ssc(:destroy) {
@window = nil
false }
scrolled_menu = ::Gtk::ScrolledWindow.new.set_policy(::Gtk::POLICY_NEVER, ::Gtk::POLICY_AUTOMATIC)
window.add(::Gtk::HPaned.new.add1(scrolled_menu.add_with_viewport(menu)).add2(scrolled.add_with_viewport(settings))) end
show_once(event, *ids)
click to toggle source
このIDの組み合わせが出現したことがないなら真
Args
- event
-
イベント名
- ids
-
ID
Return
初めて表示するキーなら真
def show_once(event, *ids)
@show_once ||= Hash.new{ |h, k| h[k] = [] }
result = []
ids.each_with_index{ |id, index|
storage = @show_once[event][index] ||= Set.new
if storage.include? id
result << true
else
storage << id
result << false end }
not result.all?(&ret_nth) end
slide_timeline_focus(src, dest)
click to toggle source
タイムライン src で選択されているディスプレイ上のy座標が同じ dest のツイートに フォーカスを移動する
Args
- src
-
フォーカスを取得するタイムライン
- dest
-
フォーカスを設定するタイムライン
def slide_timeline_focus(src, dest)
type_strict src => Plugin::GUI::Timeline, dest => Plugin::GUI::Timeline
y = Plugin.filtering(:gui_timeline_cursor_position, src, nil).last
notice "y = #{y}"
if y
Plugin.call(:gui_timeline_move_cursor_to, dest, y) end end
spec_generate(dir)
click to toggle source
def spec_generate(dir)
specfile = File.join(dir, "spec")
spec = if FileTest.exist?(specfile)
YAML.load_file(specfile)
else
user = UserConfig[:verify_credentials] || {}
idname = user[:idname]
{"slug" => File.basename(dir).to_sym, "depends" => {"mikutter" => Environment::VERSION.to_s}, "version" => "1.0", "author" => idname} end
slug = spec["slug"].to_sym
basefile = File.join(dir, "#{slug}.rb")
unless FileTest.exist? basefile
puts "file #{basefile} notfound. select plugin slug."
expects = Dir.glob(File.join(dir, "*.rb")).map{ |filename| File.basename(filename, '.rb') }
if expects.empty?
puts "please create #{basefile}."
end
expects.each_with_index{ |filename, index|
puts "[#{index}] #{filename}"
}
print "input number or slug [q:quit, s:skip]> "
number = STDIN.gets.chomp
case number
when %rq/
abort
when %rs/
return
when %r^[0-9]+$/
slug = expects[number.to_i].to_sym
else
slug = number.to_sym end
spec["slug"] = slug
basefile = File.join(dir, "#{slug}.rb") end
source = File.open(basefile){ |io| io.read }
if not spec.has_key?("name")
print "#{slug}: name> "
spec["name"] = STDIN.gets.chomp end
if not spec.has_key?("description")
print "#{slug}: description> "
spec["description"] = STDIN.gets.chomp end
spec["depends"] = {"version" => "1.0", "plugin" => []} if not spec.has_key?("depends")
spec["depends"]["plugin"] = [] if not spec["depends"].has_key?("plugin")
depend = Depend.new(source).set_spec(spec)
depend.parse
content = YAML.dump(depend.spec)
File.open(specfile, "w"){ |io| io.write content }
puts content
end
start(service)
click to toggle source
def start(service)
notice "boot period"
@crawlers.each{ |s| s.call(service) }
Reserver.new(60){
start(service) } end
streamerror(e)
click to toggle source
def streamerror(e)
@success_flag = false
@fail.notify(e) end
tab_close(list)
click to toggle source
list のためのタブを閉じる。タブがない場合は何もしない。
Args
- list
-
リスト
Return
self
def tab_close(list)
type_strict list => UserList
slug = timelines.keys.find{ |slug| timelines[slug] == list }
return self if not timelines.has_key? slug
if slug
timelines.delete(slug)
tab(slug).destroy end
self end
tab_mark(list)
click to toggle source
list に対応するタブにマークをつける。
Args
- list
-
リスト
def tab_mark(list)
type_strict list => UserList
if list_visible?(list)
@mark << list end end
tab_open(list)
click to toggle source
list のためのタブを開く。タブがすでに有る場合は何もしない。
Args
- list
-
リスト
Return
self
def tab_open(list)
type_strict list => UserList
slug = "list_#{list[:full_name]}".to_sym
return self if timelines.has_key? slug
timelines[slug] = list
tab(slug, list[:full_name]) do
set_icon MUI::Skin.get("list.png")
timeline slug end
list_modify_member(list, true)
visible_list_obj = at(:visible_list_obj, {}).melt
visible_list_obj[list[:id]] = list.to_hash
visible_list_obj[list[:id]][:user] = list[:user].to_hash
store(:visible_list_obj, visible_list_obj)
self end
tab_reflesh() { || ... }
click to toggle source
このブロック中で _add_tab(list)_ を呼ばれなかったリストは、ブロックを出た時に全て削除される。
また、新たにマークを付けられたタブは、タブが作成される。
Return
ブロックの戻り値
def tab_reflesh
@tab_reflesh ||= Mutex.new
@tab_reflesh.synchronize do
@mark = Set.new
result = yield
available_lists.each{ |list|
if @mark.include? list
tab_open(list)
else
tab_close(list) end }
result end end
tab_update_icon(i_tab)
click to toggle source
def tab_update_icon(i_tab)
type_strict i_tab => Plugin::GUI::TabLike
tab = widgetof(i_tab)
if tab
tab.remove(tab.child) if tab.child
if i_tab.icon.is_a?(String)
tab.add(::Gtk::WebIcon.new(i_tab.icon, 24, 24).show)
else
tab.add(::Gtk::Label.new(i_tab.name).show) end end
self end
tclambda(*args, &proc)
click to toggle source
#type_checkで型をチェックしてからブロックを評価する無名関数を生成して返す
def tclambda(*args, &proc)
lambda{ |*a|
if proc.arity >= 0
if proc.arity != a.size
raise ArgumentError.new("wrong number of arguments (#{a.size} for #{proc.arity})") end
elsif -(proc.arity+1) > a.size
raise ArgumentError.new("wrong number of arguments (#{a.size} for #{proc.arity})") end
proc.call(*a) if type_check(a.slice(0, args.size).zip(args)) } end
tcor(*types)
click to toggle source
types のうちいずれかとis_a?関係ならtrueを返すProcオブジェクトを返す
def tcor(*types)
lambda{ |v| types.any?{ |c| v.is_a?(c) } } end
the_day?()
click to toggle source
def the_day?
time = Time.new
4 == time.month and 1 == time.day end
timelines()
click to toggle source
表示中のタイムライン/タブのスラッグとリストオブジェクトの連想配列
def timelines
@timelines ||= {} end
type_check(args, &proc)
click to toggle source
引数のチェックをすべてパスした場合のみブロックを実行する チェックに引っかかった項目があればwarnを出力してブロックは実行せずにnilを返す。
チェックはassocできる配列か、Hashで定義する。
type_check(value => nil,
value => Module,
value => [:method, *args],
value => lambda{ |x| ...})
チェックをすべてパスしたかどうかを真偽値で返す。 ブロックが指定されていれば、それを実行してブロックの実行結果を返す メモ:
いずれかのタイプに一致するチェックを定義するにはtcorを使う
type_check object => tcor(Array, Hash)
def type_check(args, &proc)
check_function = lambda{ |val, check|
if not check
true
elsif check.respond_to?(:call)
check.call(val)
elsif check.is_a? Array
val.__send__(*check)
elsif check.is_a? Symbol
val.respond_to?(check)
elsif check.is_a?(Class) or check.is_a?(Module)
val.is_a?(check) end }
error = args.find{ |a| not(check_function.call(*a)) }
if(error)
warn "argument error: #{error[0].inspect} is not passed #{error[1].inspect}"
warn "in #{caller_util}"
false
else
if proc
proc.call
else
true end end end
type_strict(args, &proc)
click to toggle source
#type_checkと同じだが、チェックをパスしなかった場合にabortする
#type_checkの戻り値を返す
def type_strict(args, &proc)
result = type_check(args, &proc)
if not result
into_debug_mode(binding)
raise ArgumentError.new end
result end
user_name(user)
click to toggle source
ユーザ名を表示する
Args
- user
-
表示するUser
Return
ユーザの名前の部分のGtkコンテナ
def user_name(user)
w_screen_name = ::Gtk::Label.new.set_markup("<b><u><span foreground=\"#0000ff\">#{Pango.escape(user[:idname])}</span></u></b>")
w_ev = ::Gtk::EventBox.new
w_ev.modify_bg(::Gtk::STATE_NORMAL, Gdk::Color.new(0xffff, 0xffff, 0xffff))
w_ev.ssc(:realize) {
w_ev.window.set_cursor(Gdk::Cursor.new(Gdk::Cursor::HAND2))
false }
w_ev.ssc(:button_press_event) { |this, e|
if e.button == 1
::Gtk.openurl("http://twitter.com/#{user[:idname]}")
true end }
::Gtk::HBox.new(false, 16).closeup(w_ev.add(w_screen_name)).closeup(::Gtk::Label.new(user[:name]))
end
visible_list_ids()
click to toggle source
表示設定されているリストのIDを返す
Return
表示できるリストのIDの配列(TypedArray)
def visible_list_ids
at(:visible_lists, []).freeze end
wakachigaki(str, ret_io=false)
click to toggle source
def wakachigaki(str, ret_io=false)
if(ret_io)
IO.popen('mecab -Owakati', 'r+').tap{ |io|
io.write(str)
io.close_write }
else
IO.popen('mecab -Owakati', 'r+'){ |io|
io.write(str)
io.close_write
io.read } end end
warn(msg)
click to toggle source
警告メッセージを表示する。
def warn(msg)
log "warning", msg if Mopt.error_level >= 2
end
where_should_insert_it(insertion, src, order)
click to toggle source
insertion を、 src の挿入するべき場所のインデックスを返す。 order
は順番を表す配列で、 src 内のオブジェクトの前後関係を表す。 order 内に
insertion が存在しない場合は一番最後のインデックスを返す
def where_should_insert_it(insertion, src, order)
if(order.include?(insertion)) then
src.dup.push(insertion).sort_by{|a|
order.index(a) or 65536
}.index(insertion)
else
src.size end end
window_order_save_request(i_window)
click to toggle source
ウィンドウ内のペイン、タブの現在の順序を設定に保存する
Args
- i_window
-
ウィンドウ
def window_order_save_request(i_window)
notice "window_order_save_request: #{i_window.inspect}"
type_strict i_window => Plugin::GUI::Window
Delayer.new do
panes_order = {}
i_window.children.each{ |i_pane|
if i_pane.is_a? Plugin::GUI::Pane
tab_order = []
pane = widgetof(i_pane)
if pane
pane.n_pages.times{ |page_num|
i_widget = find_implement_widget_by_gtkwidget(pane.get_tab_label(pane.get_nth_page(page_num)))
tab_order << i_widget.slug if i_widget } end
panes_order[i_pane.slug] = tab_order if not tab_order.empty? end }
ui_tab_order = (UserConfig[:ui_tab_order] || {}).melt
ui_tab_order[i_window.slug] = panes_order
UserConfig[:ui_tab_order] = ui_tab_order
end
end
yamlisp(node)
click to toggle source
def yamlisp(node)
if(node.is_a? YamLisp::Node) then
node.yamlisp_eval
else
node
end
end