aboutsummaryrefslogtreecommitdiffstats
path: root/core/miquire_plugin.rb
blob: 43ce59cdd8e70549c503257bb4f3749f1d5fa17b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# -*- coding: utf-8 -*-
miquire :core, "miquire", "plugin", "miquire_to_spec"

# プラグインのロードに関すること
module Miquire::Plugin
  class << self
    using Miquire::ToSpec
    include Enumerable

    # ロードパスの配列を返す。
    # ロードパスに追加したい場合は、以下のようにすればいい
    #
    #  Miquire::Plugin.loadpath << 'pathA' << 'pathB'
    def loadpath
      @loadpath ||= [] end

    # プラグインのファイル名(フルパス)で繰り返す。
    def each
      iterated = Set.new
      detected = []
      loadpath.reverse.each { |path|
        Dir[File.join(File.expand_path(path), '*')].each { |file|
          if FileTest.directory?(file) and FileTest.exist?(File.join(file, File.basename(file))+'.rb')
            file = File.join(file, File.basename(file))+'.rb'
          elsif not file.end_with?('.rb'.freeze)
            next end
          plugin_name = File.basename(file, '.rb')
          if not iterated.include? plugin_name
            iterated << plugin_name
            detected << file end } }
      detected.sort_by{ |a|
        [:bundle == get_kind(a) ? 0 : 1, a]
      }.each(&Proc.new) end

    def each_spec
      each{ |path|
        spec = get_spec path
        yield spec if spec } end

    def to_hash
      result = {}
      each_spec{ |spec|
        result[spec[:slug]] = spec }
      result end

    # 受け取ったパスにあるプラグインのスラッグを返す
    # ==== Args
    # [path] パス(String)
    # ==== Return
    # プラグインスラッグ(Symbol)
    def get_slug(path)
      type_strict path => String
      spec = get_spec(path)
      if spec
        spec[:slug]
      else
        File.basename(path, ".rb").to_sym end end

    # specファイルがあればそれを返す
    # ==== Args
    # [path] パス(String)
    # ==== Return
    # specファイルの内容か、存在しなければnil
    def get_spec(path)
      type_strict path => String
      plugin_dir = FileTest.directory?(path) ? path : File.dirname(path)
      spec_filename = File.join(plugin_dir, ".mikutter.yml")
      deprecated_spec = false
      unless FileTest.exist? spec_filename
        spec_filename = File.join(plugin_dir, "spec")
        deprecated_spec = true end
      if FileTest.exist? spec_filename
        YAML.load_file(spec_filename).symbolize
          .merge(kind: get_kind(path),
                 path: plugin_dir,
                 deprecated_spec: deprecated_spec)
      elsif FileTest.exist? path
        { slug: File.basename(path, ".rb").to_sym,
          kind: get_kind(path),
          path: plugin_dir,
          deprecated_spec: false } end end

    def get_spec_by_slug(slug)
      type_strict slug => Symbol
      to_hash[slug] end

    # プラグインがthirdpartyかbundleかを返す
    def get_kind(path)
      type_strict path => String
      if path.start_with?(Environment::PLUGIN_PATH)
        :bundle
      else
        :thirdparty end end

    def load_all
      each_spec{ |spec|
        begin
          load spec
        rescue Miquire::LoadError => e
          ::Plugin.call(:modify_activity,
                        kind: "system",
                        title: "#{spec[:slug]} load failed",
                        date: Time.new,
                        exception: e,
                        description: e.to_s) end } end

    def satisfy_mikutter_version?(spec)
      if defined?(spec[:depends][:mikutter]) and spec[:depends][:mikutter]
        version = Environment::Version.new(*(spec[:depends][:mikutter].split(".").map(&:to_i) + ([0]*4))[0...4])
        if Environment::VERSION < version
          raise Miquire::LoadError, "plugin #{spec[:slug]}: #{Environment::NAME} version too old (#{spec[:depends][:mikutter]} required, but #{Environment::NAME} version is #{Environment::VERSION})"
          return false end end
      true
    end

    def depended_plugins(_spec, recursive: false)
      spec = _spec.to_spec
      unless spec
        error "spec #{_spec.inspect}"
        return false
      end
      if defined? spec[:depends][:plugin]
        if recursive
          local_depends = Array(spec[:depends][:plugin]).map{ |s| Array(s).first.to_sym }
          local_depends += local_depends.map {|s|
            depended_plugins(s, recursive: recursive).map{|d|d[:slug].to_sym}
          }.flatten
          local_depends.uniq.map{|d| d.to_spec }
        else
          Array(spec[:depends][:plugin]).map do |s|
            slug = Array(s).first.to_sym
            if slug
              slug.to_spec
            else
              slug end end end
      else
        [] end end

    def load(_spec)
      return false unless _spec
      spec = _spec.to_spec
      return false unless spec
      return true if ::Plugin.instance_exist?(spec[:slug])
      return false unless satisfy_mikutter_version?(spec)

      depended_plugins(spec).each do |depend|
        begin
          raise Miquire::LoadError unless load(depend)
        rescue Miquire::LoadError
          raise Miquire::LoadError, "plugin #{spec[:slug]}: dependency error: plugin #{depend} was not loaded." end end

      notice "plugin loaded: " + File.join(spec[:path], "#{spec[:slug]}.rb")
      ::Plugin.create(spec[:slug].to_sym) do
        self.spec = spec end
      Kernel.load File.join(spec[:path], "#{spec[:slug]}.rb")
      if spec[:deprecated_spec]
        title = "#{spec[:slug]}: specファイルは非推奨になりました。"
        Plugin.call(:modify_activity,
                    { plugin: spec[:slug],
                      kind: "error",
                      title: title,
                      date: Time.now,
                      spec: spec,
                      description: "#{title}\n代わりに.mikutter.ymlを使ってください。"}) end
      true end
  end
end