mmi 0.1.3 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,14 @@
1
+ require 'json'
2
+ require 'open-uri'
3
+
4
+ module Mmi
5
+ module ModrinthApi
6
+ BASE_URL = URI('https://api.modrinth.com/v2/')
7
+
8
+ class << self
9
+ def project_versions(mod_slug)
10
+ JSON.parse((BASE_URL + "project/#{mod_slug}/version").open.read)
11
+ end
12
+ end
13
+ end
14
+ 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