mod_organizer 1.0.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 +7 -0
- data/CHANGELOG.md +12 -0
- data/LICENSE.md +31 -0
- data/README.md +68 -0
- data/lib/default_categories.dat +57 -0
- data/lib/mod_organizer/download.rb +76 -0
- data/lib/mod_organizer/mod.rb +104 -0
- data/lib/mod_organizer/source.rb +55 -0
- data/lib/mod_organizer/utils.rb +23 -0
- data/lib/mod_organizer/version.rb +5 -0
- data/lib/mod_organizer.rb +148 -0
- data/spec/mod_organizer_test/helpers.rb +115 -0
- data/spec/mod_organizer_test/scenarios/mod_organizer/download_spec.rb +55 -0
- data/spec/mod_organizer_test/scenarios/mod_organizer/mod_spec.rb +184 -0
- data/spec/mod_organizer_test/scenarios/mod_organizer/source_spec.rb +71 -0
- data/spec/mod_organizer_test/scenarios/mod_organizer_spec.rb +292 -0
- data/spec/mod_organizer_test/scenarios/rubocop_spec.rb +31 -0
- data/spec/spec_helper.rb +100 -0
- metadata +149 -0
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'logger'
|
3
|
+
require 'mod_organizer'
|
4
|
+
|
5
|
+
module ModOrganizerTest
|
6
|
+
|
7
|
+
module Helpers
|
8
|
+
|
9
|
+
# ModOrganizer: The ModOrganizer instance that has been setup
|
10
|
+
attr_reader :mod_organizer
|
11
|
+
|
12
|
+
# String: The instance directory that has been setup
|
13
|
+
attr_reader :instance_dir
|
14
|
+
|
15
|
+
# Prepare a ModOrganizer instance directory
|
16
|
+
# The directory path is stored in an instance variable named @instance_dir.
|
17
|
+
#
|
18
|
+
# Parameters::
|
19
|
+
# * *ini* (Hash< Symbol, Hash<Symbol, String> >): Content of the ini file to overwrite default values [default: {}]
|
20
|
+
# * *instance_name* (String or nil): The instance name to be used to mock a shared installation, or nil for a portable installation [default: nil]
|
21
|
+
def setup_instance_dir(ini: {}, instance_name: nil)
|
22
|
+
@instance_dir = "#{Dir.tmpdir}/ModOrganizerTest/ModOrganizer/#{instance_name || 'PortableInstance'}"
|
23
|
+
FileUtils.rm_rf(@instance_dir)
|
24
|
+
FileUtils.mkdir_p(@instance_dir)
|
25
|
+
IniFile.new(
|
26
|
+
content: {
|
27
|
+
General: {
|
28
|
+
gamePath: 'C:/path/to/game',
|
29
|
+
selected_profile: 'Default'
|
30
|
+
},
|
31
|
+
Settings: {
|
32
|
+
log_level: '1'
|
33
|
+
}
|
34
|
+
}.merge(ini) do |_section, default_properties, overwrite_properties|
|
35
|
+
default_properties.merge(overwrite_properties)
|
36
|
+
end
|
37
|
+
).write(filename: "#{@instance_dir}/ModOrganizer.ini")
|
38
|
+
end
|
39
|
+
|
40
|
+
# Setup an instance of ModOrganizer setup on an instance directory.
|
41
|
+
# It uses the variable @instance_name to eventually setup a shared installation.
|
42
|
+
# The instance is stored in an instance variable named @mod_organizer.
|
43
|
+
#
|
44
|
+
# Parameters::
|
45
|
+
# * *ini* (Hash< Symbol, Hash<Symbol, String> >): Content of the ini file to overwrite default values [default: {}]
|
46
|
+
def setup_mo(ini: {})
|
47
|
+
setup_instance_dir(ini:, instance_name: @instance_name)
|
48
|
+
mo_logger = StringIO.new
|
49
|
+
ENV['LOCALAPPDATA'] = "#{Dir.tmpdir}/ModOrganizerTest"
|
50
|
+
@mod_organizer = ModOrganizer.new(@instance_dir, instance_name: @instance_name, logger: Logger.new(mo_logger))
|
51
|
+
end
|
52
|
+
|
53
|
+
# Setup a mod in the default mods folder.
|
54
|
+
# Prerequisite: instance_dir has to be setup previously with setup_instance_dir.
|
55
|
+
#
|
56
|
+
# Parameters::
|
57
|
+
# * *ini* (Hash< Symbol, Hash<Symbol, String> >): Content of the meta ini file to overwrite default values [default: {}]
|
58
|
+
# * *mod_name* (String): The mod name [default: 'TestMod']
|
59
|
+
# Result::
|
60
|
+
# * String: The mod directory
|
61
|
+
def setup_mod(ini: {}, mod_name: 'TestMod')
|
62
|
+
mod_dir = "#{instance_dir}/mods/#{mod_name}"
|
63
|
+
FileUtils.mkdir_p(mod_dir)
|
64
|
+
IniFile.new(
|
65
|
+
content: {
|
66
|
+
General: {
|
67
|
+
category: '2,',
|
68
|
+
url: 'https://test-mod.url',
|
69
|
+
installationFile: 'TestMod-v1.7z'
|
70
|
+
},
|
71
|
+
installedFiles: {
|
72
|
+
size: '1',
|
73
|
+
'1\\modid': '1337',
|
74
|
+
'1\\fileid': '666'
|
75
|
+
}
|
76
|
+
}.merge(ini) do |_section, default_properties, overwrite_properties|
|
77
|
+
default_properties.merge(overwrite_properties)
|
78
|
+
end
|
79
|
+
).write(filename: "#{mod_dir}/meta.ini")
|
80
|
+
mod_dir
|
81
|
+
end
|
82
|
+
|
83
|
+
# Setup a download in the default downloads folder.
|
84
|
+
# Prerequisite: instance_dir has to be setup previously with setup_instance_dir.
|
85
|
+
#
|
86
|
+
# Parameters::
|
87
|
+
# * *ini* (Hash< Symbol, Hash<Symbol, String> >): Content of the meta ini file to overwrite default values [default: {}]
|
88
|
+
# * *file_name* (String): The downloaded file name [default: 'TestMod-v1.7z']
|
89
|
+
# Result::
|
90
|
+
# * String: The downloads directory
|
91
|
+
def setup_download(ini: {}, file_name: 'TestMod-v1.7z')
|
92
|
+
downloads_dir = "#{instance_dir}/downloads"
|
93
|
+
FileUtils.mkdir_p(downloads_dir)
|
94
|
+
IniFile.new(
|
95
|
+
content: {
|
96
|
+
General: {
|
97
|
+
name: 'Test Mod file',
|
98
|
+
modID: '1107',
|
99
|
+
fileID: '42'
|
100
|
+
}
|
101
|
+
}.merge(ini) do |_section, default_properties, overwrite_properties|
|
102
|
+
default_properties.merge(overwrite_properties)
|
103
|
+
end
|
104
|
+
).write(filename: "#{downloads_dir}/#{file_name}.meta")
|
105
|
+
File.write("#{downloads_dir}/#{file_name}", "#{file_name} downloaded content")
|
106
|
+
downloads_dir
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
RSpec.configure do |config|
|
114
|
+
config.include ModOrganizerTest::Helpers
|
115
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'time'
|
2
|
+
|
3
|
+
describe ModOrganizer::Download do
|
4
|
+
|
5
|
+
let(:download) { mod_organizer.mod(name: 'TestMod').sources.first.download }
|
6
|
+
let(:download_dir) { "#{instance_dir}/downloads" }
|
7
|
+
let(:downloaded_file) { "#{instance_dir}/downloads/TestMod-v1.7z" }
|
8
|
+
|
9
|
+
before do
|
10
|
+
setup_mo
|
11
|
+
setup_mod
|
12
|
+
setup_download
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'returns the downloaded file path' do
|
16
|
+
expect(download.downloaded_file_path).to eq downloaded_file
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'returns the downloaded file date' do
|
20
|
+
file_date = Time.parse('2023-01-05 10:20:30 UTC')
|
21
|
+
File.utime(File.atime(downloaded_file), file_date, downloaded_file)
|
22
|
+
expect(download.downloaded_date).to eq file_date
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'returns the NexusMods file name' do
|
26
|
+
expect(download.nexus_file_name).to eq 'Test Mod file'
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'caches the returned NexusMods file name' do
|
30
|
+
expect(download.nexus_file_name).to eq 'Test Mod file'
|
31
|
+
setup_download(ini: { General: { name: 'Alternative Test Mod file' } })
|
32
|
+
expect(download.nexus_file_name).to eq 'Test Mod file'
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'returns the NexusMods mod id' do
|
36
|
+
expect(download.nexus_mod_id).to eq 1107
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'caches the returned NexusMods mod id' do
|
40
|
+
expect(download.nexus_mod_id).to eq 1107
|
41
|
+
setup_download(ini: { General: { modID: '666' } })
|
42
|
+
expect(download.nexus_mod_id).to eq 1107
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'returns the NexusMods file id' do
|
46
|
+
expect(download.nexus_file_id).to eq 42
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'caches the returned NexusMods file id' do
|
50
|
+
expect(download.nexus_file_id).to eq 42
|
51
|
+
setup_download(ini: { General: { fileID: '666' } })
|
52
|
+
expect(download.nexus_file_id).to eq 42
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
describe ModOrganizer::Mod do
|
2
|
+
|
3
|
+
let(:mod) { mod_organizer.mod(name: 'TestMod') }
|
4
|
+
let(:mod_dir) { "#{instance_dir}/mods/TestMod" }
|
5
|
+
|
6
|
+
context 'with a simple mod' do
|
7
|
+
|
8
|
+
before do
|
9
|
+
setup_mo
|
10
|
+
setup_mod
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'returns the mod name' do
|
14
|
+
expect(mod.name).to eq 'TestMod'
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'returns the mod enabled flag when enabled' do
|
18
|
+
FileUtils.mkdir_p("#{instance_dir}/profiles/Default")
|
19
|
+
File.write(
|
20
|
+
"#{instance_dir}/profiles/Default/modlist.txt",
|
21
|
+
<<~EO_MOD_LIST
|
22
|
+
+TestMod
|
23
|
+
EO_MOD_LIST
|
24
|
+
)
|
25
|
+
expect(mod.enabled?).to be(true)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'returns the mod enabled flag when disabled' do
|
29
|
+
FileUtils.mkdir_p("#{instance_dir}/profiles/Default")
|
30
|
+
File.write(
|
31
|
+
"#{instance_dir}/profiles/Default/modlist.txt",
|
32
|
+
<<~EO_MOD_LIST
|
33
|
+
-TestMod
|
34
|
+
EO_MOD_LIST
|
35
|
+
)
|
36
|
+
expect(mod.enabled?).to be(false)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'returns the mod categories' do
|
40
|
+
expect(mod.categories).to eq %w[Armour]
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'returns no plugins for mods having no plugins' do
|
44
|
+
expect(mod.plugins).to eq []
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'returns plugins from the mod\'s root directory' do
|
48
|
+
File.write("#{mod_dir}/plugin1.esm", 'Plugin1 esm content')
|
49
|
+
File.write("#{mod_dir}/plugin2.esp", 'Plugin2 esp content')
|
50
|
+
File.write("#{mod_dir}/plugin3.esl", 'Plugin3 esl content')
|
51
|
+
File.write("#{mod_dir}/other_file.txt", 'Other file content')
|
52
|
+
FileUtils.mkdir_p("#{mod_dir}/others")
|
53
|
+
File.write("#{mod_dir}/others/plugin4.esp", 'Plugin4 esp content')
|
54
|
+
expect(mod.plugins.sort).to eq %w[
|
55
|
+
plugin1.esm
|
56
|
+
plugin2.esp
|
57
|
+
plugin3.esl
|
58
|
+
]
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'returns plugins in lower case' do
|
62
|
+
File.write("#{mod_dir}/PlUgIn1.esm", 'Plugin1 esm content')
|
63
|
+
expect(mod.plugins).to eq %w[plugin1.esm]
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'caches plugins being returned' do
|
67
|
+
File.write("#{mod_dir}/plugin1.esm", 'Plugin1 esm content')
|
68
|
+
expect(mod.plugins).to eq %w[plugin1.esm]
|
69
|
+
File.unlink("#{mod_dir}/plugin1.esm")
|
70
|
+
expect(mod.plugins).to eq %w[plugin1.esm]
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'returns the mod source' do
|
74
|
+
sources = mod.sources
|
75
|
+
expect(sources.size).to be 1
|
76
|
+
expect(sources.first.type).to be :nexus_mods
|
77
|
+
expect(sources.first.nexus_mod_id).to be 1337
|
78
|
+
expect(sources.first.nexus_file_id).to be 666
|
79
|
+
expect(sources.first.file_name).to eq 'TestMod-v1.7z'
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'caches the returned the mod source' do
|
83
|
+
expect(mod.sources.first.nexus_mod_id).to be 1337
|
84
|
+
setup_mod(ini: { installedFiles: { '1\\modid': '1107' } })
|
85
|
+
expect(mod.sources.first.nexus_mod_id).to be 1337
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'returns the mod URL' do
|
89
|
+
expect(mod.url).to eq 'https://test-mod.url'
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
context 'with a mod having no ini file' do
|
95
|
+
|
96
|
+
before do
|
97
|
+
setup_mo
|
98
|
+
setup_mod
|
99
|
+
File.unlink("#{mod_dir}/meta.ini")
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'returns the mod name' do
|
103
|
+
expect(mod.name).to eq 'TestMod'
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'returns the mod enabled flag when enabled' do
|
107
|
+
FileUtils.mkdir_p("#{instance_dir}/profiles/Default")
|
108
|
+
File.write(
|
109
|
+
"#{instance_dir}/profiles/Default/modlist.txt",
|
110
|
+
<<~EO_MOD_LIST
|
111
|
+
+TestMod
|
112
|
+
EO_MOD_LIST
|
113
|
+
)
|
114
|
+
expect(mod.enabled?).to be(true)
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'returns the mod enabled flag when disabled' do
|
118
|
+
FileUtils.mkdir_p("#{instance_dir}/profiles/Default")
|
119
|
+
File.write(
|
120
|
+
"#{instance_dir}/profiles/Default/modlist.txt",
|
121
|
+
<<~EO_MOD_LIST
|
122
|
+
-TestMod
|
123
|
+
EO_MOD_LIST
|
124
|
+
)
|
125
|
+
expect(mod.enabled?).to be(false)
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'returns no mod categories' do
|
129
|
+
expect(mod.categories).to eq []
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'returns an unknown mod source' do
|
133
|
+
sources = mod.sources
|
134
|
+
expect(sources.size).to be 1
|
135
|
+
expect(sources.first.type).to be :unknown
|
136
|
+
expect(sources.first.nexus_mod_id).to be_nil
|
137
|
+
expect(sources.first.nexus_file_id).to be_nil
|
138
|
+
expect(sources.first.file_name).to be_nil
|
139
|
+
end
|
140
|
+
|
141
|
+
it 'returns no mod URL' do
|
142
|
+
expect(mod.url).to be_nil
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
context 'with a mod having several sources' do
|
148
|
+
|
149
|
+
before do
|
150
|
+
setup_mo
|
151
|
+
setup_mod(
|
152
|
+
ini: {
|
153
|
+
installedFiles: {
|
154
|
+
size: '3',
|
155
|
+
'1\\modid': '100',
|
156
|
+
'1\\fileid': '200',
|
157
|
+
'2\\modid': '101',
|
158
|
+
'2\\fileid': '201',
|
159
|
+
'3\\modid': '102',
|
160
|
+
'3\\fileid': '202'
|
161
|
+
}
|
162
|
+
}
|
163
|
+
)
|
164
|
+
end
|
165
|
+
|
166
|
+
it 'returns several sources' do
|
167
|
+
expect(mod.sources.map.with_index { |source, idx| [idx, source.nexus_mod_id, source.nexus_file_id] }.sort).to eq [
|
168
|
+
[0, 100, 200],
|
169
|
+
[1, 101, 201],
|
170
|
+
[2, 102, 202]
|
171
|
+
]
|
172
|
+
end
|
173
|
+
|
174
|
+
it 'associates the downloaded file name to the last source only' do
|
175
|
+
expect(mod.sources.map.with_index { |source, idx| [idx, source.file_name] }.sort).to eq [
|
176
|
+
[0, nil],
|
177
|
+
[1, nil],
|
178
|
+
[2, 'TestMod-v1.7z']
|
179
|
+
]
|
180
|
+
end
|
181
|
+
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
describe ModOrganizer::Source do
|
2
|
+
|
3
|
+
let(:source) { mod_organizer.mod(name: 'TestMod').sources.first }
|
4
|
+
|
5
|
+
context 'with a source from nexus_mods' do
|
6
|
+
|
7
|
+
before do
|
8
|
+
setup_mo
|
9
|
+
setup_mod
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'returns the NexusMods mod id' do
|
13
|
+
expect(source.nexus_mod_id).to be 1337
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'returns the NexusMods file id' do
|
17
|
+
expect(source.nexus_file_id).to be 666
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'returns the source file name' do
|
21
|
+
expect(source.file_name).to eq 'TestMod-v1.7z'
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'returns the source type' do
|
25
|
+
expect(source.type).to be :nexus_mods
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'returns the download info of the source' do
|
29
|
+
FileUtils.mkdir_p("#{instance_dir}/downloads")
|
30
|
+
downloaded_file = "#{instance_dir}/downloads/TestMod-v1.7z"
|
31
|
+
File.write(downloaded_file, 'TestMod v1 downloaded content')
|
32
|
+
expect(source.download.downloaded_file_path).to eq downloaded_file
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'returns no download info of the source if the downloaded file is missing' do
|
36
|
+
expect(source.download).to be_nil
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'with an unknown source' do
|
42
|
+
|
43
|
+
before do
|
44
|
+
setup_mo
|
45
|
+
setup_mod
|
46
|
+
File.unlink("#{instance_dir}/mods/TestMod/meta.ini")
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'returns no NexusMods mod id' do
|
50
|
+
expect(source.nexus_mod_id).to be_nil
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'returns no NexusMods file id' do
|
54
|
+
expect(source.nexus_file_id).to be_nil
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'returns no source file name' do
|
58
|
+
expect(source.file_name).to be_nil
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'returns the source type' do
|
62
|
+
expect(source.type).to be :unknown
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'returns no download info of the source' do
|
66
|
+
expect(source.download).to be_nil
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|