factorix 0.5.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 +20 -0
- data/LICENSE.txt +21 -0
- data/README.md +105 -0
- data/completion/_factorix.bash +202 -0
- data/completion/_factorix.fish +197 -0
- data/completion/_factorix.zsh +376 -0
- data/doc/factorix.1 +377 -0
- data/exe/factorix +20 -0
- data/lib/factorix/api/category.rb +69 -0
- data/lib/factorix/api/image.rb +35 -0
- data/lib/factorix/api/license.rb +71 -0
- data/lib/factorix/api/mod_download_api.rb +66 -0
- data/lib/factorix/api/mod_info.rb +166 -0
- data/lib/factorix/api/mod_management_api.rb +237 -0
- data/lib/factorix/api/mod_portal_api.rb +204 -0
- data/lib/factorix/api/release.rb +49 -0
- data/lib/factorix/api/tag.rb +95 -0
- data/lib/factorix/api.rb +7 -0
- data/lib/factorix/api_credential.rb +54 -0
- data/lib/factorix/application.rb +218 -0
- data/lib/factorix/cache/file_system.rb +307 -0
- data/lib/factorix/cli/commands/backup_support.rb +46 -0
- data/lib/factorix/cli/commands/base.rb +90 -0
- data/lib/factorix/cli/commands/cache/evict.rb +180 -0
- data/lib/factorix/cli/commands/cache/stat.rb +201 -0
- data/lib/factorix/cli/commands/command_wrapper.rb +71 -0
- data/lib/factorix/cli/commands/completion.rb +83 -0
- data/lib/factorix/cli/commands/confirmable.rb +53 -0
- data/lib/factorix/cli/commands/download_support.rb +123 -0
- data/lib/factorix/cli/commands/launch.rb +79 -0
- data/lib/factorix/cli/commands/man.rb +29 -0
- data/lib/factorix/cli/commands/mod/check.rb +99 -0
- data/lib/factorix/cli/commands/mod/disable.rb +188 -0
- data/lib/factorix/cli/commands/mod/download.rb +291 -0
- data/lib/factorix/cli/commands/mod/edit.rb +114 -0
- data/lib/factorix/cli/commands/mod/enable.rb +216 -0
- data/lib/factorix/cli/commands/mod/image/add.rb +47 -0
- data/lib/factorix/cli/commands/mod/image/edit.rb +41 -0
- data/lib/factorix/cli/commands/mod/image/list.rb +74 -0
- data/lib/factorix/cli/commands/mod/install.rb +443 -0
- data/lib/factorix/cli/commands/mod/list.rb +372 -0
- data/lib/factorix/cli/commands/mod/search.rb +134 -0
- data/lib/factorix/cli/commands/mod/settings/dump.rb +88 -0
- data/lib/factorix/cli/commands/mod/settings/restore.rb +101 -0
- data/lib/factorix/cli/commands/mod/show.rb +202 -0
- data/lib/factorix/cli/commands/mod/sync.rb +299 -0
- data/lib/factorix/cli/commands/mod/uninstall.rb +325 -0
- data/lib/factorix/cli/commands/mod/update.rb +222 -0
- data/lib/factorix/cli/commands/mod/upload.rb +90 -0
- data/lib/factorix/cli/commands/path.rb +79 -0
- data/lib/factorix/cli/commands/requires_game_stopped.rb +32 -0
- data/lib/factorix/cli/commands/version.rb +25 -0
- data/lib/factorix/cli.rb +42 -0
- data/lib/factorix/dependency/edge.rb +89 -0
- data/lib/factorix/dependency/entry.rb +124 -0
- data/lib/factorix/dependency/graph/builder.rb +108 -0
- data/lib/factorix/dependency/graph.rb +210 -0
- data/lib/factorix/dependency/list.rb +244 -0
- data/lib/factorix/dependency/mod_version_requirement.rb +73 -0
- data/lib/factorix/dependency/node.rb +60 -0
- data/lib/factorix/dependency/parser.rb +148 -0
- data/lib/factorix/dependency/validation_result.rb +138 -0
- data/lib/factorix/dependency/validator.rb +190 -0
- data/lib/factorix/errors.rb +112 -0
- data/lib/factorix/formatting.rb +56 -0
- data/lib/factorix/game_version.rb +98 -0
- data/lib/factorix/http/cache_decorator.rb +106 -0
- data/lib/factorix/http/cached_response.rb +37 -0
- data/lib/factorix/http/client.rb +187 -0
- data/lib/factorix/http/response.rb +31 -0
- data/lib/factorix/http/retry_decorator.rb +59 -0
- data/lib/factorix/http/retry_strategy.rb +80 -0
- data/lib/factorix/info_json.rb +90 -0
- data/lib/factorix/installed_mod.rb +239 -0
- data/lib/factorix/mod.rb +55 -0
- data/lib/factorix/mod_list.rb +174 -0
- data/lib/factorix/mod_settings.rb +278 -0
- data/lib/factorix/mod_state.rb +34 -0
- data/lib/factorix/mod_version.rb +99 -0
- data/lib/factorix/portal.rb +185 -0
- data/lib/factorix/progress/download_handler.rb +46 -0
- data/lib/factorix/progress/multi_presenter.rb +45 -0
- data/lib/factorix/progress/presenter.rb +67 -0
- data/lib/factorix/progress/presenter_adapter.rb +46 -0
- data/lib/factorix/progress/scan_handler.rb +33 -0
- data/lib/factorix/progress/upload_handler.rb +33 -0
- data/lib/factorix/runtime/base.rb +233 -0
- data/lib/factorix/runtime/linux.rb +32 -0
- data/lib/factorix/runtime/mac_os.rb +53 -0
- data/lib/factorix/runtime/user_configurable.rb +69 -0
- data/lib/factorix/runtime/windows.rb +85 -0
- data/lib/factorix/runtime/wsl.rb +118 -0
- data/lib/factorix/runtime.rb +32 -0
- data/lib/factorix/save_file.rb +178 -0
- data/lib/factorix/ser_des/deserializer.rb +198 -0
- data/lib/factorix/ser_des/serializer.rb +231 -0
- data/lib/factorix/ser_des/signed_integer.rb +63 -0
- data/lib/factorix/ser_des/unsigned_integer.rb +65 -0
- data/lib/factorix/service_credential.rb +127 -0
- data/lib/factorix/transfer/downloader.rb +162 -0
- data/lib/factorix/transfer/uploader.rb +232 -0
- data/lib/factorix/version.rb +6 -0
- data/lib/factorix.rb +38 -0
- data/sig/dry/auto_inject.rbs +15 -0
- data/sig/dry/cli.rbs +19 -0
- data/sig/dry/configurable.rbs +13 -0
- data/sig/dry/core/container.rbs +17 -0
- data/sig/dry/events/publisher.rbs +22 -0
- data/sig/dry/logger.rbs +16 -0
- data/sig/factorix/api/category.rbs +15 -0
- data/sig/factorix/api/image.rbs +15 -0
- data/sig/factorix/api/license.rbs +20 -0
- data/sig/factorix/api/mod_download_api.rbs +18 -0
- data/sig/factorix/api/mod_info.rbs +67 -0
- data/sig/factorix/api/mod_management_api.rbs +25 -0
- data/sig/factorix/api/mod_portal_api.rbs +31 -0
- data/sig/factorix/api/release.rbs +27 -0
- data/sig/factorix/api/tag.rbs +15 -0
- data/sig/factorix/api.rbs +8 -0
- data/sig/factorix/api_credential.rbs +17 -0
- data/sig/factorix/application.rbs +86 -0
- data/sig/factorix/cache/file_system.rbs +35 -0
- data/sig/factorix/cli/commands/base.rbs +13 -0
- data/sig/factorix/cli/commands/cache/evict.rbs +17 -0
- data/sig/factorix/cli/commands/cache/stat.rbs +17 -0
- data/sig/factorix/cli/commands/command_wrapper.rbs +13 -0
- data/sig/factorix/cli/commands/completion/zsh.rbs +15 -0
- data/sig/factorix/cli/commands/confirmable.rbs +12 -0
- data/sig/factorix/cli/commands/download_support.rbs +12 -0
- data/sig/factorix/cli/commands/launch.rbs +15 -0
- data/sig/factorix/cli/commands/mod/check.rbs +18 -0
- data/sig/factorix/cli/commands/mod/disable.rbs +20 -0
- data/sig/factorix/cli/commands/mod/download.rbs +18 -0
- data/sig/factorix/cli/commands/mod/edit.rbs +30 -0
- data/sig/factorix/cli/commands/mod/enable.rbs +20 -0
- data/sig/factorix/cli/commands/mod/image/add.rbs +19 -0
- data/sig/factorix/cli/commands/mod/image/edit.rbs +19 -0
- data/sig/factorix/cli/commands/mod/image/list.rbs +19 -0
- data/sig/factorix/cli/commands/mod/install.rbs +19 -0
- data/sig/factorix/cli/commands/mod/list.rbs +30 -0
- data/sig/factorix/cli/commands/mod/search.rbs +18 -0
- data/sig/factorix/cli/commands/mod/settings/dump.rbs +17 -0
- data/sig/factorix/cli/commands/mod/settings/restore.rbs +17 -0
- data/sig/factorix/cli/commands/mod/sync.rbs +19 -0
- data/sig/factorix/cli/commands/mod/uninstall.rbs +20 -0
- data/sig/factorix/cli/commands/mod/update.rbs +19 -0
- data/sig/factorix/cli/commands/mod/upload.rbs +24 -0
- data/sig/factorix/cli/commands/path.rbs +18 -0
- data/sig/factorix/cli/commands/requires_game_stopped.rbs +13 -0
- data/sig/factorix/cli/commands/version.rbs +13 -0
- data/sig/factorix/cli.rbs +11 -0
- data/sig/factorix/dependency/edge.rbs +32 -0
- data/sig/factorix/dependency/entry.rbs +30 -0
- data/sig/factorix/dependency/graph/builder.rbs +17 -0
- data/sig/factorix/dependency/graph.rbs +39 -0
- data/sig/factorix/dependency/list.rbs +69 -0
- data/sig/factorix/dependency/mod_version_requirement.rbs +18 -0
- data/sig/factorix/dependency/node.rbs +24 -0
- data/sig/factorix/dependency/parser.rbs +11 -0
- data/sig/factorix/dependency/validation_result.rbs +56 -0
- data/sig/factorix/dependency/validator.rbs +13 -0
- data/sig/factorix/errors.rbs +132 -0
- data/sig/factorix/formatting.rbs +8 -0
- data/sig/factorix/game_version.rbs +24 -0
- data/sig/factorix/http/cache_decorator.rbs +64 -0
- data/sig/factorix/http/client.rbs +55 -0
- data/sig/factorix/http/response.rbs +28 -0
- data/sig/factorix/http/retry_decorator.rbs +44 -0
- data/sig/factorix/http/retry_strategy.rbs +42 -0
- data/sig/factorix/info_json.rbs +19 -0
- data/sig/factorix/installed_mod.rbs +34 -0
- data/sig/factorix/mod.rbs +20 -0
- data/sig/factorix/mod_list.rbs +44 -0
- data/sig/factorix/mod_settings.rbs +47 -0
- data/sig/factorix/mod_state.rbs +18 -0
- data/sig/factorix/mod_version.rbs +23 -0
- data/sig/factorix/portal.rbs +37 -0
- data/sig/factorix/progress/download_handler.rbs +19 -0
- data/sig/factorix/progress/multi_presenter.rbs +15 -0
- data/sig/factorix/progress/presenter.rbs +17 -0
- data/sig/factorix/progress/presenter_adapter.rbs +17 -0
- data/sig/factorix/progress/scan_handler.rbs +16 -0
- data/sig/factorix/progress/upload_handler.rbs +17 -0
- data/sig/factorix/runtime/base.rbs +45 -0
- data/sig/factorix/runtime/linux.rbs +15 -0
- data/sig/factorix/runtime/mac_os.rbs +15 -0
- data/sig/factorix/runtime/user_configurable.rbs +13 -0
- data/sig/factorix/runtime/windows.rbs +23 -0
- data/sig/factorix/runtime/wsl.rbs +19 -0
- data/sig/factorix/runtime.rbs +9 -0
- data/sig/factorix/save_file.rbs +40 -0
- data/sig/factorix/ser_des/deserializer.rbs +49 -0
- data/sig/factorix/ser_des/serializer.rbs +45 -0
- data/sig/factorix/ser_des/signed_integer.rbs +37 -0
- data/sig/factorix/ser_des/unsigned_integer.rbs +37 -0
- data/sig/factorix/service_credential.rbs +19 -0
- data/sig/factorix/transfer/downloader.rbs +15 -0
- data/sig/factorix/transfer/uploader.rbs +21 -0
- data/sig/factorix.rbs +9 -0
- data/sig/tty/progressbar.rbs +18 -0
- metadata +431 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Factorix
|
|
6
|
+
class CLI
|
|
7
|
+
module Commands
|
|
8
|
+
# Display Factorio and Factorix paths
|
|
9
|
+
#
|
|
10
|
+
# This command outputs all paths managed by the runtime environment.
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
# $ factorix path
|
|
14
|
+
# executable_path /path/to/factorio
|
|
15
|
+
# user_dir /path/to/user
|
|
16
|
+
# ...
|
|
17
|
+
class Path < Base
|
|
18
|
+
# Mapping from path type keys to runtime method names
|
|
19
|
+
PATH_TYPES = {
|
|
20
|
+
"executable_path" => :executable_path,
|
|
21
|
+
"data_dir" => :data_dir,
|
|
22
|
+
"user_dir" => :user_dir,
|
|
23
|
+
"mod_dir" => :mod_dir,
|
|
24
|
+
"save_dir" => :save_dir,
|
|
25
|
+
"script_output_dir" => :script_output_dir,
|
|
26
|
+
"mod_list_path" => :mod_list_path,
|
|
27
|
+
"mod_settings_path" => :mod_settings_path,
|
|
28
|
+
"player_data_path" => :player_data_path,
|
|
29
|
+
"lock_path" => :lock_path,
|
|
30
|
+
"current_log_path" => :current_log_path,
|
|
31
|
+
"previous_log_path" => :previous_log_path,
|
|
32
|
+
"factorix_cache_dir" => :factorix_cache_dir,
|
|
33
|
+
"factorix_config_path" => :factorix_config_path,
|
|
34
|
+
"factorix_log_path" => :factorix_log_path
|
|
35
|
+
}.freeze
|
|
36
|
+
private_constant :PATH_TYPES
|
|
37
|
+
|
|
38
|
+
# @!parse
|
|
39
|
+
# # @return [Runtime::Base]
|
|
40
|
+
# attr_reader :runtime
|
|
41
|
+
# # @return [Dry::Logger::Dispatcher]
|
|
42
|
+
# attr_reader :logger
|
|
43
|
+
include Import[:runtime, :logger]
|
|
44
|
+
|
|
45
|
+
desc "Display Factorio and Factorix paths"
|
|
46
|
+
|
|
47
|
+
example [
|
|
48
|
+
" # Display paths in table format",
|
|
49
|
+
"--json # Display paths in JSON format"
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
option :json, type: :flag, default: false, desc: "Output in JSON format"
|
|
53
|
+
|
|
54
|
+
# Execute the path command
|
|
55
|
+
#
|
|
56
|
+
# @param json [Boolean] output in JSON format
|
|
57
|
+
# @return [void]
|
|
58
|
+
def call(json:, **)
|
|
59
|
+
logger.debug("Displaying all paths")
|
|
60
|
+
|
|
61
|
+
result = PATH_TYPES.transform_values {|method_name| runtime.public_send(method_name).to_s }
|
|
62
|
+
|
|
63
|
+
if json
|
|
64
|
+
puts JSON.pretty_generate(result)
|
|
65
|
+
else
|
|
66
|
+
output_table(result)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private def output_table(result)
|
|
71
|
+
key_width = result.keys.map(&:length).max
|
|
72
|
+
result.each do |key, value|
|
|
73
|
+
puts "%-#{key_width}s %s" % [key, value]
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Factorix
|
|
4
|
+
class CLI
|
|
5
|
+
module Commands
|
|
6
|
+
# Mixin for commands that require the game to be stopped
|
|
7
|
+
#
|
|
8
|
+
# This module provides automatic validation that the game is not running
|
|
9
|
+
# before executing commands that modify MOD installation state or
|
|
10
|
+
# mod-list.json/mod-settings.dat files.
|
|
11
|
+
#
|
|
12
|
+
# Prepend this module in commands that should not run while the game is active
|
|
13
|
+
# (e.g., install, uninstall, enable, disable)
|
|
14
|
+
module RequiresGameStopped
|
|
15
|
+
# Wrapper for command call that checks game state
|
|
16
|
+
# @param options [Hash] command options passed to the original call method
|
|
17
|
+
# @return [void]
|
|
18
|
+
def call(**options)
|
|
19
|
+
check_game_stopped
|
|
20
|
+
super
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private def check_game_stopped
|
|
24
|
+
return unless runtime.running?
|
|
25
|
+
|
|
26
|
+
logger.error("Operation blocked: game is running")
|
|
27
|
+
raise GameRunningError, "Cannot perform this operation while Factorio is running. Please stop the game and try again."
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Factorix
|
|
4
|
+
class CLI
|
|
5
|
+
module Commands
|
|
6
|
+
# Display Factorix version
|
|
7
|
+
#
|
|
8
|
+
# This command outputs the current version of the Factorix gem.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# $ factorix version
|
|
12
|
+
# 0.1.0
|
|
13
|
+
class Version < Base
|
|
14
|
+
desc "Display Factorix version"
|
|
15
|
+
|
|
16
|
+
# Execute the version command
|
|
17
|
+
#
|
|
18
|
+
# Outputs the current version of the Factorix gem to stdout.
|
|
19
|
+
#
|
|
20
|
+
# @return [void]
|
|
21
|
+
def call(**) = puts VERSION
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
data/lib/factorix/cli.rb
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry/cli"
|
|
4
|
+
|
|
5
|
+
module Factorix
|
|
6
|
+
# Command-line interface for Factorix
|
|
7
|
+
#
|
|
8
|
+
# This class serves as the registry for all CLI commands using dry-cli.
|
|
9
|
+
# Commands are registered with their names and mapped to command classes.
|
|
10
|
+
#
|
|
11
|
+
# @example Running the CLI
|
|
12
|
+
# Dry::CLI.new(Factorix::CLI).call
|
|
13
|
+
class CLI
|
|
14
|
+
extend Dry::CLI::Registry
|
|
15
|
+
|
|
16
|
+
register "version", Commands::Version
|
|
17
|
+
register "man", Commands::Man
|
|
18
|
+
register "launch", Commands::Launch
|
|
19
|
+
register "path", Commands::Path
|
|
20
|
+
register "completion", Commands::Completion
|
|
21
|
+
register "mod check", Commands::MOD::Check
|
|
22
|
+
register "mod list", Commands::MOD::List
|
|
23
|
+
register "mod show", Commands::MOD::Show
|
|
24
|
+
register "mod enable", Commands::MOD::Enable
|
|
25
|
+
register "mod disable", Commands::MOD::Disable
|
|
26
|
+
register "mod install", Commands::MOD::Install
|
|
27
|
+
register "mod uninstall", Commands::MOD::Uninstall
|
|
28
|
+
register "mod update", Commands::MOD::Update
|
|
29
|
+
register "mod download", Commands::MOD::Download
|
|
30
|
+
register "mod upload", Commands::MOD::Upload
|
|
31
|
+
register "mod edit", Commands::MOD::Edit
|
|
32
|
+
register "mod search", Commands::MOD::Search
|
|
33
|
+
register "mod sync", Commands::MOD::Sync
|
|
34
|
+
register "mod image list", Commands::MOD::Image::List
|
|
35
|
+
register "mod image add", Commands::MOD::Image::Add
|
|
36
|
+
register "mod image edit", Commands::MOD::Image::Edit
|
|
37
|
+
register "mod settings dump", Commands::MOD::Settings::Dump
|
|
38
|
+
register "mod settings restore", Commands::MOD::Settings::Restore
|
|
39
|
+
register "cache stat", Commands::Cache::Stat
|
|
40
|
+
register "cache evict", Commands::Cache::Evict
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Factorix
|
|
4
|
+
module Dependency
|
|
5
|
+
Edge = Data.define(:from_mod, :to_mod, :type, :version_requirement)
|
|
6
|
+
|
|
7
|
+
# Represents a dependency edge in the dependency graph
|
|
8
|
+
#
|
|
9
|
+
# Each edge represents a dependency relationship from one MOD (dependent)
|
|
10
|
+
# to another MOD (dependency). The edge type indicates the nature of the
|
|
11
|
+
# relationship (required, optional, incompatible, etc.).
|
|
12
|
+
class Edge
|
|
13
|
+
# @!attribute [r] from_mod
|
|
14
|
+
# @return [Factorix::MOD] MOD object (the dependent)
|
|
15
|
+
# @!attribute [r] to_mod
|
|
16
|
+
# @return [Factorix::MOD] MOD object (the dependency)
|
|
17
|
+
# @!attribute [r] type
|
|
18
|
+
# @return [Symbol] dependency type
|
|
19
|
+
# @!attribute [r] version_requirement
|
|
20
|
+
# @return [MODVersionRequirement, nil] version requirement
|
|
21
|
+
|
|
22
|
+
# Dependency types (from Factorix::Dependency::Entry)
|
|
23
|
+
REQUIRED = Entry::REQUIRED
|
|
24
|
+
public_constant :REQUIRED
|
|
25
|
+
OPTIONAL = Entry::OPTIONAL
|
|
26
|
+
public_constant :OPTIONAL
|
|
27
|
+
HIDDEN_OPTIONAL = Entry::HIDDEN_OPTIONAL
|
|
28
|
+
public_constant :HIDDEN_OPTIONAL
|
|
29
|
+
INCOMPATIBLE = Entry::INCOMPATIBLE
|
|
30
|
+
public_constant :INCOMPATIBLE
|
|
31
|
+
LOAD_NEUTRAL = Entry::LOAD_NEUTRAL
|
|
32
|
+
public_constant :LOAD_NEUTRAL
|
|
33
|
+
|
|
34
|
+
# @param from_mod [Factorix::MOD] The dependent MOD
|
|
35
|
+
# @param to_mod [Factorix::MOD] The dependency MOD
|
|
36
|
+
# @param type [Symbol] The dependency type (:required, :optional, :hidden, :incompatible, :load_neutral)
|
|
37
|
+
# @param version_requirement [MODVersionRequirement, nil] Version requirement (optional)
|
|
38
|
+
def initialize(from_mod:, to_mod:, type:, version_requirement: nil) = super
|
|
39
|
+
|
|
40
|
+
# Check if this is a required dependency
|
|
41
|
+
#
|
|
42
|
+
# @return [Boolean]
|
|
43
|
+
def required? = type == REQUIRED
|
|
44
|
+
|
|
45
|
+
# Check if this is an optional dependency
|
|
46
|
+
#
|
|
47
|
+
# @return [Boolean]
|
|
48
|
+
def optional? = type == OPTIONAL || type == HIDDEN_OPTIONAL
|
|
49
|
+
|
|
50
|
+
# Check if this is a hidden optional dependency
|
|
51
|
+
#
|
|
52
|
+
# @return [Boolean]
|
|
53
|
+
def hidden_optional? = type == HIDDEN_OPTIONAL
|
|
54
|
+
|
|
55
|
+
# Check if this is an incompatibility relationship
|
|
56
|
+
#
|
|
57
|
+
# @return [Boolean]
|
|
58
|
+
def incompatible? = type == INCOMPATIBLE
|
|
59
|
+
|
|
60
|
+
# Check if this is a load-neutral dependency
|
|
61
|
+
#
|
|
62
|
+
# @return [Boolean]
|
|
63
|
+
def load_neutral? = type == LOAD_NEUTRAL
|
|
64
|
+
|
|
65
|
+
# Check if the given version satisfies this edge's version requirement
|
|
66
|
+
#
|
|
67
|
+
# @param version [Factorix::MODVersion] The version to check
|
|
68
|
+
# @return [Boolean] true if satisfied or no requirement exists
|
|
69
|
+
def satisfied_by?(version)
|
|
70
|
+
return true unless version_requirement
|
|
71
|
+
|
|
72
|
+
version_requirement.satisfied_by?(version)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# String representation of the edge
|
|
76
|
+
#
|
|
77
|
+
# @return [String]
|
|
78
|
+
def to_s
|
|
79
|
+
requirement_str = version_requirement ? " #{version_requirement}" : ""
|
|
80
|
+
"#{from_mod} --[#{type}#{requirement_str}]--> #{to_mod}"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Detailed inspection string
|
|
84
|
+
#
|
|
85
|
+
# @return [String]
|
|
86
|
+
def inspect = "#<#{self.class.name} #{self}>"
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Factorix
|
|
4
|
+
module Dependency
|
|
5
|
+
# Define Entry as an immutable data class
|
|
6
|
+
Entry = Data.define(:mod, :type, :version_requirement)
|
|
7
|
+
|
|
8
|
+
# Represents a single MOD dependency
|
|
9
|
+
#
|
|
10
|
+
# This class encapsulates a MOD dependency with its type (required, optional, etc.)
|
|
11
|
+
# and optional version requirement.
|
|
12
|
+
#
|
|
13
|
+
# @example Creating dependencies
|
|
14
|
+
# # Required dependency on base MOD
|
|
15
|
+
# base_mod = MOD[name: "base"]
|
|
16
|
+
# dep1 = Dependency::Entry[mod: base_mod, type: :required, version_requirement: nil]
|
|
17
|
+
#
|
|
18
|
+
# # Optional dependency with version requirement
|
|
19
|
+
# some_mod = MOD[name: "some-mod"]
|
|
20
|
+
# requirement = MODVersionRequirement[operator: ">=", version: MODVersion.from_string("1.2.0")]
|
|
21
|
+
# dep2 = Dependency::Entry[mod: some_mod, type: :optional, version_requirement: requirement]
|
|
22
|
+
#
|
|
23
|
+
# # Incompatible MOD
|
|
24
|
+
# bad_mod = MOD[name: "bad-mod"]
|
|
25
|
+
# dep3 = Dependency::Entry[mod: bad_mod, type: :incompatible, version_requirement: nil]
|
|
26
|
+
class Entry
|
|
27
|
+
# @!attribute [r] mod
|
|
28
|
+
# @return [MOD] The dependent MOD
|
|
29
|
+
# @!attribute [r] type
|
|
30
|
+
# @return [Symbol] Type of dependency (:required, :optional, :hidden, :incompatible, :load_neutral)
|
|
31
|
+
# @!attribute [r] version_requirement
|
|
32
|
+
# @return [MODVersionRequirement, nil] Version requirement (nil if no requirement)
|
|
33
|
+
|
|
34
|
+
# Dependency type constants
|
|
35
|
+
REQUIRED = :required
|
|
36
|
+
public_constant :REQUIRED
|
|
37
|
+
OPTIONAL = :optional
|
|
38
|
+
public_constant :OPTIONAL
|
|
39
|
+
HIDDEN_OPTIONAL = :hidden
|
|
40
|
+
public_constant :HIDDEN_OPTIONAL
|
|
41
|
+
INCOMPATIBLE = :incompatible
|
|
42
|
+
public_constant :INCOMPATIBLE
|
|
43
|
+
LOAD_NEUTRAL = :load_neutral
|
|
44
|
+
public_constant :LOAD_NEUTRAL
|
|
45
|
+
|
|
46
|
+
VALID_TYPES = [REQUIRED, OPTIONAL, HIDDEN_OPTIONAL, INCOMPATIBLE, LOAD_NEUTRAL].freeze
|
|
47
|
+
private_constant :VALID_TYPES
|
|
48
|
+
|
|
49
|
+
# Create a new Entry
|
|
50
|
+
#
|
|
51
|
+
# @param mod [MOD] The dependent MOD
|
|
52
|
+
# @param type [Symbol] Type of dependency (:required, :optional, :hidden, :incompatible, :load_neutral)
|
|
53
|
+
# @param version_requirement [MODVersionRequirement, nil] Version requirement (nil if no requirement)
|
|
54
|
+
# @return [Entry]
|
|
55
|
+
# @raise [ArgumentError] if mod is not a MOD instance
|
|
56
|
+
# @raise [ArgumentError] if type is not valid
|
|
57
|
+
# @raise [ArgumentError] if version_requirement is not nil or MODVersionRequirement
|
|
58
|
+
def initialize(mod:, type:, version_requirement: nil)
|
|
59
|
+
unless mod.is_a?(MOD)
|
|
60
|
+
raise ArgumentError, "mod must be a MOD instance, got #{mod.class}"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
unless VALID_TYPES.include?(type)
|
|
64
|
+
raise ArgumentError, "Invalid dependency type: #{type}. Must be one of: #{VALID_TYPES.join(", ")}"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
if version_requirement && !version_requirement.is_a?(MODVersionRequirement)
|
|
68
|
+
raise ArgumentError, "version_requirement must be a MODVersionRequirement or nil, got #{version_requirement.class}"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
super
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Check if this is a required dependency
|
|
75
|
+
#
|
|
76
|
+
# @return [Boolean] true if dependency is required
|
|
77
|
+
def required? = type == REQUIRED
|
|
78
|
+
|
|
79
|
+
# Check if this is an optional dependency (including hidden optional)
|
|
80
|
+
#
|
|
81
|
+
# @return [Boolean] true if dependency is optional or hidden optional
|
|
82
|
+
def optional? = type == OPTIONAL || type == HIDDEN_OPTIONAL
|
|
83
|
+
|
|
84
|
+
# Check if this is an incompatible (conflicting) dependency
|
|
85
|
+
#
|
|
86
|
+
# @return [Boolean] true if dependency is incompatible
|
|
87
|
+
def incompatible? = type == INCOMPATIBLE
|
|
88
|
+
|
|
89
|
+
# Check if this dependency does not affect load order
|
|
90
|
+
#
|
|
91
|
+
# @return [Boolean] true if dependency is load-neutral
|
|
92
|
+
def load_neutral? = type == LOAD_NEUTRAL
|
|
93
|
+
|
|
94
|
+
# Check if a given version satisfies this dependency's version requirement
|
|
95
|
+
#
|
|
96
|
+
# @param version [MODVersion] Version to check
|
|
97
|
+
# @return [Boolean] true if version requirement is satisfied, or true if no requirement exists
|
|
98
|
+
def satisfied_by?(version)
|
|
99
|
+
return true unless version_requirement
|
|
100
|
+
|
|
101
|
+
version_requirement.satisfied_by?(version)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Return string representation of the dependency
|
|
105
|
+
#
|
|
106
|
+
# @return [String] String representation (e.g., "? some-mod >= 1.2.0")
|
|
107
|
+
def to_s
|
|
108
|
+
result = case type
|
|
109
|
+
when REQUIRED then ""
|
|
110
|
+
when OPTIONAL then "? "
|
|
111
|
+
when HIDDEN_OPTIONAL then "(?) "
|
|
112
|
+
when INCOMPATIBLE then "! "
|
|
113
|
+
when LOAD_NEUTRAL then "~ "
|
|
114
|
+
else
|
|
115
|
+
raise ArgumentError, "Unexpected dependency type: #{type}"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
result += mod.name
|
|
119
|
+
result += " #{version_requirement}" if version_requirement
|
|
120
|
+
result
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Factorix
|
|
4
|
+
module Dependency
|
|
5
|
+
class Graph
|
|
6
|
+
# Builds a dependency graph from installed MODs and MOD list
|
|
7
|
+
#
|
|
8
|
+
# The Builder constructs a Graph by:
|
|
9
|
+
# 1. Creating nodes for all installed MODs
|
|
10
|
+
# 2. Setting enabled state from mod-list.json
|
|
11
|
+
# 3. Creating edges from dependency information in info.json
|
|
12
|
+
class Builder
|
|
13
|
+
# Build a dependency graph from current state
|
|
14
|
+
#
|
|
15
|
+
# @param installed_mods [Array<Factorix::InstalledMOD>] Installed MODs from MOD directory
|
|
16
|
+
# @param mod_list [Factorix::MODList] MOD list from mod-list.json
|
|
17
|
+
# @return [Factorix::Dependency::Graph] The constructed graph
|
|
18
|
+
def self.build(installed_mods:, mod_list:) = new(installed_mods:, mod_list:).build
|
|
19
|
+
|
|
20
|
+
# @param installed_mods [Array<Factorix::InstalledMOD>] Installed MODs
|
|
21
|
+
# @param mod_list [Factorix::MODList] MOD list
|
|
22
|
+
def initialize(installed_mods:, mod_list:)
|
|
23
|
+
@installed_mods = installed_mods
|
|
24
|
+
@mod_list = mod_list
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Build the graph
|
|
28
|
+
#
|
|
29
|
+
# @return [Factorix::Dependency::Graph] The constructed graph
|
|
30
|
+
def build
|
|
31
|
+
graph = Graph.new
|
|
32
|
+
|
|
33
|
+
unique_mods = @installed_mods.map(&:mod)
|
|
34
|
+
unique_mods.uniq!
|
|
35
|
+
|
|
36
|
+
unique_mods.each do |mod|
|
|
37
|
+
add_node_for_mod(graph, mod)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Only active versions contribute edges
|
|
41
|
+
active_versions = graph.nodes.to_h {|node| [node.mod, node.version] }
|
|
42
|
+
|
|
43
|
+
@installed_mods.each do |installed_mod|
|
|
44
|
+
next unless active_versions[installed_mod.mod] == installed_mod.version
|
|
45
|
+
|
|
46
|
+
add_edges_for_dependencies(graph, installed_mod)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
graph
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private def add_node_for_mod(graph, mod)
|
|
53
|
+
version = select_version_for_mod(mod)
|
|
54
|
+
enabled = mod_enabled?(mod)
|
|
55
|
+
|
|
56
|
+
node = Node.new(mod:, version:, enabled:, installed: true)
|
|
57
|
+
graph.add_node(node)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Select which version to use for a MOD
|
|
61
|
+
#
|
|
62
|
+
# @param mod [Factorix::MOD] The MOD
|
|
63
|
+
# @return [Factorix::MODVersion] The selected version
|
|
64
|
+
private def select_version_for_mod(mod)
|
|
65
|
+
if @mod_list.exist?(mod)
|
|
66
|
+
specified_version = @mod_list.version(mod)
|
|
67
|
+
if specified_version
|
|
68
|
+
installed_with_version = @installed_mods.find {|im| im.mod == mod && im.version == specified_version }
|
|
69
|
+
return specified_version if installed_with_version
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
versions_for_mod = @installed_mods.select {|im| im.mod == mod }
|
|
74
|
+
versions_for_mod.max_by(&:version).version
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Add edges for a MOD's dependencies
|
|
78
|
+
#
|
|
79
|
+
# @param graph [Factorix::Dependency::Graph] The graph to add to
|
|
80
|
+
# @param installed_mod [Factorix::InstalledMOD] The installed MOD
|
|
81
|
+
# @return [void]
|
|
82
|
+
private def add_edges_for_dependencies(graph, installed_mod)
|
|
83
|
+
from_mod = installed_mod.mod
|
|
84
|
+
dependencies = installed_mod.info.dependencies || []
|
|
85
|
+
|
|
86
|
+
dependencies.each do |dependency|
|
|
87
|
+
# Skip only base MOD (always available and cannot be disabled)
|
|
88
|
+
# Expansion MODs can be disabled, so they must be validated
|
|
89
|
+
next if dependency.mod.base?
|
|
90
|
+
|
|
91
|
+
edge = Edge.new(from_mod:, to_mod: dependency.mod, type: dependency.type, version_requirement: dependency.version_requirement)
|
|
92
|
+
graph.add_edge(edge)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Check if a MOD is enabled in the MOD list
|
|
97
|
+
#
|
|
98
|
+
# @param mod [Factorix::MOD] The MOD to check
|
|
99
|
+
# @return [Boolean] true if enabled, false otherwise
|
|
100
|
+
private def mod_enabled?(mod)
|
|
101
|
+
return false unless @mod_list.exist?(mod)
|
|
102
|
+
|
|
103
|
+
@mod_list.enabled?(mod)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|