mmi 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,14 @@
1
+ require 'cli/ui'
2
+
3
+ require 'mmi/interactive/modloader'
4
+ require 'mmi/interactive/assets'
5
+
1
6
  module Mmi
2
7
  module Interactive
3
8
  class Updater
9
+ include Modloader
10
+ include Assets
11
+
4
12
  attr_accessor :processor
5
13
  attr_accessor :file_path
6
14
 
@@ -10,157 +18,36 @@ module Mmi
10
18
  end
11
19
 
12
20
  def run!
13
- while true
21
+ loop do
14
22
  to_update = CLI::UI::Prompt.ask('What do you want to update?') do |handler|
15
23
  [
16
- ['assets' , :assets ],
17
- ['quit & save changes' , :quit_save ],
24
+ ['modloader', :modloader ],
25
+ ['assets', :assets ],
26
+ ['quit & save changes', :quit_save ],
18
27
  ['quit & discard changes', :quit_discard],
19
28
  ].each do |name, result|
20
- handler.option(name) do |s|
29
+ handler.option(name) do
21
30
  result
22
31
  end
23
32
  end
24
33
  end
25
34
 
26
35
  case to_update
27
- when :assets
28
- update_assets
29
- when :quit_save
30
- file_path = CLI::UI::Prompt.ask('Filename', default: self.file_path, is_file: true)
31
- yaml = self.processor.content.to_yaml
32
-
33
- File.write(File.expand_path(file_path, Dir.pwd), yaml)
34
- break
35
- when :quit_discard
36
- break
37
- else
38
- raise 'Consider yourself lucky, you found a bug.'
39
- end
40
- end
41
- end
42
-
43
- def update_assets
44
- while true
45
- assets = processor.assets.assets
46
-
47
- choice = CLI::UI::Prompt.ask('Which asset do you want to change?') do |handler|
48
- assets.each do |asset|
49
- handler.option(asset.display_name) do |s|
50
- asset
51
- end
52
- end
53
-
54
- handler.option('add' , &:to_sym)
55
- handler.option('quit', &:to_sym)
56
- end
57
-
58
- case choice
59
- when :quit
60
- break
61
- when :add
62
- add_asset
63
- else
64
- update_asset(choice)
65
- end
66
- end
67
- end
68
-
69
- def add_asset
70
- source_type = CLI::UI::Prompt.ask('Choose a source type.') do |handler|
71
- [
72
- 'github',
73
- ].each do |type|
74
- handler.option(type, &:to_sym)
75
- end
76
-
77
- handler.option('quit', &:to_sym)
78
- end
79
-
80
- case source_type
81
- when :quit
82
- false
83
- when :github
84
- options = {
85
- 'source' => {
86
- 'type' => 'github',
87
- 'asset_id' => 0,
88
- }
89
- }
90
-
91
- options['source']['owner' ] = CLI::UI::Prompt.ask('Who is the owner of the source repository?').strip
92
- options['source']['repo' ] = CLI::UI::Prompt.ask('What is the name of the source repository?').strip
93
- options['source']['install_dir'] = CLI::UI::Prompt.ask('In which directory should the asset be placed?', default: 'mods').strip
94
- options['source']['filename' ] = CLI::UI::Prompt.ask('Under which filename should the asset be saved? (leave empty for release asset name)', allow_empty: true).strip.then do |filename|
95
- filename == '' ? nil : filename
96
- end
97
-
98
- options['source'].compact!
99
-
100
- source = Mmi::Source::Github.new(options['source'])
101
-
102
- if update_asset(source)
103
- self.processor.content['assets'] ||= []
104
-
105
- self.processor.content['assets'].push(options)
106
- self.processor.assets.assets.push(source)
107
-
108
- true
109
- else
110
- CLI::UI.puts('Aborting asset addition. No change will be made.', color: CLI::UI::Color::RED)
111
-
112
- false
113
- end
114
- end
115
- end
116
-
117
- def update_asset(asset)
118
- case asset
119
- when Mmi::Source::Github
120
- releases = ::Github::Client::Repos::Releases.new.list(owner: asset.owner, repo: asset.repo, per_page: 100)
121
-
122
- release = CLI::UI::Prompt.ask('Choose a release.') do |handler|
123
- releases.select do |release|
124
- release.assets.any?
125
- end.each do |release|
126
- handler.option(release.name) do |s|
127
- release
128
- end
129
- end
130
-
131
- handler.option('quit', &:to_sym)
132
- end
133
-
134
- case release
135
- when :quit
136
- false
137
- else
138
- release_asset = CLI::UI::Prompt.ask('Choose an asset.') do |handler|
139
- release.assets.each do |a|
140
- handler.option(a.name) do |s|
141
- a
142
- end
143
- end
36
+ when :modloader
37
+ update_modloader
38
+ when :assets
39
+ update_assets
40
+ when :quit_save
41
+ file_path = CLI::UI::Prompt.ask('Filename', default: self.file_path, is_file: true)
42
+ yaml = self.processor.options.to_yaml
144
43
 
145
- handler.option('quit', &:to_sym)
146
- end
147
-
148
- case release_asset
149
- when :quit
150
- false
44
+ File.write(File.expand_path(file_path, Dir.pwd), yaml)
45
+ break
46
+ when :quit_discard
47
+ break
151
48
  else
152
- asset.options.delete('release')
153
- asset.options.delete('file' )
154
-
155
- asset.options['asset_id'] = release_asset.id
156
-
157
- true
158
- end
49
+ raise 'Consider yourself lucky, you found a bug.'
159
50
  end
160
- else
161
- CLI::UI.puts('This asset cannot be updated.', color: CLI::UI::Color::RED)
162
-
163
- false
164
51
  end
165
52
  end
166
53
  end
@@ -1,10 +1,9 @@
1
- require 'cli/ui'
2
1
  require 'mmi'
3
2
  require 'mmi/interactive/updater'
4
3
 
5
4
  module Mmi
6
5
  module Interactive
7
- class << self;
6
+ class << self
8
7
  def update(file_path, processor)
9
8
  Updater.new(file_path, processor).run!
10
9
  end
@@ -1,50 +1,63 @@
1
+ require 'mmi/assets_processor'
1
2
  require 'mmi/modloader/none'
2
3
  require 'mmi/modloader/fabric'
3
- require 'mmi/assets_processor'
4
+ require 'mmi/option_attributes'
4
5
  require 'mmi/semver'
5
6
 
6
7
  module Mmi
7
8
  class ModFileProcessor
8
- attr_reader :content
9
+ include OptionAttributes
9
10
 
10
- attr_reader :version
11
- attr_reader :profile_dir
11
+ opt_accessor :version
12
+ opt_accessor :modloader
13
+ opt_accessor :assets
12
14
 
13
- attr_reader :modloader
14
- attr_reader :assets
15
+ attr_reader :parsed_modloader
16
+ attr_reader :parsed_assets
15
17
 
16
- def initialize(content)
17
- @content = content
18
-
19
- @version = content['version' ]
20
- @profile_dir = content['profile_dir'] || Mmi.minecraft_dir
18
+ def initialize(options)
19
+ @options = options
21
20
 
21
+ parse!
22
+ end
23
+
24
+ def parse!
22
25
  version = Semver.parse(self.version)
23
26
  lib_version = Semver.parse(Mmi::VERSION)
24
27
 
25
28
  if self.version
26
29
  if version.major <= lib_version.major
27
30
  if version.minor > lib_version.minor
28
- Mmi.warn %Q{Config file specified "version" #{version}, but MMI is at #{lib_version}. Some features might not be supported.}
31
+ Mmi.warn %Q(Config file specified "version" #{version}, but MMI is at #{lib_version}. Some features might not be supported.)
29
32
  end
30
33
 
31
- ml = content['modloader']
32
- @modloader = if ml
33
- case ml['name']
34
- when 'none'
35
- Modloader::None.new(ml)
36
- when 'fabric'
37
- Modloader::Fabric.new(ml)
34
+ ml = self.modloader
35
+ @parsed_modloader =
36
+ if ml
37
+ case ml['name']
38
+ when 'none'
39
+ Modloader::None.new(ml)
40
+ when 'fabric'
41
+ Modloader::Fabric.new(ml)
42
+ else
43
+ raise Mmi::InvalidAttributeError, "Unkown modloader #{ml['name'].inspect}."
44
+ end
38
45
  else
39
- raise Mmi::InvalidAttributeError, %Q{Unkown modloader #{ml['name'].inspect}.}
46
+ Modloader::None.new
47
+ end
48
+
49
+
50
+ if self.assets
51
+ if self.assets.is_a?(Hash)
52
+ @parsed_assets = AssetsProcessor.new(self.assets)
53
+ else
54
+ raise Mmi::InvalidAttributeError, %Q(Invalid "assets": expected Hash but received #{self.assets.inspect})
40
55
  end
41
56
  else
42
- Modloader::None.new
57
+ raise Mmi::MissingAttributeError, 'Missing "assets".'
43
58
  end
44
-
45
- @assets = AssetsProcessor.new(self.profile_dir, content['assets'])
46
59
  else
47
- raise Mmi::InvalidAttributeError, %Q{Config file specified "version" #{version}, but MMI is at #{lib_version}.}
60
+ raise Mmi::InvalidAttributeError, %Q(Config file specified "version" #{version}, but MMI is at #{lib_version}.)
48
61
  end
49
62
  else
50
63
  raise Mmi::MissingAttributeError, 'Missing "version".'
@@ -52,8 +65,8 @@ module Mmi
52
65
  end
53
66
 
54
67
  def install
55
- self.modloader.install
56
- self.assets.install
68
+ self.parsed_modloader.install
69
+ self.parsed_assets.install
57
70
  end
58
71
  end
59
72
  end
@@ -1,31 +1,41 @@
1
+ require 'fileutils'
2
+ require 'nokogiri'
3
+
4
+ require 'mmi/cached_download'
5
+ require 'mmi/option_attributes'
6
+
1
7
  module Mmi
2
8
  module Modloader
3
9
  class Fabric
4
- attr_reader :options
10
+ include Mmi::OptionAttributes
5
11
 
6
- attr_reader :version
7
- attr_reader :install_type
8
- attr_reader :mcversion
9
- attr_reader :install_dir
12
+ opt_accessor :version
13
+ opt_accessor :install_type
14
+ opt_accessor :mcversion, 'minecraft_version'
15
+ opt_accessor(:install_dir ) { Mmi.minecraft_dir }
16
+ opt_accessor(:download_mc, 'download_minecraft') { false }
10
17
 
11
18
  def initialize(options)
12
19
  @options = options
13
20
 
14
- @version = options['version' ]
15
- @install_type = options['install_type' ]
16
- @mcversion = options['minecraft_version']
17
- @install_dir = options['install_dir' ] || Mmi.minecraft_dir
18
-
21
+ parse!
22
+ end
23
+
24
+ def parse!
19
25
  if self.version
20
26
  if self.install_type
21
- if ['client', 'server'].include?(self.install_type)
27
+ if allowed_install_types.include?(self.install_type)
22
28
  if self.mcversion
23
- # Pass.
29
+ if [true, false].include?(self.download_mc)
30
+ # Pass.
31
+ else
32
+ raise Mmi::InvalidAttributeError, %Q(Invalid "modloader.download_minecraft". Expecting true or false, got #{self.download_mc.inspect}.)
33
+ end
24
34
  else
25
35
  raise Mmi::MissingAttributeError, 'Missing "modloader.minecraft_version".'
26
36
  end
27
37
  else
28
- raise Mmi::InvalidAttributeError, %Q{Invalid "modloader.install_type". Expecting "client" or "server", got #{self.install_type.inspect}.}
38
+ raise Mmi::InvalidAttributeError, %Q(Invalid "modloader.install_type". Expecting "client" or "server", got #{self.install_type.inspect}.)
29
39
  end
30
40
  else
31
41
  raise Mmi::MissingAttributeError, 'Missing "modloader.install_type".'
@@ -35,8 +45,31 @@ module Mmi
35
45
  end
36
46
  end
37
47
 
48
+ def allowed_install_types
49
+ %w[
50
+ client
51
+ server
52
+ ]
53
+ end
54
+
55
+ def base_uri
56
+ 'https://maven.fabricmc.net/net/fabricmc/fabric-installer'
57
+ end
58
+
59
+ def metadata_uri
60
+ File.join(base_uri, 'maven-metadata.xml')
61
+ end
62
+
63
+ def metadata_sha512sum_uri
64
+ "#{metadata_uri}.sha512"
65
+ end
66
+
67
+ def metadata_path
68
+ File.join(Mmi.cache_dir, 'fabric_maven_metadata.xml')
69
+ end
70
+
38
71
  def installer_uri
39
- "https://maven.fabricmc.net/net/fabricmc/fabric-installer/#{self.version}/fabric-installer-#{self.version}.jar"
72
+ File.join(base_uri, self.version, "fabric-installer-#{self.version}.jar")
40
73
  end
41
74
 
42
75
  def installer_sha512sum_uri
@@ -55,34 +88,16 @@ module Mmi
55
88
  Mmi.info "Downloading fabric-installer version #{self.version.inspect}."
56
89
 
57
90
  begin
58
- FileUtils.mkdir_p(Mmi.cache_dir)
59
-
60
- expected_hexdigest = URI.open(installer_sha512sum_uri).read
61
-
62
- if !File.exists?(installer_path) || expected_hexdigest != Digest::SHA512.hexdigest(File.read(installer_path))
63
- stream = URI.open(installer_uri)
64
-
65
- IO.copy_stream(stream, installer_path)
66
-
67
- actual_hexdigest = Digest::SHA512.hexdigest(File.read(installer_path))
68
-
69
- if expected_hexdigest == actual_hexdigest
70
- # Pass.
71
- else
72
- Mmi.fail! "Expected fabric installer to have SHA512 sum #{expected_hexdigest.inspect} but received #{actual_hexdigest.inspect}."
73
- end
74
- else
75
- Mmi.info 'Using cached fabric-installer.'
76
- end
91
+ Mmi::CachedDownload.download_cached(installer_uri, installer_path, sha512_uri: installer_sha512sum_uri)
77
92
  rescue OpenURI::HTTPError => e
78
- Mmi.fail! %Q{Error when requesting fabric installer. Maybe "modloader.version" == #{version.inspect} is invalid.\n#{e.inspect}}
93
+ Mmi.fail! %Q(Error when requesting fabric installer. Maybe "modloader.version" == #{version.inspect} is invalid.\n#{e.inspect})
79
94
  end
80
95
  end
81
96
 
82
97
  def run_installer
83
98
  FileUtils.mkdir_p(absolute_install_dir)
84
99
 
85
- if system('java', '-jar', installer_path, self.install_type, '-dir', absolute_install_dir, '-noprofile', '-mcversion', self.mcversion)
100
+ if system('java', '-jar', installer_path, self.install_type, '-dir', absolute_install_dir, '-noprofile', '-mcversion', self.mcversion, self.download_mc ? '-downloadMinecraft' : '')
86
101
  # Pass.
87
102
  else
88
103
  Mmi.fail! 'Failed to install Fabric modloader.'
@@ -93,6 +108,20 @@ module Mmi
93
108
  download_installer
94
109
  run_installer
95
110
  end
111
+
112
+ def available_versions
113
+ begin
114
+ Mmi::CachedDownload.download_cached(metadata_uri, metadata_path, sha512_uri: metadata_sha512sum_uri)
115
+ rescue OpenURI::HTTPError => e
116
+ Mmi.fail! "Error when requesting available fabric installer versions.\n#{e.inspect}"
117
+ end
118
+
119
+ xml = File.open(metadata_path) do |f|
120
+ Nokogiri::XML(f)
121
+ end
122
+
123
+ xml.xpath('/metadata/versioning/versions/version').map(&:inner_html)
124
+ end
96
125
  end
97
126
  end
98
- end
127
+ end
@@ -2,10 +2,12 @@ module Mmi
2
2
  module Modloader
3
3
  class None
4
4
  def initialize(options=nil)
5
+ # Not installing anything requires no configuration or setup.
5
6
  end
6
7
 
7
8
  def install
9
+ # Nothing to do.
8
10
  end
9
11
  end
10
12
  end
11
- end
13
+ end
@@ -0,0 +1,18 @@
1
+ require 'json'
2
+ require 'open-uri'
3
+
4
+ module Mmi
5
+ module ModrinthApi
6
+ BASE_URL = URI('https://api.modrinth.com/api/v1/')
7
+
8
+ class << self
9
+ def mod(name)
10
+ JSON.parse((BASE_URL + "mod/#{CGI.escape(name)}").open.read)
11
+ end
12
+
13
+ def mod_versions(mod_id)
14
+ JSON.parse((BASE_URL + "mod/#{mod_id}/version").open.read)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,38 @@
1
+ module Mmi
2
+ module OptionAttributes
3
+ attr_reader :options
4
+
5
+ def self.included(klass)
6
+ klass.extend(ClassMethods)
7
+ end
8
+
9
+ module ClassMethods
10
+ def opt_reader(attr, name=attr.to_s, &block)
11
+ define_method(:"#{attr.to_sym}") do
12
+ result = self.options[name]
13
+
14
+ if result.nil? && block_given?
15
+ block.call
16
+ else
17
+ result
18
+ end
19
+ end
20
+ end
21
+
22
+ def opt_writer(attr, name=attr.to_s)
23
+ define_method(:"#{attr.to_sym}=") do |value|
24
+ if value.nil?
25
+ self.options.delete(name)
26
+ else
27
+ self.options[name] = value
28
+ end
29
+ end
30
+ end
31
+
32
+ def opt_accessor(attr, name=attr.to_s, &block)
33
+ opt_reader(attr, name, &block)
34
+ opt_writer(attr, name )
35
+ end
36
+ end
37
+ end
38
+ end
data/lib/mmi/semver.rb CHANGED
@@ -10,11 +10,11 @@ module Mmi
10
10
  self.patch = patch
11
11
  end
12
12
 
13
- def self.parse(s)
14
- if m = /\A(?<major>\d+)(\.(?<minor>\d+))?(\.(?<patch>\d+))?\z/.match(s.strip)
15
- new(m[:major], m[:minor], m[:patch])
13
+ def self.parse(version)
14
+ if (m = /\A(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)\z/.match(version.strip))
15
+ new(m[:major].to_i, m[:minor].to_i, m[:patch].to_i)
16
16
  else
17
- raise "Version string not in valid format: #{s.inspect}"
17
+ raise "Version string not in valid format: #{version.inspect}"
18
18
  end
19
19
  end
20
20
  end
@@ -1,43 +1,43 @@
1
+ require 'fileutils'
2
+ require 'open-uri'
3
+
4
+ require 'mmi/github_api'
5
+ require 'mmi/option_attributes'
6
+
1
7
  module Mmi
2
8
  module Source
3
9
  class Github
4
- attr_reader :options
10
+ include Mmi::OptionAttributes
5
11
 
6
- attr_reader :owner
7
- attr_reader :repo
8
- attr_reader :install_dir
9
- attr_reader :filename
12
+ opt_accessor :owner
13
+ opt_accessor :repo
14
+ opt_accessor :install_dir
15
+ opt_accessor :filename
10
16
 
11
- attr_reader :asset_id
12
- attr_reader :release
13
- attr_reader :file
17
+ opt_accessor :asset_id
18
+ opt_accessor :release
19
+ opt_accessor :file
14
20
 
15
21
  def initialize(options)
16
22
  @options = options
17
23
 
18
- @owner = options['owner' ]
19
- @repo = options['repo' ]
20
- @asset_id = options['asset_id' ]
21
- @release = options['release' ]
22
- @file = options['file' ]
23
- @install_dir = options['install_dir']
24
- @filename = options['filename' ]
25
-
24
+ parse!
25
+ end
26
+
27
+ def parse!
26
28
  if self.owner
27
29
  if self.repo
28
30
  if self.install_dir
29
31
  if self.asset_id
30
32
  # Pass.
31
- else
32
- if self.release
33
- if self.file
34
- # Pass.
35
- else
36
- raise Mmi::MissingAttributeError, 'Missing "source.file" from asset because "source.asset_id" is not provided.'
37
- end
33
+ elsif self.release
34
+ if self.file
35
+ # Pass.
38
36
  else
39
- raise Mmi::MissingAttributeError, 'Missing "source.release" from asset because "source.asset_id" is not provided.'
37
+ raise Mmi::MissingAttributeError, 'Missing "source.file" from asset because "source.asset_id" is not provided.'
40
38
  end
39
+ else
40
+ raise Mmi::MissingAttributeError, 'Missing "source.release" from asset because "source.asset_id" is not provided.'
41
41
  end
42
42
  else
43
43
  raise Mmi::MissingAttributeError, 'Missing "source.install_dir" from asset.'
@@ -55,7 +55,7 @@ module Mmi
55
55
  end
56
56
 
57
57
  def cached_asset_response
58
- @asset_get ||= ::Github::Client::Repos::Releases::Assets.new.get(owner: self.owner, repo: self.repo, id: self.asset_id)
58
+ @cached_asset_response ||= Mmi::GithubApi.client.release_asset("/repos/#{self.owner}/#{self.repo}/releases/assets/#{self.asset_id}")
59
59
  end
60
60
 
61
61
  def download_url
@@ -75,11 +75,11 @@ module Mmi
75
75
  FileUtils.mkdir_p(install_dir)
76
76
 
77
77
  begin
78
- stream = URI.open(download_url)
78
+ stream = URI.parse(download_url).open
79
79
 
80
80
  IO.copy_stream(stream, filepath)
81
81
  rescue OpenURI::HTTPError => e
82
- Mmi.fail! %Q{Error when requesting asset.\n#{e.inspect}}
82
+ Mmi.fail! "Error when requesting asset.\n#{e.inspect}"
83
83
  end
84
84
  end
85
85
 
@@ -88,4 +88,4 @@ module Mmi
88
88
  end
89
89
  end
90
90
  end
91
- end
91
+ end