mmi 0.1.0

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 013ded8588d84a0bae1c8220648116e88d5d3953047ccc31b239fd508ce4f4b4
4
+ data.tar.gz: 8a01d3a0fbada194c797f3b8f7f630853d6c964b7c5c05fbfdf7e51565fa9895
5
+ SHA512:
6
+ metadata.gz: e7774ec272fbf8cef3887bd3da36cc6f482dee216974996d69f9a2794e3a4a5622a4e8ef0375980073593dcc96f7f6ca0a290db91f43716ed8c6e15f1690f7e6
7
+ data.tar.gz: d375e06d6174bf417d4938b4136f33d5cacac67ab6c785a6bbf346f1c19b72f24fbeda611ebc7fa101116c662e4fdb812a7332759b765bbebf64dd806e557527
data/README.adoc ADDED
@@ -0,0 +1,91 @@
1
+ :toc:
2
+ :toc-placement!:
3
+
4
+ = MMI
5
+
6
+ MMI is some Ruby code that should make downloading and installing modloaders, mods, resource packs and other files for Minecraft easier.
7
+ Configuring the downloads/installs is done by reading a YAML file.
8
+
9
+ Ideally, a user would not need to know or write any YAML to create a configuration file.
10
+ This is still work in progress.
11
+
12
+ The application is currently CLI-only, but should work on Linux, MacOS and Windows.
13
+
14
+ toc::[]
15
+
16
+ = Installation
17
+
18
+ == Prerequisites
19
+
20
+ To run the application, Ruby version 2.5+ has to be installed.
21
+ For maximum compatibility, use version 3.0+.
22
+
23
+ == Steps
24
+
25
+ Clone the repository and navigate into its root directory.
26
+
27
+ [source,bash]
28
+ ----
29
+ $ git clone https://github.com/expeehaa/mmi.git
30
+ # or
31
+ $ hg clone https://github.com/expeehaa/mmi.git
32
+
33
+ $ cd mmi
34
+ ----
35
+
36
+ Install gem dependencies.
37
+
38
+ [source,bash]
39
+ ----
40
+ $ bundle install
41
+ ----
42
+
43
+ If you get an error that the lockfile requires `bundler` version 2.0+, install it first.
44
+
45
+ [source,bash]
46
+ ----
47
+ $ gem install bundler
48
+ ----
49
+
50
+ = Basic usage
51
+
52
+ In the repository root directory, run `exe/mmi`.
53
+
54
+ [source,text]
55
+ ----
56
+ Syntax: exe/mmi <config file> [action=install]
57
+
58
+ where action is one of
59
+ install - Install the modloader and assets as specified in the config file.
60
+ validate - Validate the config file.
61
+ update - Update the config file interactively.
62
+ ----
63
+
64
+ The script currently does not support any arguments besides the ones listed above.
65
+ Trying to get help by running `exe/mmi --help` or similar will not work.
66
+
67
+ = Example configs
68
+
69
+ Example configuration files can be found in `examples/`.
70
+ They are completely working and download and install files into `tmp/` so that your `.minecraft` folder will stay unharmed.
71
+
72
+
73
+ = TODOs
74
+
75
+ * Improve argument parsing.
76
+ * Write tests.
77
+ * Add command to interactively create a config file.
78
+ * Be able to update/change modloader interactively.
79
+ * Be able to remove assets interactively.
80
+ * Think about advising everyone to use another file extension than `.yaml` (e.g. `.mmi`, `.mmiconf`).
81
+ * Try to make a Java-executable `.jar` file using `warbler` and `JRuby`.
82
+
83
+ = Contributing
84
+
85
+ Contributions are very welcome.
86
+ If you intend to create a pull request, please choose a branch name describing its changes (i.e. not some generic term like `master` or `fix`, but e.g. `fix_typo` or `add_interactive_update_command`).
87
+
88
+ Please be aware that I have a very specific code style and am interested in keeping it in this repository.
89
+ Some basic rules are specified in the `.editorconfig` file, which your editor should support.
90
+ Other code style rules may have to be exactly specified in the future.
91
+ When in doubt, take a look at the existing code.
data/exe/mmi ADDED
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'psych'
5
+
6
+ require 'mmi'
7
+ require 'mmi/interactive'
8
+
9
+ CLI::UI::StdoutRouter.enable
10
+
11
+ def processor(file)
12
+ content = Psych.load_file(file)
13
+
14
+ begin
15
+ Mmi::ModFileProcessor.new(content)
16
+ rescue Mmi::ValidationError => e
17
+ puts e
18
+ Kernel.exit 1
19
+ end
20
+ end
21
+
22
+ if ARGV.length>0
23
+ file = ARGV.shift
24
+ action_param = ARGV.shift
25
+ action = action_param.nil? ? :install : action_param.to_sym
26
+
27
+ case action
28
+ when :install
29
+ processor(file).install
30
+ when :validate
31
+ processor(file)
32
+
33
+ puts 'File is valid.'
34
+ when :update
35
+ Mmi::Interactive.update(processor(file), file)
36
+ else
37
+ STDERR.puts "Unknown action: #{action_param.inspect}."
38
+ Kernel.exit 1
39
+ end
40
+ else
41
+ puts 'No file given.'
42
+ Kernel.exit 1
43
+ end
data/lib/mmi.rb ADDED
@@ -0,0 +1,44 @@
1
+ require 'semver'
2
+ require 'open-uri'
3
+ require 'fileutils'
4
+ require 'digest'
5
+ require 'github_api'
6
+
7
+ require 'mmi/version'
8
+ require 'mmi/mod_file_processor'
9
+
10
+ module Mmi
11
+ MMI_CACHE_DIR = File.join(Dir.home, '.cache', 'mmi')
12
+ MINECRAFT_DIR = File.join(Dir.home, '.minecraft')
13
+
14
+ def self.cache_dir
15
+ MMI_CACHE_DIR
16
+ end
17
+
18
+ def self.minecraft_dir
19
+ MINECRAFT_DIR
20
+ end
21
+
22
+ class ValidationError < StandardError; end
23
+ class MissingAttributeError < ValidationError; end
24
+ class InvalidAttributeError < ValidationError; end
25
+
26
+ def self.debug(text)
27
+ if ENV['MMI_ENV']=='dev'
28
+ puts text
29
+ end
30
+ end
31
+
32
+ def self.info(text)
33
+ puts text
34
+ end
35
+
36
+ def self.warn(text)
37
+ puts text
38
+ end
39
+
40
+ def self.fail!(text)
41
+ STDERR.puts text
42
+ Kernel.exit 1
43
+ end
44
+ end
@@ -0,0 +1,41 @@
1
+ require 'mmi/source/github'
2
+
3
+ module Mmi
4
+ class AssetsProcessor
5
+ attr_reader :profile_dir
6
+ attr_reader :assets
7
+
8
+ def initialize(profile_dir, assets)
9
+ @profile_dir = profile_dir
10
+
11
+ assets ||= []
12
+
13
+ if assets.is_a?(Array)
14
+ @assets = assets.map.with_index do |asset, index|
15
+ source = asset['source']
16
+
17
+ if source
18
+ type = source['type']
19
+
20
+ case type
21
+ when 'github'
22
+ Source::Github.new(source)
23
+ else
24
+ raise Mmi::InvalidAttributeError, %Q{Invalid "source.type" in asset #{index.inspect}: #{type.inspect}}
25
+ end
26
+ else
27
+ raise Mmi::MissingAttributeError, %Q{Missing "source" in asset #{index.inspect}.}
28
+ end
29
+ end
30
+ else
31
+ raise Mmi::InvalidAttributeError, %Q{Invalid "assets": expected Array or nothing, got #{self.assets.inspect}.}
32
+ end
33
+ end
34
+
35
+ def install
36
+ assets.each do |asset|
37
+ asset.install(self.profile_dir)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,13 @@
1
+ require 'cli/ui'
2
+ require 'mmi'
3
+ require 'mmi/interactive/updater'
4
+
5
+ module Mmi
6
+ module Interactive
7
+ class << self;
8
+ def update(file_path, processor)
9
+ Updater.new(file_path, processor).run!
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,168 @@
1
+ module Mmi
2
+ module Interactive
3
+ class Updater
4
+ attr_accessor :processor
5
+ attr_accessor :file_path
6
+
7
+ def initialize(processor, file_path=nil)
8
+ self.processor = processor
9
+ self.file_path = file_path
10
+ end
11
+
12
+ def run!
13
+ while true
14
+ to_update = CLI::UI::Prompt.ask('What do you want to update?') do |handler|
15
+ [
16
+ ['assets' , :assets ],
17
+ ['quit & save changes' , :quit_save ],
18
+ ['quit & discard changes', :quit_discard],
19
+ ].each do |name, result|
20
+ handler.option(name) do |s|
21
+ result
22
+ end
23
+ end
24
+ end
25
+
26
+ 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
144
+
145
+ handler.option('quit', &:to_sym)
146
+ end
147
+
148
+ case release_asset
149
+ when :quit
150
+ false
151
+ 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
159
+ end
160
+ else
161
+ CLI::UI.puts('This asset cannot be updated.', color: CLI::UI::Color::RED)
162
+
163
+ false
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,58 @@
1
+ require 'mmi/modloader/none'
2
+ require 'mmi/modloader/fabric'
3
+ require 'mmi/assets_processor'
4
+
5
+ module Mmi
6
+ class ModFileProcessor
7
+ attr_reader :content
8
+
9
+ attr_reader :version
10
+ attr_reader :profile_dir
11
+
12
+ attr_reader :modloader
13
+ attr_reader :assets
14
+
15
+ def initialize(content)
16
+ @content = content
17
+
18
+ @version = content['version' ]
19
+ @profile_dir = content['profile_dir'] || Mmi.minecraft_dir
20
+
21
+ version = SemVer.parse(self.version)
22
+ lib_version = SemVer.parse(Mmi::VERSION)
23
+
24
+ if self.version
25
+ if version.major <= lib_version.major
26
+ if version.minor > lib_version.minor
27
+ Mmi.warn %Q{Config file specified "version" #{version}, but MMI is at #{lib_version}. Some features might not be supported.}
28
+ end
29
+
30
+ ml = content['modloader']
31
+ @modloader = if ml
32
+ case ml['name']
33
+ when 'none'
34
+ Modloader::None.new(ml)
35
+ when 'fabric'
36
+ Modloader::Fabric.new(ml)
37
+ else
38
+ raise Mmi::InvalidAttributeError, %Q{Unkown modloader #{ml['name'].inspect}.}
39
+ end
40
+ else
41
+ Modloader::None.new
42
+ end
43
+
44
+ @assets = AssetsProcessor.new(self.profile_dir, content['assets'])
45
+ else
46
+ raise Mmi::InvalidAttributeError, %Q{Config file specified "version" #{version}, but MMI is at #{lib_version}.}
47
+ end
48
+ else
49
+ raise Mmi::MissingAttributeError, 'Missing "version".'
50
+ end
51
+ end
52
+
53
+ def install
54
+ self.modloader.install
55
+ self.assets.install
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,98 @@
1
+ module Mmi
2
+ module Modloader
3
+ class Fabric
4
+ attr_reader :options
5
+
6
+ attr_reader :version
7
+ attr_reader :install_type
8
+ attr_reader :mcversion
9
+ attr_reader :install_dir
10
+
11
+ def initialize(options)
12
+ @options = options
13
+
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
+
19
+ if self.version
20
+ if self.install_type
21
+ if ['client', 'server'].include?(self.install_type)
22
+ if self.mcversion
23
+ # Pass.
24
+ else
25
+ raise Mmi::MissingAttributeError, 'Missing "modloader.minecraft_version".'
26
+ end
27
+ else
28
+ raise Mmi::InvalidAttributeError, %Q{Invalid "modloader.install_type". Expecting "client" or "server", got #{self.install_type.inspect}.}
29
+ end
30
+ else
31
+ raise Mmi::MissingAttributeError, 'Missing "modloader.install_type".'
32
+ end
33
+ else
34
+ raise Mmi::MissingAttributeError, 'Missing "modloader.version".'
35
+ end
36
+ end
37
+
38
+ def installer_uri
39
+ "https://maven.fabricmc.net/net/fabricmc/fabric-installer/#{self.version}/fabric-installer-#{self.version}.jar"
40
+ end
41
+
42
+ def installer_sha512sum_uri
43
+ "#{installer_uri}.sha512"
44
+ end
45
+
46
+ def installer_path
47
+ File.join(Mmi.cache_dir, "fabric-installer-#{self.version}.jar")
48
+ end
49
+
50
+ def absolute_install_dir
51
+ File.expand_path(self.install_dir)
52
+ end
53
+
54
+ def download_installer
55
+ Mmi.info "Downloading fabric-installer version #{self.version.inspect}."
56
+
57
+ 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
77
+ rescue OpenURI::HTTPError => e
78
+ Mmi.fail! %Q{Error when requesting fabric installer. Maybe "modloader.version" == #{version.inspect} is invalid.\n#{e.inspect}}
79
+ end
80
+ end
81
+
82
+ def run_installer
83
+ FileUtils.mkdir_p(absolute_install_dir)
84
+
85
+ if system('java', '-jar', installer_path, 'client', '-dir', absolute_install_dir, '-noprofile', '-mcversion', self.mcversion)
86
+ # Pass.
87
+ else
88
+ Mmi.fail! 'Failed to install Fabric modloader.'
89
+ end
90
+ end
91
+
92
+ def install
93
+ download_installer
94
+ run_installer
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,11 @@
1
+ module Mmi
2
+ module Modloader
3
+ class None
4
+ def initialize(options=nil)
5
+ end
6
+
7
+ def install
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,91 @@
1
+ module Mmi
2
+ module Source
3
+ class Github
4
+ attr_reader :options
5
+
6
+ attr_reader :owner
7
+ attr_reader :repo
8
+ attr_reader :install_dir
9
+ attr_reader :filename
10
+
11
+ attr_reader :asset_id
12
+ attr_reader :release
13
+ attr_reader :file
14
+
15
+ def initialize(options)
16
+ @options = options
17
+
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
+
26
+ if self.owner
27
+ if self.repo
28
+ if self.install_dir
29
+ if self.asset_id
30
+ # 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
38
+ else
39
+ raise Mmi::MissingAttributeError, 'Missing "source.release" from asset because "source.asset_id" is not provided.'
40
+ end
41
+ end
42
+ else
43
+ raise Mmi::MissingAttributeError, 'Missing "source.install_dir" from asset.'
44
+ end
45
+ else
46
+ raise Mmi::MissingAttributeError, 'Missing "source.repo" from asset.'
47
+ end
48
+ else
49
+ raise Mmi::MissingAttributeError, 'Missing "source.owner" from asset.'
50
+ end
51
+ end
52
+
53
+ def repository_url
54
+ "https://github.com/#{self.owner}/#{self.repo}"
55
+ end
56
+
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)
59
+ end
60
+
61
+ def download_url
62
+ if self.asset_id
63
+ cached_asset_response.browser_download_url
64
+ else
65
+ "#{repository_url}/releases/download/#{release}/#{file}"
66
+ end
67
+ end
68
+
69
+ def install(dir)
70
+ install_dir = File.expand_path(self.install_dir, dir)
71
+ filepath = File.join(install_dir, self.filename || (self.asset_id ? cached_asset_response.name : self.file))
72
+
73
+ Mmi.info "Downloading #{download_url.inspect} into #{filepath.inspect}."
74
+
75
+ FileUtils.mkdir_p(install_dir)
76
+
77
+ begin
78
+ stream = URI.open(download_url)
79
+
80
+ IO.copy_stream(stream, filepath)
81
+ rescue OpenURI::HTTPError => e
82
+ Mmi.fail! %Q{Error when requesting asset.\n#{e.inspect}}
83
+ end
84
+ end
85
+
86
+ def display_name
87
+ repository_url
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,3 @@
1
+ module Mmi
2
+ VERSION = '0.1.0'
3
+ end
metadata ADDED
@@ -0,0 +1,139 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mmi
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - expeehaa
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-04-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: semver2
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: github_api
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.19'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.19'
41
+ - !ruby/object:Gem::Dependency
42
+ name: cli-ui
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.4'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.4'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.2'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.2'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '13.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '13.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: pry
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.13'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.13'
97
+ description:
98
+ email:
99
+ - expeehaa@outlook.com
100
+ executables:
101
+ - mmi
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - README.adoc
106
+ - exe/mmi
107
+ - lib/mmi.rb
108
+ - lib/mmi/assets_processor.rb
109
+ - lib/mmi/interactive.rb
110
+ - lib/mmi/interactive/updater.rb
111
+ - lib/mmi/mod_file_processor.rb
112
+ - lib/mmi/modloader/fabric.rb
113
+ - lib/mmi/modloader/none.rb
114
+ - lib/mmi/source/github.rb
115
+ - lib/mmi/version.rb
116
+ homepage: https://github.com/expeehaa/mmi
117
+ licenses: []
118
+ metadata: {}
119
+ post_install_message:
120
+ rdoc_options: []
121
+ require_paths:
122
+ - lib
123
+ required_ruby_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ required_rubygems_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ requirements: []
134
+ rubygems_version: 3.2.3
135
+ signing_key:
136
+ specification_version: 4
137
+ summary: Program to install minecraft modloaders, mods and other assets through a
138
+ single config file.
139
+ test_files: []