factorix 0.9.0 → 0.10.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 970e619a2c58b6ee5c7502adc6803469accf6ee618e41e580df5f950a8b2ed15
4
- data.tar.gz: 6651e0dea495831ede26d577410722d5ea08542c6ee2516772c413ddec1b7b55
3
+ metadata.gz: 1f669f6ec36e75ef8f9720e7e09337627d56bc84453233484157643f22a55aa9
4
+ data.tar.gz: 2029ef6d075978429ddd533c7ba5aa68eb0c16bf77e9c1802bcbfd159844f767
5
5
  SHA512:
6
- metadata.gz: 6d10a143153a82c1d2b969d928ce85dd22c8c9bf95c793fe27e3287cc5f7408a00fcec47d839295d40390655667a3b1b6e1ab8e76947c9ce98eec8455f87438f
7
- data.tar.gz: 2feeb57d5fc6835ee674f3c024b7ef5a4710e3b4f496dc7ada5ecc76add61e1b0fc3dcc3c7e95aea2ae57d131b28d124da04c83bb7ca1c04869067cac251e4c7
6
+ metadata.gz: f9a17f3a9a83fdf498555deac57a3d9d99b0b33d77a36c8242e5d78e8e85923ed535f4457bf5976e2e3f1b7209214c84e4eda1f9bf00425657b1c8eb471f34de
7
+ data.tar.gz: 0e6c5bfc9e7a09d4ff3529e976b0f7f90cba03d49d2e98ef70ae9eae07031e1c2de90e7f4235dbeb8d68b32d852afa5a674ecc76bcfb048684f9e229454c9c83
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.10.0] - 2026-02-21
4
+
5
+ ### Changed
6
+
7
+ - Refactor `License` to use flyweight pattern for standard licenses with `.for(id)` method
8
+ - Add `.identifiers` class method to `Category`, `License`, and `Tag`
9
+ - Replace `License.identifier_values` with `License.identifiers`
10
+
11
+ ## [0.9.1] - 2026-02-20
12
+
13
+ ### Added
14
+
15
+ - Add `mod changelog extract` command to extract a specific version's changelog section
16
+
3
17
  ## [0.9.0] - 2026-02-20
4
18
 
5
19
  ### Added
@@ -172,7 +172,7 @@ _factorix() {
172
172
  ;;
173
173
  changelog)
174
174
  if [[ $cword -eq 3 ]]; then
175
- COMPREPLY=($(compgen -W "add check release" -- "$cur"))
175
+ COMPREPLY=($(compgen -W "add check extract release" -- "$cur"))
176
176
  else
177
177
  case "${words[3]}" in
178
178
  add)
@@ -194,6 +194,15 @@ _factorix() {
194
194
  COMPREPLY=($(compgen -W "$global_opts --release --changelog --info-json" -- "$cur"))
195
195
  fi
196
196
  ;;
197
+ extract)
198
+ if [[ "$prev" == "--changelog" ]]; then
199
+ COMPREPLY=($(compgen -f -- "$cur"))
200
+ elif [[ "$prev" == "--version" ]]; then
201
+ COMPREPLY=($(compgen -W "Unreleased" -- "$cur"))
202
+ elif [[ "$cur" == -* ]]; then
203
+ COMPREPLY=($(compgen -W "$global_opts --version --json --changelog" -- "$cur"))
204
+ fi
205
+ ;;
197
206
  release)
198
207
  if [[ "$prev" == "--changelog" ]] || [[ "$prev" == "--info-json" ]]; then
199
208
  COMPREPLY=($(compgen -f -- "$cur"))
@@ -233,6 +233,7 @@ complete -c factorix -n "__factorix_using_subcommand mod sync" -ra '(__fish_comp
233
233
  # mod changelog subcommands
234
234
  complete -c factorix -n "__factorix_using_subcommand mod changelog" -a add -d 'Add an entry to MOD changelog'
235
235
  complete -c factorix -n "__factorix_using_subcommand mod changelog" -a check -d 'Validate MOD changelog structure'
236
+ complete -c factorix -n "__factorix_using_subcommand mod changelog" -a extract -d 'Extract a changelog section for a specific version'
236
237
  complete -c factorix -n "__factorix_using_subcommand mod changelog" -a release -d 'Release Unreleased changelog section with a version'
237
238
 
238
239
  # mod changelog add options
@@ -240,6 +241,11 @@ complete -c factorix -n "__factorix_using_sub_subcommand mod changelog add" -l v
240
241
  complete -c factorix -n "__factorix_using_sub_subcommand mod changelog add" -l category -d 'Category name' -xa "'Major Features' Features 'Minor Features' Graphics Sounds Optimizations Balancing 'Combat Balancing' 'Circuit Network' Changes Bugfixes Modding Scripting Gui Control Translation Debug 'Ease of use' Info Locale Compatibility"
241
242
  complete -c factorix -n "__factorix_using_sub_subcommand mod changelog add" -l changelog -d 'Path to changelog file' -rF
242
243
 
244
+ # mod changelog extract options
245
+ complete -c factorix -n "__factorix_using_sub_subcommand mod changelog extract" -l version -d 'Version (X.Y.Z or Unreleased)' -ra 'Unreleased'
246
+ complete -c factorix -n "__factorix_using_sub_subcommand mod changelog extract" -l json -d 'Output in JSON format'
247
+ complete -c factorix -n "__factorix_using_sub_subcommand mod changelog extract" -l changelog -d 'Path to changelog file' -rF
248
+
243
249
  # mod changelog check options
244
250
  complete -c factorix -n "__factorix_using_sub_subcommand mod changelog check" -l release -d 'Disallow Unreleased section'
245
251
  complete -c factorix -n "__factorix_using_sub_subcommand mod changelog check" -l changelog -d 'Path to changelog file' -rF
@@ -293,6 +293,7 @@ _factorix_mod_changelog() {
293
293
  subcommands=(
294
294
  'add:Add an entry to MOD changelog'
295
295
  'check:Validate MOD changelog structure'
296
+ 'extract:Extract a changelog section for a specific version'
296
297
  'release:Release Unreleased changelog section with a version'
297
298
  )
298
299
  _describe -t subcommands 'changelog subcommand' subcommands
@@ -314,6 +315,13 @@ _factorix_mod_changelog() {
314
315
  '--changelog[Path to changelog file]:changelog file:_files' \
315
316
  '--info-json[Path to info.json file]:info.json file:_files'
316
317
  ;;
318
+ extract)
319
+ _arguments \
320
+ $global_opts \
321
+ '--version[Version (X.Y.Z or Unreleased)]:version:(Unreleased)' \
322
+ '--json[Output in JSON format]' \
323
+ '--changelog[Path to changelog file]:changelog file:_files'
324
+ ;;
317
325
  release)
318
326
  _arguments \
319
327
  $global_opts \
data/doc/factorix.1 CHANGED
@@ -184,6 +184,53 @@ Skip confirmation prompts.
184
184
  .TP
185
185
  .BR \-j ", " \-\-jobs =\fIVALUE\fR
186
186
  Number of parallel downloads (default: 4).
187
+ .SS factorix mod changelog add ENTRY
188
+ Add an entry to MOD changelog.
189
+ .TP
190
+ .BR \-\-version =\fIVALUE\fR
191
+ Version (X.Y.Z or Unreleased, default: Unreleased).
192
+ .TP
193
+ .BR \-\-category =\fIVALUE\fR
194
+ Category (e.g., Features, Bugfixes). Required.
195
+ .TP
196
+ .BR \-\-changelog =\fIVALUE\fR
197
+ Path to changelog file (default: changelog.txt).
198
+ .SS factorix mod changelog extract
199
+ Extract a changelog section for a specific version.
200
+ .TP
201
+ .BR \-\-version =\fIVALUE\fR
202
+ Version (X.Y.Z or Unreleased). Required.
203
+ .TP
204
+ .B \-\-json
205
+ Output in JSON format.
206
+ .TP
207
+ .BR \-\-changelog =\fIVALUE\fR
208
+ Path to changelog file (default: changelog.txt).
209
+ .SS factorix mod changelog check
210
+ Validate MOD changelog structure.
211
+ .TP
212
+ .B \-\-release
213
+ Disallow Unreleased section.
214
+ .TP
215
+ .BR \-\-changelog =\fIVALUE\fR
216
+ Path to changelog file (default: changelog.txt).
217
+ .TP
218
+ .BR \-\-info\-json =\fIVALUE\fR
219
+ Path to info.json file (default: info.json).
220
+ .SS factorix mod changelog release
221
+ Convert Unreleased changelog section to a versioned section.
222
+ .TP
223
+ .BR \-\-version =\fIVALUE\fR
224
+ Version (X.Y.Z, default: from info.json).
225
+ .TP
226
+ .BR \-\-date =\fIVALUE\fR
227
+ Release date (YYYY-MM-DD, default: today UTC).
228
+ .TP
229
+ .BR \-\-changelog =\fIVALUE\fR
230
+ Path to changelog file (default: changelog.txt).
231
+ .TP
232
+ .BR \-\-info\-json =\fIVALUE\fR
233
+ Path to info.json file (default: info.json).
187
234
  .SH MOD PORTAL COMMANDS
188
235
  These commands interact with the Factorio MOD Portal and require authentication.
189
236
  .SS factorix mod upload FILE
@@ -53,6 +53,9 @@ module Factorix
53
53
  }.freeze
54
54
  private_constant :CATEGORIES
55
55
 
56
+ # @return [Array<String>] all category identifiers
57
+ def self.identifiers = CATEGORIES.keys
58
+
56
59
  # Get Category instance for the given value
57
60
  #
58
61
  # Returns predefined instance for known categories (flyweight pattern).
@@ -9,10 +9,11 @@ module Factorix
9
9
  # License object from MOD Portal API
10
10
  #
11
11
  # Represents a MOD license information.
12
+ # Uses flyweight pattern for standard licenses.
12
13
  # Also provides valid license identifiers for edit_details API.
13
14
  #
14
15
  # @see https://wiki.factorio.com/Mod_portal_API
15
- # @see https://wiki.factorio.com/Mod_details_API
16
+ # @see https://wiki.factorio.com/Mod_details_API#License
16
17
  class License
17
18
  # @!attribute [r] id
18
19
  # @return [String] license ID
@@ -25,37 +26,92 @@ module Factorix
25
26
  # @!attribute [r] url
26
27
  # @return [URI::HTTPS] license URL
27
28
 
28
- # Valid license identifiers for edit_details API
29
- # Custom licenses (custom_$ID) are not included
30
- IDENTIFIERS = {
31
- "default_mit" => "MIT",
32
- "default_gnugplv3" => "GNU GPLv3",
33
- "default_gnulgplv3" => "GNU LGPLv3",
34
- "default_mozilla2" => "Mozilla Public License 2.0",
35
- "default_apache2" => "Apache License 2.0",
36
- "default_unlicense" => "The Unlicense"
29
+ # Predefined standard license instances
30
+ DEFAULT_MIT = new(
31
+ id: "default_mit",
32
+ name: "MIT",
33
+ title: "MIT License",
34
+ description: "A permissive license that is short and to the point. It lets people do anything with your code with proper attribution and without warranty.",
35
+ url: "https://raw.githubusercontent.com/spdx/license-list-data/main/text/MIT.txt"
36
+ )
37
+ private_constant :DEFAULT_MIT
38
+ DEFAULT_GNUGPLV3 = new(
39
+ id: "default_gnugplv3",
40
+ name: "GNU GPLv3",
41
+ title: "GNU General Public License v3.0",
42
+ description: "The GNU GPL is the most widely used free software license and has a strong copyleft requirement.",
43
+ url: "https://raw.githubusercontent.com/spdx/license-list-data/main/text/GPL-3.0-or-later.txt"
44
+ )
45
+ private_constant :DEFAULT_GNUGPLV3
46
+ DEFAULT_GNULGPLV3 = new(
47
+ id: "default_gnulgplv3",
48
+ name: "GNU LGPLv3",
49
+ title: "GNU Lesser General Public License v3.0",
50
+ description: "Version 3 of the GNU LGPL is an additional set of permissions to the GNU GPLv3 license that requires derived works use the same license.",
51
+ url: "https://raw.githubusercontent.com/spdx/license-list-data/main/text/LGPL-3.0-or-later.txt"
52
+ )
53
+ private_constant :DEFAULT_GNULGPLV3
54
+ DEFAULT_MOZILLA2 = new(
55
+ id: "default_mozilla2",
56
+ name: "Mozilla Public License 2.0",
57
+ title: "Mozilla Public License Version 2.0",
58
+ description: "The Mozilla Public License (MPL 2.0) attempts to be a compromise between the permissive BSD license and the reciprocal GPL license.",
59
+ url: "https://raw.githubusercontent.com/spdx/license-list-data/main/text/MPL-2.0.txt"
60
+ )
61
+ private_constant :DEFAULT_MOZILLA2
62
+ DEFAULT_APACHE2 = new(
63
+ id: "default_apache2",
64
+ name: "Apache License 2.0",
65
+ title: "Apache License, Version 2.0",
66
+ description: "A permissive license that also provides an express grant of patent rights from contributors to users.",
67
+ url: "https://raw.githubusercontent.com/spdx/license-list-data/main/text/Apache-2.0.txt"
68
+ )
69
+ private_constant :DEFAULT_APACHE2
70
+ DEFAULT_UNLICENSE = new(
71
+ id: "default_unlicense",
72
+ name: "The Unlicense",
73
+ title: "The Unlicense",
74
+ description: "The Unlicense is a template to waive copyright interest in software and dedicate it to the public domain.",
75
+ url: "https://raw.githubusercontent.com/spdx/license-list-data/main/text/Unlicense.txt"
76
+ )
77
+ private_constant :DEFAULT_UNLICENSE
78
+
79
+ # Lookup table for flyweight pattern
80
+ LICENSES = {
81
+ "default_mit" => DEFAULT_MIT,
82
+ "default_gnugplv3" => DEFAULT_GNUGPLV3,
83
+ "default_gnulgplv3" => DEFAULT_GNULGPLV3,
84
+ "default_mozilla2" => DEFAULT_MOZILLA2,
85
+ "default_apache2" => DEFAULT_APACHE2,
86
+ "default_unlicense" => DEFAULT_UNLICENSE
37
87
  }.freeze
38
- private_constant :IDENTIFIERS
88
+ private_constant :LICENSES
39
89
 
40
90
  # Pattern for custom license identifiers (custom_ + 24 lowercase hex chars)
41
91
  CUSTOM_LICENSE_PATTERN = /\Acustom_[0-9a-f]{24}\z/
42
92
  private_constant :CUSTOM_LICENSE_PATTERN
43
93
 
94
+ # @return [Array<String>] all license identifiers
95
+ def self.identifiers = LICENSES.keys
96
+
97
+ # Get License instance for the given identifier
98
+ #
99
+ # Returns predefined instance for known licenses (flyweight pattern).
100
+ # Raises an error for unknown license identifiers.
101
+ #
102
+ # @param id [String] license identifier
103
+ # @return [License] License instance
104
+ # @raise [KeyError] if license identifier is unknown
105
+ def self.for(id) = LICENSES.fetch(id.to_s)
106
+
44
107
  # Check if the given value is a valid license identifier
45
108
  #
46
109
  # @param value [String] license identifier
47
110
  # @return [Boolean] true if valid (standard or custom license)
48
111
  def self.valid_identifier?(value)
49
- IDENTIFIERS.key?(value) || CUSTOM_LICENSE_PATTERN.match?(value)
112
+ LICENSES.key?(value) || CUSTOM_LICENSE_PATTERN.match?(value)
50
113
  end
51
114
 
52
- # List all valid license identifier values
53
- #
54
- # @return [Array<String>] array of license identifiers
55
- def self.identifier_values = IDENTIFIERS.keys
56
-
57
- # Create License from API response hash
58
- #
59
115
  # @param id [String] license ID
60
116
  # @param name [String] license name
61
117
  # @param title [String] license title
@@ -79,6 +79,9 @@ module Factorix
79
79
  }.freeze
80
80
  private_constant :TAGS
81
81
 
82
+ # @return [Array<String>] all tag identifiers
83
+ def self.identifiers = TAGS.keys
84
+
82
85
  # Get Tag instance for the given value
83
86
  #
84
87
  # Returns predefined instance for known tags (flyweight pattern).
@@ -89,21 +89,24 @@ module Factorix
89
89
  #
90
90
  # @return [String]
91
91
  def to_s
92
- @sections.map {|section| format_section(section) }.join("\n") + "\n"
92
+ @sections.map {|section| "#{SEPARATOR}\n#{format_section(section)}" }.join("\n") + "\n"
93
93
  end
94
94
 
95
- private def find_or_create_section(version)
96
- @sections.find {|s| s.version == version } || create_section(version)
97
- end
98
-
99
- private def create_section(version)
100
- section = Section[version:, date: nil, categories: {}]
101
- @sections.unshift(section)
102
- section
95
+ # Find a section by version
96
+ #
97
+ # @param version [MODVersion, String] target version (or Changelog::UNRELEASED)
98
+ # @return [Section]
99
+ # @raise [InvalidArgumentError] if the version is not found
100
+ def find_section(version)
101
+ @sections.find {|s| s.version == version } or raise InvalidArgumentError, "version not found: #{version}"
103
102
  end
104
103
 
105
- private def format_section(section)
106
- lines = [SEPARATOR]
104
+ # Format a section as plain text (without separator line)
105
+ #
106
+ # @param section [Section] changelog section
107
+ # @return [String]
108
+ def format_section(section)
109
+ lines = []
107
110
  lines << "Version: #{section.version}"
108
111
  lines << "Date: #{section.date}" if section.date
109
112
  section.categories.each do |cat, entries|
@@ -117,6 +120,16 @@ module Factorix
117
120
  lines.join("\n")
118
121
  end
119
122
 
123
+ private def find_or_create_section(version)
124
+ @sections.find {|s| s.version == version } || create_section(version)
125
+ end
126
+
127
+ private def create_section(version)
128
+ section = Section[version:, date: nil, categories: {}]
129
+ @sections.unshift(section)
130
+ section
131
+ end
132
+
120
133
  # Parslet grammar for Factorio changelog.txt
121
134
  class Grammar < Parslet::Parser
122
135
  rule(:newline) { str("\r\n") | str("\n") }
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Factorix
6
+ class CLI
7
+ module Commands
8
+ module MOD
9
+ module Changelog
10
+ # Extract a specific version's changelog section
11
+ class Extract < Base
12
+ desc "Extract a changelog section for a specific version"
13
+
14
+ option :version, required: true, desc: "Version (X.Y.Z or Unreleased)"
15
+ option :json, type: :flag, default: false, desc: "Output in JSON format"
16
+ option :changelog, default: "changelog.txt", desc: "Path to changelog file"
17
+
18
+ # @param version [String] version string (X.Y.Z or Unreleased)
19
+ # @param json [Boolean] output in JSON format
20
+ # @param changelog [String] path to changelog file
21
+ # @return [void]
22
+ def call(version:, json: false, changelog: "changelog.txt", **)
23
+ target_version = version.casecmp("unreleased").zero? ? Factorix::Changelog::UNRELEASED : MODVersion.from_string(version)
24
+ log = Factorix::Changelog.load(Pathname(changelog))
25
+ section = log.find_section(target_version)
26
+
27
+ if json
28
+ out.puts JSON.pretty_generate(version: section.version.to_s, date: section.date, entries: section.categories)
29
+ else
30
+ out.puts log.format_section(section)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -100,7 +100,7 @@ module Factorix
100
100
  return if API::License.valid_identifier?(license)
101
101
 
102
102
  say "Invalid license identifier: #{license}", prefix: :error
103
- say "Valid identifiers: #{API::License.identifier_values.join(", ")}"
103
+ say "Valid identifiers: #{API::License.identifiers.join(", ")}"
104
104
  say "Custom licenses: custom_<24 hex chars> (e.g., custom_0123456789abcdef01234567)"
105
105
  raise InvalidArgumentError, "Invalid license identifier"
106
106
  end
data/lib/factorix/cli.rb CHANGED
@@ -21,6 +21,7 @@ module Factorix
21
21
  register "completion", Commands::Completion
22
22
  register "mod changelog add", Commands::MOD::Changelog::Add
23
23
  register "mod changelog check", Commands::MOD::Changelog::Check
24
+ register "mod changelog extract", Commands::MOD::Changelog::Extract
24
25
  register "mod changelog release", Commands::MOD::Changelog::Release
25
26
  register "mod check", Commands::MOD::Check
26
27
  register "mod list", Commands::MOD::List
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Factorix
4
- VERSION = "0.9.0"
4
+ VERSION = "0.10.0"
5
5
  public_constant :VERSION
6
6
  end
@@ -9,6 +9,7 @@ module Factorix
9
9
  attr_reader name: String
10
10
  attr_reader description: String
11
11
 
12
+ def self.identifiers: () -> Array[String]
12
13
  def self.for: (String value) -> Category
13
14
  end
14
15
  end
@@ -11,8 +11,9 @@ module Factorix
11
11
  attr_reader description: String
12
12
  attr_reader url: URI::HTTPS
13
13
 
14
+ def self.identifiers: () -> Array[String]
15
+ def self.for: (String id) -> License
14
16
  def self.valid_identifier?: (String value) -> bool
15
- def self.identifier_values: () -> Array[String]
16
17
 
17
18
  def initialize: (id: String, name: String, title: String, description: String, url: String) -> void
18
19
  end
@@ -9,6 +9,7 @@ module Factorix
9
9
  attr_reader name: String
10
10
  attr_reader description: String
11
11
 
12
+ def self.identifiers: () -> Array[String]
12
13
  def self.for: (String value) -> Tag
13
14
  end
14
15
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: factorix
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - OZAWA Sakuro
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-02-20 00:00:00.000000000 Z
11
+ date: 2026-02-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -270,6 +270,7 @@ files:
270
270
  - lib/factorix/cli/commands/man.rb
271
271
  - lib/factorix/cli/commands/mod/changelog/add.rb
272
272
  - lib/factorix/cli/commands/mod/changelog/check.rb
273
+ - lib/factorix/cli/commands/mod/changelog/extract.rb
273
274
  - lib/factorix/cli/commands/mod/changelog/release.rb
274
275
  - lib/factorix/cli/commands/mod/check.rb
275
276
  - lib/factorix/cli/commands/mod/disable.rb