class Object

Constants

BIRD_CACHE_PATH
BIRD_URL
BOOT_TIME
CACHE_DIR
DEFAULT_SOUND_DIRECTORY
Deferred
Event
EventFilter
EventListener
FOLLOW_DIR
HYDE

基本的な単位であり、数学的にも重要なマジックナンバーで、至るところで使われる。 これが言語仕様に含まれていないRubyは正直気が狂っていると思う。 ja.uncyclopedia.info/wiki/Hyde

Plugin
Post

Twitter APIとmikutterプラグインのインターフェイス

RUBY_VERSION_ARRAY

Rubyのバージョンを配列で。あると便利。

Retriever
SerialThread

SerialThreadGroup のインスタンス。 同時実行数は1固定

Sound
TABPOS

Public Class Methods

boot() click to toggle source
# File core/plugin/gtk/delayer.rb, line 7
def self.boot
  Gtk.idle_add_priority(GLib::PRIORITY_LOW) {
    Delayer.run
    false }
end
define_periodical_executer(api, interval, count, &success) click to toggle source
# File core/plugin/rest/rest.rb, line 5
def self.define_periodical_executer(api, interval, count, &success)
  counter = UserConfig[interval]
  lambda{ |service|
    counter += 1
    if counter >= UserConfig[interval]
      counter = 0
      service.call_api(api, count: UserConfig[count]){ |messages|
        success.call(service, messages) if messages and not messages.empty? } end } end
defnotify(label, kind) click to toggle source
# File core/plugin/notify/notify.rb, line 7
def self.defnotify(label, kind)
  settings (label) do
    boolean _('ポップアップ'), "notify_#{kind}".to_sym
  fileselect(_('サウンド'), "notify_sound_#{kind}".to_sym, DEFAULT_SOUND_DIRECTORY) end end
notify(user, text) click to toggle source
# File core/plugin/notify/notify.rb, line 81
def self.notify(user, text)
  Plugin.call(:popup_notify, user, text) end
notify_sound(sndfile) click to toggle source
# File core/plugin/notify/notify.rb, line 84
def self.notify_sound(sndfile)
  if sndfile.respond_to?(:to_s) and FileTest.exist?(sndfile.to_s)
    Plugin.call(:play_sound, sndfile) end end

Public Instance Methods

__write_stderr(msg) click to toggle source
# File core/utils.rb, line 252
def __write_stderr (msg)
  $stderr.write(msg.gsub(FOLLOW_DIR, '{MIKUTTER_DIR}')+"\n")
end
_loop() click to toggle source
# File core/plugin/image_file_cache/image_file_cache.rb, line 78
def _loop
  Reserver.new(60, thread: SerialThread) do
    if @queue
      @queue.run
      _loop  end end end
active_datasources() click to toggle source

使用されているデータソースのSetを返す

# File core/plugin/extract/extract.rb, line 266
def active_datasources
  @active_datasources ||=
    extract_tabs.values.map{|tab|
      tab.sources
    }.inject(Set.new, &:merge).freeze end
add_intent_rule(intent:, str:, rule:, model_slug:) click to toggle source
# File core/plugin/intent_selector/intent_selector.rb, line 106
def add_intent_rule(intent,, str,, rule,, model_slug))
  unless UserConfig[:intent_selector_rules].any?{|r| r[:intent].to_sym == intent.slug && r[:str] == str && r[:rule] == rule }
    UserConfig[:intent_selector_rules] += [{uuid: SecureRandom.uuid, intent: intent.slug, model: model_slug, str: str, rule: rule}]
  end
end
add_muted_user(user) click to toggle source
# File core/plugin/user_detail_view/user_detail_view.rb, line 155
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

# File core/plugin/saved_search/saved_search.rb, line 49
def add_tab(saved_search)
  type_strict saved_search => Plugin::SavedSearch::SavedSearch
  tab(saved_search.slug, saved_search.name) do
    set_icon Skin['savedsearch.png']
    timeline saved_search.slug end
  register_cache(saved_search)
  timelines[saved_search.id] = saved_search end
addsupport(cond, element_rule = {}, &block) click to toggle source
# File core/plugin/openimg/openimg.rb, line 80
def addsupport(cond, element_rule = {}, &block); end
append_message(source, messages) click to toggle source
# File core/plugin/extract/extract.rb, line 340
def append_message(source, messages)
  type_strict source => Symbol, messages => Enumerable
  tabs = extract_tabs.values.select{ |r| r.sources && r.using?(source) }
  return if tabs.empty?
  converted_messages = messages.map{ |message| message.retweet_source ? message.retweet_source : message }
  tabs.each{ |record|
      filtered_messages = timeline(record.slug).not_in_message(converted_messages.select(&compile(record.id, record.sexp)))
      timeline(record.slug) << filtered_messages
      notificate_messages = filtered_messages.lazy.select{|message| message[:created] > defined_time}
      if record.popup?
        notificate_messages.deach do |message|
          Plugin.call(:popup_notify, message.user, message.to_show) end end
      if record.sound.is_a?(String) and notificate_messages.first and FileTest.exist?(record.sound)
        Plugin.call(:play_sound, record.sound) end
  } end
atomic() { || ... } click to toggle source

共通のMutexで処理を保護して実行する。 atomicブロックで囲まれたコードは、別々のスレッドで同時に実行されない。

# File core/utils.rb, line 282
def atomic
  $atomic.synchronize{ yield }
end
available_lists(service = nil) click to toggle source

自分がフォローしているリストを返す。 service を指定すると、そのアカウントでフォローしているリストに結果を限定する。 結果は重複する可能性がある

Args

service

Service|nil リストのフォロイーで絞り込む場合、そのService

Return

Enumerable 自分がフォローしているリスト(UserList)を列挙する

# File core/plugin/list/list.rb, line 143
def available_lists(service = nil)
  @available_lists ||= Hash.new
  if service
    @available_lists[service.user_obj] ||= UserLists.new.freeze
  else
    @all_available_lists ||= UserLists.new(@available_lists.flat_map{|k,v| v}.uniq.compact).freeze end end
background_color() click to toggle source
# File core/plugin/user_detail_view/user_detail_view.rb, line 334
def background_color
  style = ::Gtk::Style.new()
  style.set_bg(::Gtk::STATE_NORMAL, 0xFF ** 2, 0xFF ** 2, 0xFF ** 2)
  style end
backtrace() click to toggle source
# File core/plugin/bugreport/bugreport.rb, line 103
def backtrace
  "#{crashed_exception.class} #{crashed_exception.to_s}\n" +
    crashed_exception.backtrace.map{ |msg| msg.gsub(FOLLOW_DIR, '{MIKUTTER_DIR}') }.join("\n")
end
bg_system(*args) click to toggle source

コマンドをバックグラウンドで起動することを覗いては system() と同じ

# File core/utils.rb, line 287
def bg_system(*args)
  Process.detach(spawn(*args))
end
bool(val) click to toggle source

値が真であるならtrueを返す。遅延評価オブジェクトでも正確に判断することができる。

# File core/lib/lazy.rb, line 101
def bool(val)
  not(val.nil? or val.is_a?(FalseClass)) end
boot() click to toggle source
# File core/plugin/followingcontrol/followingcontrol.rb, line 120
def boot
  @activating_services = Set.new
  @relation = Struct.new(:followings, :followers).new(TimeLimitedStorage.new, TimeLimitedStorage.new)

  Service.each(&method(:service_register))
end
boot!(profile) click to toggle source

イベントの待受を開始する。 profile がtrueなら、プロファイリングした結果を一時ディレクトリに保存する

# File mikutter.rb, line 48
def boot!(profile)
  begin
    Mainloop.before_mainloop
    if profile
      require 'ruby-prof'
      begin
        notice 'start profiling'
        RubyProf.start
        Mainloop.mainloop
      ensure
        result = RubyProf.stop
        printer = RubyProf::CallTreePrinter.new(result)
        path = File.join(Environment::TMPDIR, 'profile', Time.new.strftime('%Y-%m-%d-%H%M%S'))
        FileUtils.mkdir_p(path)
        notice "profile: writing to #{path}"
        printer.print(path: path)
        notice "profile: done."
      end
    else
      Mainloop.mainloop end
  rescue => exception
    into_debug_mode(exception)
    notice "catch exception `#{exception.class}'"
    raise exception
  rescue Exception => exception
    notice "catch exception `#{exception.class}'"
    exception = Mainloop.exception_filter(exception)
    notice "=> `#{exception.class}'"
    raise exception end
  exception = Mainloop.exception_filter(nil)
  if exception
    notice "raise mainloop exception `#{exception.class}'"
    raise exception end
  notice "boot! exited normally." end
cache_expire() click to toggle source

キャッシュの有効期限を秒単位で返す

# File core/plugin/image_file_cache/image_file_cache.rb, line 38
def cache_expire
  (UserConfig[:image_file_cache_expire] || 32) * 24 * 60 * 60 end
cache_it(photo) click to toggle source
# File core/plugin/image_file_cache/image_file_cache.rb, line 41
def cache_it(photo)
  unless @db.key?(photo.uri.to_s)
    notice "cache added #{photo.uri}"
    photo.download.next{|downloaded|
      @db[downloaded.uri.to_s] = downloaded.blob
    }
  end
end
caller_util() click to toggle source

utils.rbのメソッドを呼び出した最初のバックトレースを返す

# File core/utils.rb, line 224
def caller_util
  caller.each{ |result|
    return result unless /utils\.rb/ === result } end
check_dirs() click to toggle source
# File core/plugin/image_file_cache/image_file_cache.rb, line 65
def check_dirs
  @queue.new(:check_dirs) do
    Dir.foreach(@cache_directory)
      .select{|x| x =~ %r<\A[a-fA-F0-9]{2}\Z> }
      .shuffle
      .each{|subdir|
      check_subdirs(File.join(@cache_directory, subdir))
    }
    Reserver.new(cache_expire, thread: SerialThread) do
      check_dirs end
  end
end
check_subdirs(dir) click to toggle source
# File core/plugin/image_file_cache/image_file_cache.rb, line 50
  def check_subdirs(dir)
    @queue.new(:check_subdirs) do
      Dir.foreach(dir)
        .map{|x| File.join(dir, x) }
        .select{|x| FileTest.file?(x) }
        .each{|x|
        Reserver.new((File.atime(x) rescue File.mtime(x)) + cache_expire, thread: SerialThread) do
          notice "cache deleted #{x}"
          File.delete(x) if FileTest.file?(x)
          if Dir.foreach(dir).select{|y| File.file? File.join(dir, y) }.empty?
            Dir.delete(dir) rescue nil end end
      }
    end
  end

  def check_dirs
    @queue.new(:check_dirs) do
      Dir.foreach(@cache_directory)
        .select{|x| x =~ %r<\A[a-fA-F0-9]{2}\Z> }
        .shuffle
        .each{|subdir|
        check_subdirs(File.join(@cache_directory, subdir))
      }
      Reserver.new(cache_expire, thread: SerialThread) do
        check_dirs end
    end
  end

  def _loop
    Reserver.new(60, thread: SerialThread) do
      if @queue
        @queue.run
        _loop  end end end

  on_unload do
    @db.close
    @db = @queue = nil
  end

  check_dirs
  _loop

end
chi_fatal_alert(msg) click to toggle source

環境や設定の不備で終了する。msgには、何が原因かを文字列で渡す。このメソッドは 処理を返さずにアボートする。

# File core/utils.rb, line 258
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 が存在するか否かを返す。

# File core/utils.rb, line 88
def command_exist?(cmd)
  system("which #{cmd} > /dev/null")
end
compile(tab_id, code) click to toggle source
# File core/plugin/extract/extract.rb, line 272
def compile(tab_id, code)
  atomic do
    @compiled ||= {}
    @compiled[tab_id] ||=
      if code.empty?
        ret_nth
      else
        begin
          before = Set.new
          extract_condition ||= Hash[Plugin.filtering(:extract_condition, []).first.map{ |condition| [condition.slug, condition] }]
          evaluated = MIKU::Primitive.new(:to_ruby_ne).call(MIKU::SymbolTable.new, metamorphose(code: code, assign: before, extract_condition: extract_condition))
          code_string = "lambda{ |message|\n" + before.to_a.join("\n") + "\n  " + evaluated + "\n}"
          instance_eval(code_string)
        rescue Plugin::Extract::ConditionNotFoundError => exception
          Plugin.call(:modify_activity,
                      plugin: self,
                      kind: 'error'.freeze,
                      title: _("抽出タブ条件エラー"),
                      date: Time.new,
                      description: _("抽出タブ「%{tab_name}」で使われている条件が見つかりませんでした:\n%{error_string}") % {tab_name: extract_tabs[tab_id].name, error_string: exception.to_s})
          warn exception
          ret_nth end end end end
confroot(*path) click to toggle source

Environment::CONFROOT内のファイル名を得る。

confroot(*path)

File::expand_path(File.join(Environment::CONFROOT, *path))

と等価。

# File core/utils.rb, line 44
def confroot(*path)
  File::expand_path(File.join(Environment::CONFROOT, *path))
end
crashed_exception() click to toggle source
# File core/plugin/bugreport/bugreport.rb, line 108
def crashed_exception
  @crashed_exception ||= object_get_contents(File.expand_path(File.join(Environment::TMPDIR, 'crashed_exception'))) rescue nil
end
create_pane(i_pane) click to toggle source

ペインを作成

Args

i_pane

ペイン

Return

ペイン(Gtk::Notebook)

# File core/plugin/gtk/gtk.rb, line 535
def create_pane(i_pane)
  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_tab(i_tab) click to toggle source

タブを作成する

Args

i_tab

タブ

Return

Tab(Gtk::EventBox)

# File core/plugin/gtk/gtk.rb, line 136
def create_tab(i_tab)
  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, event|
    if event.button == 3
      Plugin::GUI::Command.menu_pop(i_tab)
    else
      Plugin::GUI.keypress(::Gtk::buttonname([event.event_type, event.button, event.state]), i_tab)
    end
    false }
  tab.ssc(:destroy) {
    i_tab.destroy
    false }
  tab.show_all end
datasource_slug(list) click to toggle source
# File core/plugin/list/list.rb, line 20
def datasource_slug(list)
  type_strict list => UserList
  :"#{list.user.idname}_list_#{list[:id]}" end
datasources() click to toggle source

このプラグインが提供するデータソースを返す

Return

Hash データソース

# File core/plugin/quoted_message/quoted_message.rb, line 6
def datasources
  ds = {nested_quoted_myself: _("ナウい引用(全てのアカウント)".freeze)}
  Service.each do |service|
    ds["nested_quote_quotedby_#{service.user_obj.id}".to_sym] = "@#{service.user_obj.idname}/" + _('ナウい引用'.freeze) end
  ds end
debugging_wait() click to toggle source

他のスレッドでinto_debug_modeが呼ばれているなら、それが終わるまでカレントスレッドをスリープさせる

# File core/utils.rb, line 154
def debugging_wait
  if $into_debug_mode
    $into_debug_mode << Thread.current
    Thread.stop end end
defsequence(name, &content) click to toggle source
# File core/plugin/change_account/change_account.rb, line 75
def defsequence(name, &content)
  @sequence[name] = content end
delete_cache(id) click to toggle source

保存した検索の情報をキャッシュから削除

Args

id

削除するID

# File core/plugin/saved_search/saved_search.rb, line 124
def delete_cache(id)
  cache = at(:last_saved_search_state, {}).melt
  cache.delete(id)
  store(:last_saved_search_state, cache) end
delete_tab(id) click to toggle source

idに対応するタブを削除

Args

id

saved search の ID

# File core/plugin/saved_search/saved_search.rb, line 60
def delete_tab(id)
  type_strict id => Integer
  saved_search = timelines[id]
  timelines.delete(id)
  tab(saved_search.slug).destroy if saved_search.slug end
destroy_compile_cache() click to toggle source
# File core/plugin/extract/extract.rb, line 336
def destroy_compile_cache
  atomic do
    @compiled = {} end end
divide_intents(intents, uri, model_slug) click to toggle source

intent の配列を受け取り、ユーザが過去に入力したルールに基づき、 recommendedとsuggestedに分ける

Args

intents

スキャン対象のintent

uri

リソースのURI

model_slug

絞り込みに使うModelのslug。

Return

条件に対して推奨されるintentの配列と、intentsに指定されたそれ以外の値の配列

# File core/plugin/intent_selector/intent_selector.rb, line 120
def divide_intents(intents, uri, model_slug)
  intent_slugs = UserConfig[:intent_selector_rules].select{|record|
    ((record[:model] == nil) || (model_slug == record[:model].to_s)) && uri.to_s.start_with?(record[:str])
  }.map{|record|
    record[:intent].to_sym
  }
  intents.partition{|intent| intent_slugs.include?(intent.slug.to_sym) }
end
enable_sequence() click to toggle source
# File core/plugin/aspectframe/aspectframe.rb, line 95
def enable_sequence
  Plugin::AspectFrame::SCHEDULE.lazy.select{|s| s.range.cover? now} end
enable_sequence?(seq) click to toggle source
# File core/plugin/aspectframe/aspectframe.rb, line 98
def enable_sequence?(seq)
  enable_sequence.map(&:slug).include? seq end
encode_parameters(params, delimiter = '&', quote = nil) click to toggle source
# File core/plugin/bugreport/bugreport.rb, line 112
def encode_parameters(params, delimiter = '&', quote = nil)
  if params.is_a?(Hash)
    params = params.map do |key, value|
      "#{escape(key)}=#{quote}#{escape(value)}#{quote}"
    end
  else
    params = params.map { |value| escape(value) }
  end
  delimiter ? params.join(delimiter) : params
end
error(msg) click to toggle source

エラーメッセージを表示する。

# File core/utils.rb, line 124
def error(msg)
  log "error", msg if Mopt.error_level >= 1
  abort if Mopt.error_level >= 4
end
error_handling!(exception) click to toggle source
# File mikutter.rb, line 83
def error_handling!(exception)
  notice "catch #{exception.class}"
  File.open(File.expand_path(File.join(Environment::TMPDIR, 'crashed_exception')), 'w'){ |io| Marshal.dump(exception, io) }
  raise exception end
escape(value) click to toggle source
# File core/plugin/bugreport/bugreport.rb, line 123
def escape(value)
  URI.escape(value.to_s, /[^a-zA-Z0-9\-\.\_\~]/)
end
everytime(&proc) click to toggle source

毎回評価オブジェクトを作成する

# File core/lib/lazy.rb, line 93
def everytime(&proc)
  EveryTime.new(&proc) end
extract_tabs() click to toggle source

抽出タブオブジェクト。各キーは抽出タブIDで、値は以下のようなオブジェクト

name

タブの名前

sexp

条件式(S式)

source

どこのツイートを見るか(イベント名、配列で複数)

slug

タイムラインとタブのスラッグ

id

抽出タブのID

# File core/plugin/extract/extract.rb, line 67
def extract_tabs
  @extract_tabs ||= {} end
fetch_and_modify_for_using_lists(service, cache=:keep) click to toggle source

service が所有するリストを更新し、そのうち実際に抽出タブで使用されているリストについて、 リストのメンバーの更新と、直近のツイートの取得を行う

Args

service

Service

# File core/plugin/list/list.rb, line 130
def fetch_and_modify_for_using_lists(service, cache=:keep)
  fetch_list_of_service(service, cache).next{|service_that_has_list|
    modifier = (Set.new(service_that_has_list) & Set.new(using_lists)).map{|list| list_modify_member(list, service: service, cache: cache)}
    Deferred.when(*modifier) unless modifier.empty?
  }.terminate(_('%{user_name}がフォローしているリストを取得できませんでした') % {user_name: service.user_obj.idname}) end
fetch_list_of_service(service, cache=:keep) click to toggle source

service が作成またはフォローしている全てのリストを取得する。 ただし、自分のアカウント(A)で作成したリストを、自分のアカウント(B)でフォローしている場合、 #fetch_list_of_service(B) の結果にそのリストは含まれず、 #fetch_list_of_service(A) の結果からしか見ることができない。

Args

service

リストのオーナーのServiceオブジェクト

cache

キャッシュの利用方法

Return

deferred

# File core/plugin/list/list.rb, line 66
def fetch_list_of_service(service, cache=:keep)
  type_strict service => Service
  service.lists(cache: cache, user: service.user_obj).next do |lists|
    if lists
      set_available_lists(service, lists)
      Service.reject(&service.method(:==)).map(&:user).inject(lists.lazy) do |stream, u|
        stream.reject{|l| l.user == u } end end end end
file_get_contents(fn) click to toggle source

ファイルの内容を文字列に読み込む

# File core/utils.rb, line 65
def file_get_contents(fn)
  open(fn, 'rb:utf-8'.freeze){ |input|
    input.read
  }
end
file_put_contents(fn, body) click to toggle source

文字列をファイルに書き込む

# File core/utils.rb, line 72
def file_put_contents(fn, body)
  File.open(fn, 'wb'.freeze){ |put|
    put.write body
    body
  }
end
find_implement_widget_by_gtkwidget(widget) click to toggle source

Gtkオブジェクト widget に対応するウィジェットのオブジェクトを返す

Args

widget

Gtkウィジェット

Return

widget に対応するウィジェットオブジェクトまたは偽

# File core/plugin/gtk/gtk.rb, line 592
def find_implement_widget_by_gtkwidget(widget)
  @slug_dictionary.imaginally_by_gtk(widget) end
find_url_open_command() click to toggle source
# File core/plugin/web/web.rb, line 35
      def find_url_open_command
openable_commands = %w{xdg-open open /etc/alternatives/x-www-browser}
wellknown_browsers = %w{firefox chromium opera}
command = nil
catch(:urlopen) do
  openable_commands.each{ |o|
    if command_exist?(o)
      command = o
      throw :urlopen end }
  wellknown_browsers.each{ |o|
    if command_exist?(o)
      activity :system, _('この環境で、URLを開くためのコマンドが判別できなかったので、"%{command}"を使用します。設定の「表示→URLを開く方法」で、URLを開く方法を設定してください。') % {command: command}
      command = o
      throw :urlopen end } end
command end
focus_move_to_latest_widget(postbox) click to toggle source

最後にアクティブだったペインにフォーカスを与える。 親タイムラインがあれば、それにフォーカスを与える。

Args

postbox

基準となるウィジェット

# File core/plugin/command/command.rb, line 298
def focus_move_to_latest_widget(postbox)
  if postbox.parent.is_a? Plugin::GUI::Window
    pane = last_active_pane[postbox.parent.slug]
    pane.active! if pane
  else
    postbox.parent.active! end end
focus_move_to_nearest_postbox(widget) click to toggle source

一番近い postbox にフォーカスを与える

Args

widget

基準となるウィジェット

# File core/plugin/command/command.rb, line 286
def focus_move_to_nearest_postbox(widget)
  if widget.is_a? Plugin::GUI::HierarchyParent
    postbox = widget.children.find{ |w| w.is_a? Plugin::GUI::Postbox }
    if postbox
      return postbox.active! end end
  if widget.is_a? Plugin::GUI::HierarchyChild
    focus_move_to_nearest_postbox(widget.parent) end end
focus_move_widget(widget, distance) click to toggle source

フォーカスを widget から distance に移動する

Args

widget

起点となるウィジェット

distance

移動距離

# File core/plugin/command/command.rb, line 260
def focus_move_widget(widget, distance)
  type_strict widget => Plugin::GUI::HierarchyParent
  type_strict widget => Plugin::GUI::HierarchyChild
  children = widget.parent.children.select{ |w| w.is_a? widget.class }
  index = children.index(widget)
  term = children[(index + distance) % children.size]
  term = term.active_chain.last if term.respond_to? :active_chain
  term.active! if term
  src_tl = widget.active_chain.last
  if widget.is_a?(Plugin::GUI::Pane) and src_tl.is_a?(Plugin::GUI::Timeline) and term.is_a?(Plugin::GUI::Timeline)
    slide_timeline_focus(src_tl, term) end end
freezable?() click to toggle source

freezeできるならtrueを返す

# File core/utils.rb, line 323
def freezable?
  true end
freeze_ifn() click to toggle source

freezeできる場合はfreezeする。selfを返す

# File core/utils.rb, line 327
def freeze_ifn
  freeze if freezable?
  self end
gc() click to toggle source
# File core/plugin/api_request_file_cache.rb, line 5
def gc
  notice "apirequest cache was deleted. "+MikuTwitter::Cache.garbage_collect.inspect
  Reserver.new(3600, thread: SerialThread){
    gc }
end
gen_counter(count=0, increment=1) click to toggle source

スレッドセーフなカウンタを返す。 カウンタの初期値は count で、呼び出すたびに値が increment づつ増える。 なお、カウンタが返す値はインクリメント前の値。

# File core/utils.rb, line 56
def gen_counter(count=0, increment=1)
  mutex = Mutex.new
  lambda{
    mutex.synchronize{
      result = count
      count += increment
      result } } end
gen_listener_for_invisible_check(uc, kind) click to toggle source
# File core/plugin/activity/activity.rb, line 99
def gen_listener_for_invisible_check(uc, kind)
  UserConfig[uc] ||= []
  Plugin::Settings::Listener.new        get: ->(){ (not UserConfig[uc].include?(kind)) rescue true },
    set: ->(value) do
      unless value
        UserConfig[uc] += [kind]
      else
        UserConfig[uc] -= [kind] end end
gen_listener_for_visible_check(uc, kind) click to toggle source
# File core/plugin/activity/activity.rb, line 89
def gen_listener_for_visible_check(uc, kind)
  UserConfig[uc] ||= []
  Plugin::Settings::Listener.new        get: ->(){ UserConfig[uc].include?(kind) rescue false },
    set: ->(value) do
      if value
        UserConfig[uc] += [kind]
      else
        UserConfig[uc] -= [kind] end end
gen_message_filter() click to toggle source

Messageの配列を受け取り、一度以上受け取ったことのあるものを除外して返すフィルタを作成して返す

Return

フィルタのプロシージャ(Proc)

# File core/plugin/core/core.rb, line 24
def gen_message_filter
  appeared = Set.new
  ->(messages){
    [messages.select{ |message|
       appeared.add(message.id) unless appeared.include?(message.id) }] } end
gen_message_filter_with_service() click to toggle source

Serviceと、Messageの配列を受け取り、一度以上受け取ったことのあるものを除外して返すフィルタを作成して返す。 ただし、除外したかどうかはService毎に記録する。 また、アカウント登録前等、serviceがnilの時はシステムメッセージ以外を全て削除し、記録しない。

Return

フィルタのプロシージャ(Proc)

# File core/plugin/core/core.rb, line 9
def gen_message_filter_with_service
  service_filters = Hash.new{|h,k|h[k] = gen_message_filter}
  ->(service, messages, &cancel) {
    if service
      [service] + service_filters[service.user_obj.id].(messages)
    else
      system = messages.select(&:system?)
      if system.empty?
        cancel.call
      else
        [nil, system] end end } end
gen_scrollbars(widget) click to toggle source

widget のためのスクロールバーを作って返す

Args

widget

Gtk::TextView

Return

縦スクロールバーと横スクロールバー

# File core/plugin/console/console.rb, line 74
def gen_scrollbars(widget)
  scroll_v = ::Gtk::VScrollbar.new
  scroll_h = ::Gtk::HScrollbar.new
  widget.set_scroll_adjustment(scroll_h.adjustment, scroll_v.adjustment)
  return scroll_v, scroll_h
end
gen_tags(buffer) click to toggle source

タグを作る

Args

buffer

Gtk::TextBuffer

# File core/plugin/console/console.rb, line 84
def gen_tags(buffer)
  type_strict buffer => ::Gtk::TextBuffer
  buffer.create_tag("prompt",
                    foreground_gdk: Gdk::Color.new(0, 0x6666, 0))
  buffer.create_tag("echo",
                    weight: Pango::FontDescription::WEIGHT_BOLD)
  buffer.create_tag("result",
                    foreground_gdk: Gdk::Color.new(0, 0, 0x6666))
  buffer.create_tag("errorclass",
                    foreground_gdk: Gdk::Color.new(0x6666, 0, 0))
  buffer.create_tag("error",
                    weight: Pango::FontDescription::WEIGHT_BOLD,
                    foreground_gdk: Gdk::Color.new(0x9999, 0, 0))
  buffer.create_tag("backtrace",
                    foreground_gdk: Gdk::Color.new(0x3333, 0, 0))
end
get_window_geometry(slug) click to toggle source
# File core/plugin/gtk/gtk.rb, line 520
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
icon_path(photo) click to toggle source
# File core/plugin/libnotify/notify-send.rb, line 24
def icon_path(photo)
  ext = photo.uri.path.split('.').last || 'png'
  fn = File.join(icon_tmp_dir, Digest::MD5.hexdigest(photo.uri.to_s) + ".#{ext}")
  Delayer::Deferred.new.next{
    case
    when FileTest.exist?(fn)
      fn
    else
      photo.download_pixbuf(width: 48, height: 48).next{|p|
        FileUtils.mkdir_p(icon_tmp_dir)
        photo.pixbuf(width: 48, height: 48).save(fn, 'png')
        fn
      }
    end
  }
end
icon_tmp_dir() click to toggle source
# File core/plugin/libnotify/notify-send.rb, line 41
        def icon_tmp_dir
  File.join(Environment::TMPDIR, 'libnotify', 'icon').freeze
end
imsorry() click to toggle source
# File core/plugin/bugreport/bugreport.rb, line 48
def imsorry
  _("%{mikutter} が突然終了してしまったみたいで ヽ('ω')ノ三ヽ('ω')ノもうしわけねぇもうしわけねぇ")%{mikutter: Environment::NAME}+"\n"+
    _('OKボタンを押したら、自動的に以下のテキストが送られます。これがバグを直すのにとっても役に立つんですよ。よかったら送ってくれません?')
end
intent_choose_dialog(intents, model: nil, uri: model.uri) click to toggle source
# File core/plugin/intent_selector/intent_selector.rb, line 44
def intent_choose_dialog(intents, model: nil, uri: model.uri)
  dialog = Gtk::Dialog.new('開く - %{application_name}' % {application_name: Environment::NAME})
  dialog.window_position = Gtk::Window::POS_CENTER
  dialog.add_button(Gtk::Stock::OK, Gtk::Dialog::RESPONSE_OK)
  dialog.add_button(Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL)
  dialog.vbox.closeup(Gtk::Label.new("%{uri}\nを開こうとしています。どの方法で開きますか?" % {uri: uri}, false))
  intent_token_builder = {
    uri: uri,
    model: model,
    intent: nil,
    parent: nil }
  intents.inject(nil) do |group, intent|
    if group
      radio = Gtk::RadioButton.new(group, intent.label)
    else
      intent_token_builder[:intent] = intent
      radio = Gtk::RadioButton.new(intent.label) end
    radio.ssc(:toggled) do |w|
      intent_token_builder[:intent] = intent
      false
    end
    radio.ssc(:activate) do |w|
      intent_token_builder[:intent] = intent
      dialog.signal_emit(:response, Gtk::Dialog::RESPONSE_OK)
      false
    end
    dialog.vbox.closeup(radio)
    group || radio
  end
  saving_rule_checkbox(dialog, intent_token_builder, specified_model_slug(model))
  dialog.ssc(:response) do |w, response_id|
    if response_id == Gtk::Dialog::RESPONSE_OK and intent_token_builder[:intent]
      Plugin::Intent::IntentToken.open(**intent_token_builder)
    end
    w.destroy
    false
  end
  dialog.show_all
end
intent_open(intents, model: nil, uri: model.uri) click to toggle source

model: または uri: を開くintentを intents の中から選び出し、その方法で開く。 このメソッドは、まず設定されたルールでintentを選出し、一つにintentが定まれば直ちにそれで開く。 候補が一つに絞れなかった場合は、intent選択ダイアログを表示して、ユーザに決定を仰ぐ。

Args

intents

Intent modelの配列

model:

開くModel。 uri: しかわからない場合は、省略してもよい

uri:

開くURI。 model: を渡している場合は、省略してもよい

# File core/plugin/intent_selector/intent_selector.rb, line 31
def intent_open(intents, model: nil, uri: model.uri)
  recommended, suggested = divide_intents(intents, uri, specified_model_slug(model))
  if recommended.size == 1
    Plugin::Intent::IntentToken.open(
      uri: uri,
      model: model,
      intent: recommended.first,
      parent: nil)
  else
    intent_choose_dialog(recommended + suggested, model: model, uri: uri)
  end
end
into_debug_mode(exception = nil, bind = binding) click to toggle source

–debug オプション付きで起動されている場合、インタプリタに入る。

Args

exception

原因となった例外

binding

インタプリタを実行するスコープ

Return

インタプリタから復帰したら(インタプリタが起動されたら)true、 デバッグモードではない、pryがインストールされていない等、起動に失敗したらfalse

# File core/utils.rb, line 136
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
jump_seq(name) click to toggle source
# File core/plugin/change_account/change_account.rb, line 78
def jump_seq(name)
  if defined? @sequence[name]
    store(:tutorial_sequence, name)
    if @sequence.has_key? name
      @sequence[name].call
    else
      @sequence[:first].call
    end end end
last_active_pane() click to toggle source
# File core/plugin/command/command.rb, line 305
def last_active_pane
  @last_active_pane ||= {} end
lazy(&proc) click to toggle source

遅延評価オブジェクトを作成する。ブロックを取って呼ばれた場合は Lazy のインスタンスを、 何も取らずに読んだ場合は MethodCallDelayer のインスタンスを返す。

# File core/lib/lazy.rb, line 86
def lazy(&proc)
  if proc
    Lazy.new(&proc)
  else
    MethodCallDelayer.new end end
list_modify_member(list, cache: :keep, service: fail) click to toggle source

リストのメンバーを取得する

Args

list

リスト

cache

キャッシュの利用方法

service

list にアクセス可能なユーザのService

Return

deferred

# File core/plugin/list/list.rb, line 81
def list_modify_member(list, cache: :keep, service: fail)
  service.list_members( list_id: list[:id],
                        mode: list[:mode],
                        cache: false).next{ |users|
    list.add_member(users) if users
    service.list_statuses(:id => list[:id],
                          :cache => cache).next{ |res|
      if res.is_a?(Array) and using?(list)
        Plugin.call(:extract_receive_message, datasource_slug(list), res) end
    }.terminate
  }.trap { |error|
    if defined?(error.httpresponse.code) && 404 == error.httpresponse.code.to_i
      Plugin.call(:list_destroy, service, [list])
      Plugin.activity :error, _("リスト「%{list_name} (#%{list_id})」は削除されているか、@%{screen_name} が閲覧することを禁止されています") % {
        screen_name: service.user_obj.idname,
        list_id: list[:id],
        list_name: list[:full_name] }
    else
      Deferred.fail(error)
    end }.terminate(_("リスト %{list_name} (#%{list_id}) のメンバーの取得に失敗しました") % {
                      list_name: list[:full_name],
                      list_id: list[:id] }) end
localfile_hash(hash) click to toggle source
# File core/plugin/aspectframe/aspectframe.rb, line 89
def localfile_hash(hash)
  File.expand_path(File.join(Plugin::AspectFrame::CACHE_DIR, "#{hash.upcase}_5.png")) end
log(prefix, object) click to toggle source

エラーログに記録する。 内部処理用。外部からは呼び出さないこと。

# File core/utils.rb, line 230
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
      $log_lock.synchronize do
        if msg.is_a? Exception
          __write_stderr(msg.to_s)
          __write_stderr(msg.backtrace.join("\n"))
        else
          __write_stderr(msg) end 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

ログファイルを取得設定

# File core/utils.rb, line 273
def logfile(fn = nil)
  if(fn) then
    $logfile = fn
  end
  $logfile or nil
end
main() click to toggle source
# File core/plugin/bugreport/bugreport.rb, line 53
def main
  Gtk::VBox.new(false, 0).
    closeup(Gtk::IntelligentTextview.new(imsorry)).
    pack_start(Gtk::ScrolledWindow.
               new.set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_ALWAYS).
               add(Gtk::IntelligentTextview.new(backtrace)))
end
mainthread_only() click to toggle source

メインスレッド以外で呼び出されたらThreadErrorを投げる

# File core/utils.rb, line 214
def mainthread_only
  unless Thread.main == Thread.current
    raise ThreadError.new('The method can calls only main thread. but called by another thread.') end end
melt() click to toggle source

freezeされていない同じ内容のオブジェクトを作って返す。 メルト 溶けてしまいそう (実装が)dupだなんて 絶対に 言えない

# File core/utils.rb, line 333
def melt
  if frozen? then dup else self end end
menu_widget(widgets_dict) click to toggle source
metamorphose(code: raise, assign: Set.new, extract_condition: nil) click to toggle source

条件をこう、くいっと変形させてな

# File core/plugin/extract/extract.rb, line 296
def metamorphose(code: raise, assign: Set.new, extract_condition: nil)
  extract_condition ||= Hash[Plugin.filtering(:extract_condition, []).first.map{ |condition| [condition.slug, condition] }]
  case code
  when MIKU::Atom
    return code
  when MIKU::List
    return true if code.empty?
    condition = if code.size <= 2
                  extract_condition[code.car]
                else
                  extract_condition[code.cdr.car] end
    case condition
    when Plugin::Extract::ExtensibleSexpCondition
      metamorphose_sexp(code: code, condition: condition)
    when Plugin::Extract::ExtensibleCondition
      assign << "#{condition.slug} = Plugin::Extract::Calc.new(message, extract_condition[:#{condition.slug}])"
      if condition.operator
        code
      else
        # MIKU::Cons.new(:call, MIKU::Cons.new(condition.slug, nil))
        [:call, condition.slug]
      end
    else
      if code.cdr.car.is_a? Symbol and not %[and or not].include?(code.car)
        raise Plugin::Extract::ConditionNotFoundError, _('抽出条件 `%{condition}\ が見つかりませんでした') % {condition: code.cdr.car} end
      code.map{|node| metamorphose(code: node,
                                   assign: assign,
                                   extract_condition: extract_condition) } end end end
metamorphose_sexp(code: raise, condition: raise) click to toggle source
# File core/plugin/extract/extract.rb, line 325
def metamorphose_sexp(code: raise, condition: raise)
  miku_context = MIKU::SymbolTable.new
  miku_context[:compare] = MIKU::Cons.new(code.car, nil)
  miku_context[:args] = MIKU::Cons.new(code.cdr.cdr, nil)
  begin
    miku(condition.sexp, miku_context)
  rescue => exception
    error "error occured in code #{MIKU.unparse(condition.sexp)}"
    error miku_context
    raise exception end end
miku(node, scope=MIKU::SymbolTable.new) click to toggle source
# File core/miku/miku.rb, line 15
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
# File core/miku/miku.rb, line 28
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
mikutter_error() click to toggle source
# File core/plugin/bugreport/bugreport.rb, line 98
def mikutter_error
  name = File.expand_path(File.join(Environment::TMPDIR, 'mikutter_error'))
  if FileTest.exist?(name)
    file_get_contents(name) end end
miquire(*args) click to toggle source

ミクってかわいいよねぇ。 ツインテールいいよねー。 どう良いのかを書くとコードより長くなりそうだから詳しくは書かないけどいいよねぇ。 ふたりで寒い時とかに歩いてたら首にまいてくれるんだよー。 我ながらなんてわかりやすい説明なんだろう。 (訳: Miquire.miquire のエイリアス)

# File core/miquire.rb, line 16
def miquire(*args)
  Miquire.miquire(*args)
end
modify_extract_tabs() click to toggle source

抽出タブの現在の内容を保存する

# File core/plugin/extract/extract.rb, line 261
def modify_extract_tabs
  UserConfig[:extract_tabs] = extract_tabs.values.map(&:export_to_userconfig)
  self end
mute?(params) click to toggle source

そのイベントをミュートするかどうかを返す(trueなら表示しない)

# File core/plugin/activity/activity.rb, line 63
def mute?(params)
  mute_kind = UserConfig[:activity_mute_kind]
  if mute_kind.is_a? Array
    return true if mute_kind.map(&:to_s).include? params[:kind].to_s end
  mute_kind_related = UserConfig[:activity_mute_kind_related]
  if mute_kind_related
    return true if mute_kind_related.map(&:to_s).include?(params[:kind].to_s) and !params[:related] end
  false end
mutebutton(user) click to toggle source
# File core/plugin/user_detail_view/user_detail_view.rb, line 143
def mutebutton(user)
  changer = lambda{ |new, widget|
    if new === nil
      UserConfig[:muted_users] and UserConfig[:muted_users].include?(user.idname)
    elsif new
      add_muted_user(user)
    else
      remove_muted_user(user)
    end
  }
  Mtk::boolean(changer, _('ミュート')) end
no_mainthread() click to toggle source

メインスレッドで呼び出されたらThreadErrorを投げる

# File core/utils.rb, line 219
def no_mainthread
  if Thread.main == Thread.current
    raise ThreadError.new('The method can not calls main thread. but called by main thread.') end end
notice(msg) click to toggle source

一般メッセージを表示する。

# File core/utils.rb, line 114
def notice(msg)
  log "notice", msg if Mopt.error_level >= 3
end
now() click to toggle source
# File core/plugin/aspectframe/aspectframe.rb, line 36
def now
  Plugin::AspectFrame.now
end
object_get_contents(fn) click to toggle source

ファイル fn の内容からオブジェクトを読み込む。 fn は、object_put_contents() で保存されたファイルでなければならない。

# File core/utils.rb, line 81
def object_get_contents(fn)
  File.open(fn, 'rb'.freeze){ |input|
    Marshal.load input
  }
end
open_model(model, token: nil) click to toggle source

model をUI上で開く。 このメソッドが呼ばれたらIntentTokenを生成して、開くことを試みる。 #open_uriは、Modelが必要になった時にURIからModelの取得生成を試みるが、 このメソッドはヒントとして model を与えるため、探索が発生せず高速に処理できる。

Args

model

対象となるDiva::Model

# File core/plugin/intent/intent.rb, line 128
def open_model(model, token: nil)
  intents = Plugin.filtering(:intent_select_by_model_slug, model.class.slug, Set.new).last
  if token
    intents = intents.reject{|intent| token.intent_ancestors.include?(intent) }
  end
  head = intents.first(2)
  case head.size
  when 0
    open_uri(model.uri, token: token)
  when 1
    intent = head.first
    Plugin::Intent::IntentToken.open(
      uri: model.uri,
      model: model,
      intent: intent,
      parent: token)
  else
    Plugin.call(:intent_select, intents, model)
  end
end
open_uri(uri, token: nil) click to toggle source

uri をUI上で開く。 このメソッドが呼ばれたらIntentTokenを生成して、開くことを試みる。 #open_modelのほうが高速なので、modelオブジェクトが存在するならばopen_modelを呼ぶこと。

Args

uri

対象となるURI

token:

親となるIntentToken

# File core/plugin/intent/intent.rb, line 95
def open_uri(uri, token: nil)
  model_slugs = Plugin.filtering(:model_of_uri, uri.freeze, Set.new).last
  if model_slugs.empty?
    error "model not found to open for #{uri}"
    return
  end
  intents = model_slugs.lazy.flat_map{|model_slug|
    Plugin.filtering(:intent_select_by_model_slug, model_slug, []).last
  }
  if token
    intents = intents.reject{|intent| token.intent_ancestors.include?(intent) }
  end
  head = intents.first(2)
  case head.size
  when 0
    error "intent not found to open for #{model_slugs.to_a}"
    return
  when 1
    Plugin::Intent::IntentToken.open(
      uri: uri,
      intent: head.first,
      parent: token)
  else
    Plugin.call(:intent_select, intents, uri)
  end
end
openurl(url) click to toggle source
# File core/plugin/web/web.rb, line 10
def openurl(url)
  if UserConfig[:url_open_specified_command]
    url_open_command = UserConfig[:url_open_command]
    bg_system(url_open_command, url)
  elsif(defined? Win32API) then
    shellExecuteA = Win32API.new('shell32.dll','ShellExecuteA',%w(p p p p p i),'i')
    shellExecuteA.call(0, 'open', url, 0, 0, 1)
  else
    url_open_command = find_url_open_command
    if url_open_command
      bg_system(url_open_command, url)
    else
      activity :system, _("この環境で、URLを開くためのコマンドが判別できませんでした。設定の「表示→URLを開く方法」で、URLを開く方法を設定してください。") end end
rescue => exception
  title = _('コマンド "%{command}" でURLを開こうとしましたが、開けませんでした。設定の「表示→URLを開く方法」で、URLを開く方法を設定してください。') % {command: url_open_command}
  description = {
    title: title,
    message: exception.to_s,
    backtrace: exception.backtrace.join("\n") }
  activity :system, title,
           error: exception,
           description: "%{title}\n\n%{message}\n\n%{backtrace}" % description
end
pane_order_delete(i_pane) click to toggle source

ペインを順序リストから削除する

Args

i_pane

ペイン

# File core/plugin/gtk/gtk.rb, line 566
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

平行評価オブジェクトを作成する

# File core/lib/lazy.rb, line 97
def parallel(&proc)
  Parallel.new(&proc) end
popup() click to toggle source
posted_url_length(url) click to toggle source

URL url がTwitterに投稿された時に何文字としてカウントされるかを返す

Args

url

String URL

Return

Fixnum URLの長さ

# File core/plugin/core/core.rb, line 35
def posted_url_length(url)
  if url.start_with?('https://'.freeze)
    @twitter_configuration[:short_url_length_https] || 23
  else
    @twitter_configuration[:short_url_length] || 22 end end
preview_icons(dir) click to toggle source

プレビューアイコンのリスト

# File core/plugin/skin_setting_gtk/skin_setting_gtk.rb, line 4
def preview_icons(dir)
  famous_icons = [ "timeline.png", "reply.png", "activity.png", "directmessage.png" ]
  skin_icons = Dir.glob(File.join(dir, "*.png")).sort.map { |_| File.basename(_) }

  (famous_icons + skin_icons).uniq.select { |_| File.exist?(File.join(dir, _)) }[0, 12]
end
preview_widget(info) click to toggle source

スキンのプレビューを表示するウィジェットを生成する

# File core/plugin/skin_setting_gtk/skin_setting_gtk.rb, line 12
def preview_widget(info)
  fix = Gtk::Fixed.new
  frame = Gtk::Frame.new
  box = Gtk::HBox.new(false)

  preview_icons(info[:dir]).each { |_|
    photos = Enumerator.new{|y|
      Plugin.filtering(:photo_filter, File.join(info[:dir], _), y)
    }
    image = Gtk::Image.new photos.first.load_pixbuf(width: 32, height: 32) do |pixbuf|
      image = pixbuf
    end
    box.pack_start(image, false, false)
  }

  fix.put(frame.add(box, nil), 16, 0)
end
profile_head(user, intent_token) click to toggle source

ユーザのプロフィールのヘッダ部を返す

Args

user

表示するUser

intent_token

ユーザを開くときに利用するIntent

Return

ヘッダ部を表すGtkコンテナ

# File core/plugin/user_detail_view/user_detail_view.rb, line 256
def profile_head(user, intent_token)
  eventbox = ::Gtk::EventBox.new
  eventbox.ssc('visibility-notify-event'){
    eventbox.style = background_color
    false }

  icon = ::Gtk::EventBox.new.add(::Gtk::WebIcon.new(user.icon_large, UserConfig[:profile_icon_size], UserConfig[:profile_icon_size]).tooltip(_('アイコンを開く')))
  icon.ssc(:button_press_event) do |this, event|
    Plugin.call(:open, user.icon_large)
    true end
  icon.ssc(:realize) do |this|
    this.window.set_cursor(Gdk::Cursor.new(Gdk::Cursor::HAND2))
    false end

  icon_alignment = Gtk::Alignment.new(0.5, 0, 0, 0)
                   .set_padding(*[UserConfig[:profile_icon_margin]]*4)

  eventbox.add(::Gtk::VBox.new(false, 0).
                add(::Gtk::HBox.new.
                     closeup(icon_alignment.add(icon)).
                     add(::Gtk::VBox.new.closeup(user_name(user, intent_token)).closeup(profile_table(user)))))
end
profile_table(user) click to toggle source

プロフィールの上のところの格子になってる奴をかえす

Args

user

表示するUser

Return

プロフィールのステータス部を表すGtkコンテナ

# File core/plugin/user_detail_view/user_detail_view.rb, line 315
def profile_table(user)
  _, profile_columns = Plugin.filtering(:user_detail_view_header_columns, user, [
                                          ['tweets',     user[:statuses_count].to_s],
                                          ['favs',       user[:favourites_count].to_s],
                                          ['followings', user[:friends_count]],
                                          ['followers',  user[:followers_count]],
                                        ])
  ::Gtk::Table.new(2, profile_columns.size).tap{|table|
    profile_columns.each_with_index do |column, index|
      key, value = column
      table.
        attach(::Gtk::Label.new(value.to_s).right, 0, 1, index, index+1).
        attach(::Gtk::Label.new(key.to_s)  .left , 1, 2, index, index+1)
    end
  }.set_row_spacing(0, 4).
    set_row_spacing(1, 4).
    set_column_spacing(0, 16)
end
refresh(cache=:keep) click to toggle source

Service について saved search を取得する

Args

cache

キャッシュの利用方法

Return

deferred

# File core/plugin/saved_search/saved_search.rb, line 81
def refresh(cache=:keep)
  Deferred.when(*Service.map { |service| refresh_for_service(service, cache) }) end
refresh_for_service(service, cache=:keep) click to toggle source

あるServiceに対してのみ saved search 一覧を取得する

Args

service

Service 対象となるService

cache

キャッシュの利用方法

Return

deferred

# File core/plugin/saved_search/saved_search.rb, line 90
def refresh_for_service(service, cache=:keep)
  service.saved_searches(cache: cache).next{ |res|
    if res
      saved_searches = {}
      res.each{ |record|
        saved_searches[record[:id]] = Plugin::SavedSearch::SavedSearch.new(record[:id],
                                                                           URI.decode(record[:query]),
                                                                           URI.decode(record[:name]),
                                                                           :"savedsearch_#{record[:id]}",
                                                                           service) }
      new_ids = saved_searches.keys
      old_ids = timelines.values.select{|s| s.service == service }.map(&:id)
      (new_ids - old_ids).each{ |id| add_tab(saved_searches[id]) }
      (old_ids - new_ids).each{ |id| delete_tab(id) }
      new_ids.each{ |id| rewind_timeline(saved_searches[id]) } end }.terminate end
register_cache(saved_search) click to toggle source

保存した検索の情報をキャッシュに登録する

Args

saved_search

保存した検索

# File core/plugin/saved_search/saved_search.rb, line 109
def register_cache(saved_search)
  type_strict saved_search => Plugin::SavedSearch::SavedSearch
  cache = at(:last_saved_search_state, {}).melt
  cache[saved_search.id] = {
    id: saved_search.id,
    query: saved_search.query,
    name: saved_search.name,
    slug: saved_search.slug,
    service_id: saved_search.service.user_obj.id
  }
  store(:last_saved_search_state, cache) end
relation() click to toggle source
# File core/plugin/followingcontrol/followingcontrol.rb, line 127
def relation
  @relation end
relation_bar(user) click to toggle source

フォロー関係を表示する

Args

user

対象となるユーザ

Return

リレーションバーのウィジェット(Gtk::VBox)

# File core/plugin/user_detail_view/user_detail_view.rb, line 174
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.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.icon, 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.icon, 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(Skin[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(Skin.get_path(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_id: user.id).next{ |msg|
              Plugin.call(event, me, Users.new([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
# File core/plugin/user_detail_view/user_detail_view.rb, line 162
def remove_muted_user(user)
  type_strict user => User
  atomic{
    muted = (UserConfig[:muted_users] ||= []).melt
    muted.delete(user.idname)
    UserConfig[:muted_users] = muted } end
request_token(reset = false) click to toggle source
# File core/plugin/change_account/change_account.rb, line 87
def request_token(reset = false)
  if !@request_token || reset
    twitter = MikuTwitter.new
    twitter.consumer_key = Environment::TWITTER_CONSUMER_KEY
    twitter.consumer_secret = Environment::TWITTER_CONSUMER_SECRET
    @request_token = twitter.request_oauth_token end
  @request_token end
require_if_exist(file) click to toggle source

存在するかわからないrubyファイル file を読み込む。 ただし、file が存在しない場合は例外を投げずにfalseを返す。

# File core/utils.rb, line 94
def require_if_exist(file)
  begin
    require file
    true
  rescue LoadError
    warn "require-if-exist: file not found: #{file}"
    false end end
reset_activity(model) click to toggle source

アクティビティの古い通知を一定時間後に消す

# File core/plugin/activity/activity.rb, line 73
def reset_activity(model)
  Reserver.new(60, thread: Delayer) do
    if not model.destroyed?
      iters = model.to_enum(:each).to_a
      remove_count = iters.size - UserConfig[:activity_max]
      if remove_count > 0
        iters[-remove_count, remove_count].each do |_m,_p,iter|
          @contains_uris.delete(iter[ActivityView::URI])
          model.remove(iter)
        end
      end
      reset_activity(model)
    end
  end
end
result_strict(must, &block) click to toggle source

blockの評価結果がチェックをパスしなかった場合にabortする

# File core/utils.rb, line 207
def result_strict(must, &block)
  result = block.call
  type_strict(result => must)
  result
end
ret_nth(num=0) click to toggle source

num 番目の引数をそのまま返す関数を返す

# File core/utils.rb, line 50
def ret_nth(num=0)
  lambda { |*arg| arg[num] } end
retrieve_user(user_id, services = Service.services.shuffle) click to toggle source
# File core/plugin/user_detail_view/user_detail_view.rb, line 22
def retrieve_user(user_id, services = Service.services.shuffle)
  if services.nil? or services.empty?
    return nil end
  service = services.car
  (service/:users/:show).user(user_id: user_id,
                              cache: false).trap{
    retrieve_user(user_id, services.cdr) } end
revision() click to toggle source
# File core/plugin/bugreport/bugreport.rb, line 92
def revision
  begin
    open('|env LC_ALL=C svn info').read.match(/Revision\s*:\s*(\d+)/)[1]
  rescue
    '' end end
rewind(direction, target) click to toggle source
# File core/plugin/followingcontrol/followingcontrol.rb, line 130
def rewind(direction, target)
  relation = @relation[direction.to_sym]
  target.each { |service|
    user = service.user_obj
    service.__send__(direction, cache: :keep, user_id: user[:id]).next { |users|
      primitive = relation[user]
      if primitive and not primitive.empty?
        created = users - primitive
        Plugin.call("#{direction}_created".to_sym, service, created) if not created.empty?
        destroyed = primitive - users
        Plugin.call("#{direction}_destroy".to_sym, service, destroyed) if not destroyed.empty?
      else
        relation[user] = Users.new(users)
        Plugin.call("#{direction}_modified".to_sym, service, users)
      end
    }
  }
end
rewind_timeline(saved_search) click to toggle source

タイムラインを更新する

Args

saved_search

saved search

# File core/plugin/saved_search/saved_search.rb, line 69
def rewind_timeline(saved_search)
  type_strict saved_search => Plugin::SavedSearch::SavedSearch
  saved_search.service.search(q: saved_search.query, count: 100).next{ |res|
    timeline(saved_search.slug) << res if res.is_a? Array
  }.trap{ |e|
    timeline(saved_search.slug) << Mikutter::System::Message.new(description: _("更新中にエラーが発生しました (%{error})") % {error: e.to_s}) } end
rotten?() click to toggle source
# File core/plugin/display_requirements/display_requirements.rb, line 121
def rotten?
end
saving_rule_checkbox(dialog, intent_token_builder, model_slug) click to toggle source
# File core/plugin/intent_selector/intent_selector.rb, line 84
def saving_rule_checkbox(dialog, intent_token_builder, model_slug)
  save_check = Gtk::CheckButton.new(_('次回から、次の内容から始まるURLはこの方法で開く'))
  rule = Gtk::Entry.new.set_text(intent_token_builder[:uri].to_s)
  rule.sensitive = false
  save_check.ssc(:toggled) do |widget|
    rule.sensitive = widget.active?
    false
  end
  dialog.ssc(:response) do |w, response_id|
    if response_id == Gtk::Dialog::RESPONSE_OK and intent_token_builder[:intent] and save_check.active?
      add_intent_rule(intent: intent_token_builder[:intent],
                      str: rule.text,
                      rule: 'start',
                      model_slug: model_slug)
    end
    false
  end
  dialog.vbox.
    closeup(save_check).
    closeup(rule)
end
scan(slug, messages) click to toggle source

messagesの中で、タイムライン slug に入れるべきものがあれば入れる

Args

slug

タイムラインスラッグ

messages

入れるMessageの配列

# File core/plugin/smartthread/smartthread.rb, line 13
def scan(slug, messages)
  seeds = @timelines[slug]
  i_timeline = timeline(slug)
  if i_timeline and seeds
    SerialThread.new do
      messages.each{ |message|
        message.each_ancestor { |cur|
          if seeds.include? cur
            i_timeline << message end } } end end end
send() click to toggle source
# File core/plugin/bugreport/bugreport.rb, line 61
def send
  Thread.new{
    begin
      exception = crashed_exception
      m = exception.backtrace.first.match(/(.+?):(\d+)/)
      crashed_file, crashed_line = m[1], m[2]
      param = {
        'backtrace' => JSON.generate(exception.backtrace.map{ |msg| msg.gsub(FOLLOW_DIR, '{MIKUTTER_DIR}') }),
        'file' => crashed_file.gsub(FOLLOW_DIR, '{MIKUTTER_DIR}'),
        'line' => crashed_line,
        'exception_class' => exception.class,
        'description' => exception.to_s,
        'ruby_version' => RUBY_VERSION,
        'rubygtk_version' => Gtk::BINDING_VERSION.join('.'),
        'platform' => RUBY_PLATFORM,
        'url' => 'exception',
        'version' => Environment::VERSION }
      Net::HTTP.start('mikutter.hachune.net', 4567){ |http|
        console = mikutter_error
        param['stderr'] = console if console
        eparam = encode_parameters(param)
        http.post('/', eparam) }
      File.delete(File.expand_path(File.join(Environment::TMPDIR, 'mikutter_error'))) rescue nil
      File.delete(File.expand_path(File.join(Environment::TMPDIR, 'crashed_exception'))) rescue nil
      Plugin.activity :system, _("エラー報告を送信しました。ありがとう♡")
      Plugin.call :send_bugreport, param
    rescue Timeout::Error, StandardError => e
      Plugin.activity :system, _("ピャアアアアアアアアアアアアアアアアアアアアアアアwwwwwwwwwwwwwwwwwwwwww")
      Plugin.activity :error, e.to_s, exception: e
    end } end
sequence() click to toggle source

茶番オブジェクトを新しく作る

# File core/plugin/change_account/change_account.rb, line 68
def sequence
  # 茶番でしか使わないクラスなので、チュートリアル時だけロードする
  require File.join(File.dirname(__FILE__), "interactive")
  Plugin::ChangeAccount::Interactive.generate end
service_by_user_id(user_id) click to toggle source

ユーザIDから対応する Service を返す

Args

user_id

ユーザID

Return

Service か、見つからなければnil

# File core/plugin/saved_search/saved_search.rb, line 134
def service_by_user_id(user_id)
  Service.find{ |service| service.user_obj.id == user_id } end
service_register(service) click to toggle source

service を監視対象に入れる

Args

service

監視するservice

# File core/plugin/followingcontrol/followingcontrol.rb, line 152
def service_register(service)
  @activating_services << service
  user = service.user_obj
  Deferred.when(service.followings(cache: true, user_id: user[:id]),
                service.followers(cache: true, user_id: user[:id])).next { |followings, followers|
    @relation.followings[user] = Users.new(followings)
    @relation.followers[user] = Users.new(followers)
    Plugin.call(:followings_modified, service, @relation.followings[user])
    Plugin.call(:followers_modified, service, @relation.followers[user])
    @activating_services.delete(service)
  }.trap {
    @activating_services.delete(service)
  }
end
set_available_lists(service, newlist) click to toggle source

service がフォローしているリストを新しく設定する

Args

service

Service リストのフォロイー TODO: これ実装する

newlist

Enumerable 新しいリスト

Return

self

# File core/plugin/list/list.rb, line 156
def set_available_lists(service, newlist)
  type_strict service => Service, newlist => Enumerable
  newlist_ary = newlist.to_a
  available_list_of_service = available_lists(service).to_a
  created = newlist_ary - available_list_of_service
  deleted = available_list_of_service - newlist_ary
  Plugin.call(:list_created, service, UserLists.new(created)) if not created.empty?
  Plugin.call(:list_destroy, service, UserLists.new(deleted)) if not deleted.empty?
  @available_lists[service.user_obj] = UserLists.new(newlist_ary).freeze
  @all_available_lists = nil
  Plugin.call(:list_data, service, available_lists(service)) if not(created.empty? and deleted.empty?)
  self end
setting_container() click to toggle source

設定のGtkウィジェット

# File core/plugin/list_settings/list_settings.rb, line 13
def setting_container
  tab = Plugin::ListSettings::Tab.new(self)
  available_lists.each{ |list|
    iter = tab.model.append
    iter[Plugin::ListSettings::Tab::SLUG] = list[:full_name]
    iter[Plugin::ListSettings::Tab::LIST] = list
    iter[Plugin::ListSettings::Tab::NAME] = list[:name]
    iter[Plugin::ListSettings::Tab::DESCRIPTION] = list[:description]
    iter[Plugin::ListSettings::Tab::PUBLICITY] = list[:mode] }
  Gtk::HBox.new.add(tab).closeup(tab.buttons(Gtk::VBox)).show_all end
setting_window() click to toggle source
# File core/plugin/settings/settings.rb, line 21
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::SettingDSL.new(Plugin.instance plugin)
    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_message(message, token, force=false) click to toggle source
# File core/plugin/message_detail_view/message_detail_view.rb, line 22
def show_message(message, token, force=false)
  slug = "message_detail_view-#{message.uri}".to_sym
  if !force and Plugin::GUI::Tab.exist?(slug)
    Plugin::GUI::Tab.instance(slug).active!
  else
    container = Gtk::DivaHeaderWidget.new(message, intent_token: token)
    i_cluster = tab slug, _("詳細タブ") do
      set_icon Skin['message.png']
      set_deletable true
      temporary_tab
      shrink
      nativewidget container
      expand
      cluster nil end
    Thread.new {
      Plugin.filtering(:message_detail_view_fragments, [], i_cluster, message).first
    }.next { |tabs|
      tabs.map(&:last).each(&:call)
    }.next {
      if !force
        i_cluster.active! end
    }.trap{ |exc|
      error exc
    }
  end
end
show_profile(user, token, force=false) click to toggle source
# File core/plugin/user_detail_view/user_detail_view.rb, line 42
def show_profile(user, token, force=false)
  slug = "profile-#{user.uri}".to_sym
  if !force and Plugin::GUI::Tab.exist?(slug)
    Plugin::GUI::Tab.instance(slug).active!
  else
    UserConfig[:profile_opened_tabs] = ((UserConfig[:profile_opened_tabs] || []) + [user.id]).uniq
    container = profile_head(user, token)
    i_cluster = tab slug, _("%{user} のプロフィール") % {user: user[:name]} do
      set_icon user.icon
      set_deletable true
      shrink
      nativewidget container
      expand
      cluster nil end
    Thread.new {
      Plugin.filtering(:user_detail_view_fragments, [], i_cluster, user).first
    }.next { |tabs|
      tabs.map(&:last).each(&:call)
    }.next {
      Plugin.call(:filter_stream_reconnect_request)
      if !force
        i_cluster.active! end }
  end end
skin_infos() click to toggle source

スキンの情報を得る

# File core/plugin/skin_setting_gtk/skin_setting_gtk.rb, line 44
def skin_infos()
  default_info = { :vanilla => { :face => _("(デフォルト)"), :dir => Skin::default_dir } }

  skin_infos_tmp = skin_list.inject({}) { |hash, _|
    hash[_] = { :face => _, :dir => File.join(Skin::SKIN_ROOT, _) }
    hash
  }

  default_info.merge(skin_infos_tmp)
end
skin_list() click to toggle source

インストール済みスキンのリスト

# File core/plugin/skin_setting_gtk/skin_setting_gtk.rb, line 31
def skin_list()
  dirs = Dir.glob(File.join(Skin::SKIN_ROOT, "*")).select { |_|
    File.directory?(_)
  }.select { |_|
    Dir.glob(File.join(_, "*.png")).length != 0
  }.map { |_|
    _.gsub(/^#{Skin::SKIN_ROOT}\//, "")
  }

  dirs
end
slide_timeline_focus(src, dest) click to toggle source

タイムライン src で選択されているディスプレイ上のy座標が同じ dest のツイートに フォーカスを移動する

Args

src

フォーカスを取得するタイムライン

dest

フォーカスを設定するタイムライン

# File core/plugin/command/command.rb, line 277
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
  if y
    Plugin.call(:gui_timeline_move_cursor_to, dest, y) end end
spec_generate(dir) click to toggle source
# File core/boot/shell/spec.rb, line 76
def spec_generate(dir)
  dir = File.expand_path(dir)
  specfile = File.join(dir, ".mikutter.yml")
  legacy_specfile = File.join(dir, "spec")
  spec = if FileTest.exist?(specfile)
           YAML.load_file(specfile)
         elsif FileTest.exist?(legacy_specfile)
           YAML.load_file(legacy_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 /q/i
      abort
    when /s/i
      return
    when /\A[0-9]+\Z/
      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
specified_model_slug(model) click to toggle source

model のmodel slugを文字列で得る。

Args

model

Diva::Modelのインスタンス又はnil

Return

String

Modelのslug。 model がnilだった場合は空文字列

# File core/plugin/intent_selector/intent_selector.rb, line 134
def specified_model_slug(model)
  model ? model.class.slug.to_s : ''
end
start() click to toggle source
# File core/plugin/rest/rest.rb, line 24
def start
  if Service.instances.empty?
    @account_observer ||= on_service_registered do |s|
      start
      @account_observer.detach
      @account_observer = nil end
  else
    Service.instances.each do |service|
      @crawlers.each{ |s| s.call(service) }
    end
    Reserver.new(60, thread: Delayer) do
      start
    end
  end
end
streamerror(exception) click to toggle source
# File core/plugin/streaming/filter.rb, line 65
def streamerror(exception)
  @success_flag = false
  @fail.notify(exception) end
tab_update_icon(i_tab) click to toggle source
# File core/plugin/gtk/gtk.rb, line 508
def tab_update_icon(i_tab)
  type_strict i_tab => Plugin::GUI::TabLike
  tab = widgetof(i_tab)
  if tab
    tab.tooltip(i_tab.name)
    tab.remove(tab.child) if tab.child
    if i_tab.icon
      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
tcor(*types) click to toggle source

types のうちいずれかとis_a?関係ならtrueを返すProcオブジェクトを返す

# File core/utils.rb, line 194
def tcor(*types)
  lambda{ |v| types.any?{ |c| v.is_a?(c) } } end
the_day?() click to toggle source
# File core/plugin/aspectframe/aspectframe.rb, line 92
def the_day?
  Plugin::AspectFrame::THE_DAY.cover?(now) end
timeline_destroy_event(i_timeline) click to toggle source

Timelineウィジェットのdestroyのコールバックを返す

Args

i_timeline

タイムラインのインターフェイス

Return

Proc

# File core/plugin/gtk/gtk.rb, line 204
def timeline_destroy_event(i_timeline)
  lambda { |this|
    i_timeline.destroy
    false } end
timeline_focus_in_event(i_timeline) click to toggle source

Timelineウィジェットのfocus_in_eventのコールバックを返す

Args

i_timeline

タイムラインのインターフェイス

Return

Proc

# File core/plugin/gtk/gtk.rb, line 184
def timeline_focus_in_event(i_timeline)
  lambda { |this, event|
    if this.focus?
      i_timeline.active!(true, true) end
    false } end
timeline_key_press_event(i_timeline) click to toggle source

Timelineウィジェットのkey_press_eventのコールバックを返す

Args

i_timeline

タイムラインのインターフェイス

Return

Proc

# File core/plugin/gtk/gtk.rb, line 195
def timeline_key_press_event(i_timeline)
  lambda { |widget, event|
    Plugin::GUI.keypress(::Gtk::keyname([event.keyval ,event.state]), i_timeline) } end
timeline_storage() click to toggle source
# File core/plugin/user_detail_view/user_detail_view.rb, line 12
def timeline_storage # {slug: user}
  @timeline_storage ||= {} end
timelines() click to toggle source

id => SavedSearch

# File core/plugin/saved_search/saved_search.rb, line 43
def timelines
  @timelines ||= {} end
transform(icon) click to toggle source
# File core/plugin/aspectframe/aspectframe.rb, line 79
def transform(icon)
  if icon.perma_link
    Enumerator.new{|y|
      Plugin.filtering(:photo_filter, "http://toshia.dip.jp/img/api/#{Digest::MD5.hexdigest(icon.perma_link.to_s)[0,2].upcase}.png", y)
    }.first
  else
    icon
  end
end
type_check(args, &proc) click to toggle source

引数のチェックをすべてパスした場合のみブロックを実行する チェックに引っかかった項目があればwarnを出力してブロックは実行せずにnilを返す。 チェックはassocできる配列か、Hashで定義する。

type_check(value => nil,              # チェックしない(常にパス)
           value => Module,           # その型とis_a?関係ならパス
           value => [:method, *args], # value.method(*args)が真を返せばパス
           value => lambda{ |x| ...}) # xにvalueを渡して実行し、真を返せばパス

チェックをすべてパスしたかどうかを真偽値で返す。 ブロックが指定されていれば、それを実行してブロックの実行結果を返す メモ: いずれかのタイプに一致するチェックを定義するにはtcorを使う

type_check object => tcor(Array, Hash)
# File core/utils.rb, line 170
def type_check(args, &proc)
  check_function = lambda{ |val, check|
    if not check
      true
    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)
    elsif check.respond_to?(:call)
      check.call(val) 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の戻り値を返す

# File core/utils.rb, line 199
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, intent_token) click to toggle source

ユーザ名を表示する

Args

user

表示するUser

intent_token

ユーザを開くときに利用するIntent

Return

ユーザの名前の部分のGtkコンテナ

# File core/plugin/user_detail_view/user_detail_view.rb, line 285
def user_name(user, intent_token)
  w_name = ::Gtk::TextView.new
  w_name.editable = false
  w_name.cursor_visible = false
  w_name.wrap_mode = Gtk::TextTag::WRAP_CHAR
  w_name.ssc(:event) do |this, event|
    if event.is_a? ::Gdk::EventMotion
      this.get_window(::Gtk::TextView::WINDOW_TEXT)
        .set_cursor(::Gdk::Cursor.new(::Gdk::Cursor::XTERM)) end
    false end
  if Gtk::BINDING_VERSION >= [3,1,2]
    tag_sn = w_name.buffer.create_tag('sn', {foreground: '#0000ff',
                                             weight: Pango::Weight::BOLD,
                                             underline: Pango::Underline::SINGLE})
  else
    tag_sn = w_name.buffer.create_tag('sn', {foreground: '#0000ff',
                                             weight: Pango::FontDescription::WEIGHT_BOLD,
                                             underline: Pango::AttrUnderline::SINGLE})
  end
  tag_sn.ssc(:event, &user_screen_name_event_callback(user, intent_token))

  w_name.buffer.insert(w_name.buffer.start_iter, user[:idname], tag_sn)
  w_name.buffer.insert(w_name.buffer.end_iter, "\n#{user[:name]}")
  Gtk::VBox.new.add(w_name) end
user_screen_name_event_callback(user, intent_token) click to toggle source
# File core/plugin/user_detail_view/user_detail_view.rb, line 339
def user_screen_name_event_callback(user, intent_token)
  lambda do |tag, textview, event, iter|
    case event
    when ::Gdk::EventButton
      if event.event_type == ::Gdk::Event::BUTTON_RELEASE and event.button == 1
        if intent_token.respond_to?(:forward)
          intent_token.forward
        else
          Plugin.call(:open, user.uri)
        end
        next true
      end
    when ::Gdk::EventMotion
      textview
        .get_window(::Gtk::TextView::WINDOW_TEXT)
        .set_cursor(::Gdk::Cursor.new(::Gdk::Cursor::HAND2))
    end
    false
  end
end
using?(list) click to toggle source

list が抽出タブで使われていて、更新を要求されているなら真を返す

Args

list

UserList 調べるリスト

Return

使われていたら真

# File core/plugin/list/list.rb, line 123
def using?(list)
  using_lists.include? list end
using_lists() click to toggle source

現在データソースで使用されているリストを返す

Return

Enumerable データソースで使われているリスト

# File core/plugin/list/list.rb, line 107
def using_lists
  list_ids = Set.new Plugin.filtering(:extract_tabs_get, []).first.map{|tab|
    tab[:sources]
  }.select{ |sources|
    sources.is_a? Enumerable
  }.inject(Set.new, &:merge).map{ |source|
    $1.to_i if source.to_s =~ /[a-zA-Z0-9_]+_list_(\d+)/
  }.compact
  available_lists.lazy.select do |list|
    list_ids.include? list[:id] end end
warn(msg) click to toggle source

警告メッセージを表示する。

# File core/utils.rb, line 119
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 が存在しない場合は一番最後のインデックスを返す

# File core/utils.rb, line 105
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
widget_join_tab(i_tab, widget) click to toggle source

タブ tabwidget を入れる

Args

i_tab

タブ

widget

Gtkウィジェット

# File core/plugin/gtk/gtk.rb, line 479
def widget_join_tab(i_tab, widget)
  tab = widgetof(i_tab)
  return false if not tab
  i_pane = i_tab.parent
  return false if not i_pane
  pane = widgetof(i_pane)
  return false if not pane
  is_tab = i_tab.is_a?(Plugin::GUI::Tab)
  has_child = is_tab and
    not(i_tab.temporary_tab?) and
    not(i_tab.children.any?{ |child|
          not child.is_a? Plugin::GUI::TabToolbar })
  if has_child
    Plugin.call(:rewind_window_order, i_pane.parent) end
  container_index = pane.get_tab_pos_by_tab(tab)
  if container_index
    container = pane.get_nth_page(container_index)
    if container
      return container.pack_start(widget, i_tab.pack_rule[container.children.size]) end end
  if tab.parent
    raise Plugin::Gtk::GtkError, "Gtk Widget #{tab.inspect} of Tab(#{i_tab.slug.inspect}) has parent Gtk Widget #{tab.parent.inspect}" end
  container = ::Gtk::TabContainer.new(i_tab).show_all
  container.ssc(:key_press_event){ |w, event|
    Plugin::GUI.keypress(::Gtk::keyname([event.keyval ,event.state]), i_tab) }
  container.pack_start(widget, i_tab.pack_rule[container.children.size])
  pane.append_page(container, tab)
  pane.set_tab_reorderable(container, true).set_tab_detachable(container, true)
  true end
widgetof(cuscadable) click to toggle source

cuscadable に対応するGtkオブジェクトを返す

Args

cuscadable

ウィンドウ、ペイン、タブ、タイムライン等

Return

対応するGtkオブジェクト

# File core/plugin/gtk/gtk.rb, line 579
def widgetof(cuscadable)
  type_strict cuscadable => :slug
  result = @slug_dictionary.get(cuscadable)
  if result and result.destroyed?
    nil
  else
    result end end
yamlisp(node) click to toggle source
# File core/miku/yamlisp.rb, line 5
def yamlisp(node)
  if(node.is_a? YamLisp::Node) then
    node.yamlisp_eval
  else
    node
  end
end