badge 0.13.0 → 0.13.2

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: cde282fe94dedab5bdc9545d4e9b1519268a3cef9b1653b23bd7356c6cd03c89
4
- data.tar.gz: 86bc387e9bdddf2d7015f13917895db8d12f0608db2e1543b2b69d90fca485e2
3
+ metadata.gz: 8a4c10ea1fd4b7302fb8cb869a73a8d6d7aeff77a79867ecfb760d29c4873aed
4
+ data.tar.gz: eabc7362486497a4791523828ab4ded504c564bd47714d1a9c5d6ddeb45fc7ad
5
5
  SHA512:
6
- metadata.gz: 1da1500396eaf05ffb16208af5dc5d48f269f43254cff36f59ec8adfab7ba650866ef4af8ea88d6dad779e707ebd65448b0d259c9b62bf62ef216ab7a16ebd61
7
- data.tar.gz: 9ebfcdd75567fbddf15a4c72c9e8e6b1e65b13aff0e66bfb3a7274a947ef189cce38f886f4843d4c49e4e2f5f32c37eda3890ce31a64d084e8294ae5d48bff0b
6
+ metadata.gz: 45838e608fccf63d3b74d521622eada2f8cbe3c80fd8163336a37668ff53eb0af64e3c218044fb8480bde61dd75dcbdc7d794978a3b4b863b46bc8b33a1c8d11
7
+ data.tar.gz: ba986cc9c58b598dc470331ef5d532d42d5131876175ae1d6021141bcb6f983b7d8fdf6cfd8d28813ec886a0a1d62b22e24d2078b75fa0f30b37f953c0133922
data/lib/badge/base.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module Badge
2
2
 
3
- VERSION = "0.13.0"
3
+ VERSION = "0.13.2"
4
4
  DESCRIPTION = "Add a badge overlay to your app icon"
5
5
 
6
6
  def self.root
@@ -0,0 +1,126 @@
1
+ require 'json'
2
+ require 'pathname'
3
+
4
+ module Badge
5
+ class IconCatalog
6
+ attr_reader :path, :format_type, :icon_files
7
+
8
+ FORMAT_LEGACY = :legacy
9
+ FORMAT_SINGLE_SIZE = :single_size
10
+ FORMAT_LAYERED = :layered
11
+
12
+ def initialize(appiconset_path)
13
+ @path = Pathname.new(appiconset_path)
14
+ @contents_json_path = @path.join('Contents.json')
15
+ @icon_files = []
16
+ @format_type = nil
17
+
18
+ detect_format
19
+ end
20
+
21
+ # Returns list of icon file paths that should be badged
22
+ def badgeable_icons
23
+ case @format_type
24
+ when FORMAT_LAYERED
25
+ # For layered icons, badge all variants (all.png, dark.png, tint.png)
26
+ @icon_files
27
+ when FORMAT_SINGLE_SIZE
28
+ # Single size format - badge the single icon
29
+ @icon_files
30
+ when FORMAT_LEGACY
31
+ # Legacy format - badge all size variants
32
+ @icon_files
33
+ else
34
+ # Fallback to glob if format detection failed
35
+ glob_fallback
36
+ end
37
+ end
38
+
39
+ def self.find_catalogs(search_path, glob_pattern = nil)
40
+ if glob_pattern
41
+ # Use custom glob if provided (backward compatibility)
42
+ UI.verbose "Using custom glob pattern: #{glob_pattern}".blue
43
+ return glob_pattern
44
+ end
45
+
46
+ # Find all .appiconset directories
47
+ appiconset_dirs = Dir.glob("#{search_path}/**/*.appiconset")
48
+
49
+ catalogs = appiconset_dirs.map { |dir| new(dir) }
50
+ UI.verbose "Found #{catalogs.count} app icon catalog(s)".blue
51
+ catalogs.each do |catalog|
52
+ UI.verbose " - #{catalog.path.basename} (#{catalog.format_type})".blue
53
+ end
54
+
55
+ catalogs
56
+ end
57
+
58
+ private
59
+
60
+ def detect_format
61
+ unless File.exist?(@contents_json_path)
62
+ UI.verbose "No Contents.json found at #{@contents_json_path}, using fallback".yellow
63
+ @format_type = :unknown
64
+ return
65
+ end
66
+
67
+ begin
68
+ contents = JSON.parse(File.read(@contents_json_path))
69
+ images = contents['images'] || []
70
+
71
+ if images.empty?
72
+ UI.verbose "Contents.json has no images array".yellow
73
+ @format_type = :unknown
74
+ return
75
+ end
76
+
77
+ # Check for layered format (iOS 18+)
78
+ # Layered icons have images with "appearances" array containing luminosity variants
79
+ has_appearances = images.any? { |img| img['appearances'] }
80
+
81
+ if has_appearances
82
+ @format_type = FORMAT_LAYERED
83
+ @icon_files = extract_layered_icons(images)
84
+ UI.verbose "Detected layered icon format with #{@icon_files.count} variant(s)".blue
85
+ return
86
+ end
87
+
88
+ # Check for single-size format (Xcode 14+)
89
+ # Single size has one image with size "1024x1024" and "idiom" = "universal"
90
+ if images.count == 1 && images[0]['size'] == '1024x1024'
91
+ @format_type = FORMAT_SINGLE_SIZE
92
+ @icon_files = extract_icon_files(images)
93
+ UI.verbose "Detected single-size icon format".blue
94
+ return
95
+ end
96
+
97
+ # Legacy multi-size format
98
+ @format_type = FORMAT_LEGACY
99
+ @icon_files = extract_icon_files(images)
100
+ UI.verbose "Detected legacy multi-size icon format with #{@icon_files.count} size(s)".blue
101
+
102
+ rescue JSON::ParserError => e
103
+ UI.error "Failed to parse Contents.json: #{e.message}".red
104
+ @format_type = :unknown
105
+ end
106
+ end
107
+
108
+ def extract_layered_icons(images)
109
+ extract_icon_files(images)
110
+ end
111
+
112
+ def extract_icon_files(images)
113
+ images.map do |image|
114
+ filename = image['filename']
115
+ next unless filename
116
+
117
+ icon_path = @path.join(filename)
118
+ icon_path.to_s if File.exist?(icon_path) && icon_path.extname.downcase == '.png'
119
+ end.compact
120
+ end
121
+
122
+ def glob_fallback
123
+ Dir.glob("#{@path}/*.{png,PNG}")
124
+ end
125
+ end
126
+ end
data/lib/badge/options.rb CHANGED
@@ -6,30 +6,36 @@ module Badge
6
6
  def self.available_options
7
7
  [
8
8
  FastlaneCore::ConfigItem.new(key: :dark,
9
+ env_name: "BADGE_DARK",
9
10
  description: "Adds a dark badge instead of the white",
10
11
  is_string: false,
11
12
  optional: true),
12
13
 
13
14
  FastlaneCore::ConfigItem.new(key: :alpha,
15
+ env_name: "BADGE_ALPHA",
14
16
  description: "Uses the word alpha instead of beta",
15
17
  is_string: false,
16
18
  optional: true),
17
19
 
18
20
  FastlaneCore::ConfigItem.new(key: :alpha_channel,
21
+ env_name: "BADGE_ALPHA_CHANNEL",
19
22
  description: "Keeps/Adds an alpha channel to the icons",
20
23
  is_string: false,
21
24
  optional: true),
22
25
 
23
26
  FastlaneCore::ConfigItem.new(key: :custom,
27
+ env_name: "BADGE_CUSTOM",
24
28
  description: "Overlay a custom image on your icon",
25
29
  optional: true),
26
30
 
27
31
  FastlaneCore::ConfigItem.new(key: :no_badge,
32
+ env_name: "BADGE_NO_BADGE",
28
33
  description: "Removes the beta badge",
29
34
  is_string: false,
30
35
  optional: true),
31
36
 
32
37
  FastlaneCore::ConfigItem.new(key: :badge_gravity,
38
+ env_name: "BADGE_GRAVITY",
33
39
  description: "Position of the badge on icon. Default: SouthEast - Choices include: #{AVAILABLE_GRAVITIES.join(', ')}",
34
40
  verify_block: proc do |value|
35
41
  UI.user_error!("badge_gravity #{value} is invalid") unless AVAILABLE_GRAVITIES.map(&:upcase).include? value.upcase
@@ -37,23 +43,33 @@ module Badge
37
43
  optional: true),
38
44
 
39
45
  FastlaneCore::ConfigItem.new(key: :shield,
46
+ env_name: "BADGE_SHIELD",
40
47
  description: "Overlay a shield from shields.io on your icon, eg: Version-1.2-green",
41
48
  optional: true),
42
49
 
43
50
  FastlaneCore::ConfigItem.new(key: :shield_parameters,
51
+ env_name: "BADGE_SHIELD_PARAMETERS",
44
52
  description: "Parameters of the shield image. String of key-value pairs separated by ampersand as specified on shields.io, eg: colorA=abcdef&style=flat",
45
53
  optional: true),
46
54
 
55
+ FastlaneCore::ConfigItem.new(key: :shield_base_url,
56
+ env_name: "BADGE_SHIELD_BASE_URL",
57
+ description: "Override the shields.io base URL (e.g. to point at a self-hosted shields server or to work around shields.io outages). Applies to both SVG and PNG requests",
58
+ optional: true),
59
+
47
60
  FastlaneCore::ConfigItem.new(key: :shield_io_timeout,
61
+ env_name: "BADGE_SHIELD_IO_TIMEOUT",
48
62
  description: "The timeout in seconds we should wait to get a response from shields.io",
49
63
  type: Integer,
50
64
  optional: true),
51
65
 
52
66
  FastlaneCore::ConfigItem.new(key: :shield_geometry,
67
+ env_name: "BADGE_SHIELD_GEOMETRY",
53
68
  description: "Position of shield on icon, relative to gravity e.g, +50+10%",
54
69
  optional: true),
55
70
 
56
71
  FastlaneCore::ConfigItem.new(key: :shield_gravity,
72
+ env_name: "BADGE_SHIELD_GRAVITY",
57
73
  description: "Position of shield on icon. Default: North - Choices include: #{AVAILABLE_GRAVITIES.join(', ')}",
58
74
  verify_block: proc do |value|
59
75
  UI.user_error!("badge_gravity #{value} is invalid") unless AVAILABLE_GRAVITIES.map(&:upcase).include? value.upcase
@@ -61,19 +77,23 @@ module Badge
61
77
  optional: true),
62
78
 
63
79
  FastlaneCore::ConfigItem.new(key: :shield_scale,
80
+ env_name: "BADGE_SHIELD_SCALE",
64
81
  description: "Shield image scale factor; e.g, 0.5, 2, etc. - works with --shield_no_resize",
65
82
  optional: true),
66
83
 
67
84
  FastlaneCore::ConfigItem.new(key: :shield_no_resize,
85
+ env_name: "BADGE_SHIELD_NO_RESIZE",
68
86
  description: "Shield image will no longer be resized to aspect fill the full icon. Instead it will only be shrunk to not exceed the icon graphic",
69
87
  is_string: false,
70
88
  optional: true),
71
89
 
72
90
  FastlaneCore::ConfigItem.new(key: :glob,
91
+ env_name: "BADGE_GLOB",
73
92
  description: "Glob pattern for finding image files. Default: CURRENT_PATH/**/*.appiconset/*.{png,PNG}",
74
93
  optional: true),
75
94
 
76
95
  FastlaneCore::ConfigItem.new(key: :grayscale,
96
+ env_name: "BADGE_GRAYSCALE",
77
97
  description: "Whether making icons to grayscale",
78
98
  is_string: false,
79
99
  default_value: false,
data/lib/badge/runner.rb CHANGED
@@ -1,4 +1,3 @@
1
- require 'fastimage'
2
1
  require 'timeout'
3
2
  require 'mini_magick'
4
3
  require 'open-uri'
@@ -10,13 +9,23 @@ module Badge
10
9
 
11
10
  def run(path, options)
12
11
  check_tools!
13
- glob = "/**/*.appiconset/*.{png,PNG}"
14
- glob = options[:glob] if options[:glob]
15
-
16
- app_icons = Dir.glob("#{path}#{glob}")
17
12
  UI.verbose "Verbose active... VERSION: #{Badge::VERSION}".blue
18
13
  UI.verbose "Parameters: #{options.values.inspect}".blue
19
14
 
15
+ # If custom glob is provided, use legacy behavior for backward compatibility
16
+ if options[:glob]
17
+ UI.verbose "Using custom glob pattern (legacy mode)".blue
18
+ glob = options[:glob]
19
+ app_icons = Dir.glob("#{path}#{glob}")
20
+ else
21
+ # Use new IconCatalog approach to intelligently detect icon formats
22
+ UI.verbose "Using smart icon detection with Contents.json parsing".blue
23
+ catalogs = IconCatalog.find_catalogs(path, options[:glob])
24
+ app_icons = catalogs.flat_map(&:badgeable_icons)
25
+ end
26
+
27
+ UI.verbose "Found #{app_icons.count} icon file(s) to badge".blue
28
+
20
29
  if options[:custom] && !File.exist?(options[:custom])
21
30
  UI.error("Could not find custom badge image")
22
31
  UI.user_error!("Specify a valid custom badge image path!")
@@ -35,7 +44,7 @@ module Badge
35
44
  timeout = Badge.shield_io_timeout
36
45
  timeout = options[:shield_io_timeout] if options[:shield_io_timeout]
37
46
  Timeout.timeout(timeout.to_i) do
38
- shield = load_shield(options[:shield], options[:shield_parameters]) if options[:shield]
47
+ shield = load_shield(options[:shield], options[:shield_parameters], options[:shield_base_url]) if options[:shield]
39
48
  end
40
49
  rescue Timeout::Error
41
50
  UI.error "Error loading image from shields.io timeout reached. Use --verbose for more info".red
@@ -81,7 +90,11 @@ module Badge
81
90
  icon_changed = true
82
91
  end
83
92
  if icon_changed
84
- result.format "png"
93
+ # Preserve the original file format (webp, jpg, png, ...) so we
94
+ # don't silently write PNG bytes into a .webp filename. Falls back
95
+ # to png if the input has no recognizable extension.
96
+ ext = icon_path.extname.downcase.delete('.')
97
+ result.format(ext.empty? ? 'png' : ext)
85
98
  result.write full_path
86
99
  end
87
100
  end
@@ -104,10 +117,17 @@ module Badge
104
117
  if @@rsvg_enabled
105
118
  new_path = "#{shield.path}.png"
106
119
  begin
120
+ # Use argv-form system() instead of backticks so paths and scale
121
+ # values can never be interpreted by the shell (avoids e.g. the
122
+ # "Multiple SVG files are only allowed for PDF and (E)PS output"
123
+ # error caused by unquoted shield paths, #54).
107
124
  if shield_no_resize
108
- `rsvg-convert #{shield.path} -z #{shield_scale} -o #{new_path}`
125
+ system('rsvg-convert', '-z', shield_scale.to_s,
126
+ '-o', new_path, '--', shield.path)
109
127
  else
110
- `rsvg-convert #{shield.path} -w #{(icon.width * shield_scale).to_i} -a -o #{new_path}`
128
+ system('rsvg-convert',
129
+ '-w', (icon.width * shield_scale).to_i.to_s, '-a',
130
+ '-o', new_path, '--', shield.path)
111
131
  end
112
132
  rescue Exception => error
113
133
  UI.error "Other error occured. Use --verbose for more info".red
@@ -126,8 +146,10 @@ module Badge
126
146
  result = composite(result, new_shield, alpha_channel, shield_gravity || "north", shield_geometry)
127
147
  end
128
148
 
129
- def load_shield(shield_string, shield_parameters)
130
- url = (@@rsvg_enabled ? Badge.shield_svg_base_url : Badge.shield_base_url) + Badge.shield_path + shield_string + (@@rsvg_enabled ? ".svg" : ".png")
149
+ def load_shield(shield_string, shield_parameters, shield_base_url = nil)
150
+ svg_base = shield_base_url || Badge.shield_svg_base_url
151
+ png_base = shield_base_url || Badge.shield_base_url
152
+ url = (@@rsvg_enabled ? svg_base : png_base) + Badge.shield_path + shield_string + (@@rsvg_enabled ? ".svg" : ".png")
131
153
  if shield_parameters
132
154
  url = url + "?" + shield_parameters
133
155
  end
data/lib/badge.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'badge/base'
2
2
  require 'badge/runner'
3
3
  require 'badge/options.rb'
4
+ require 'badge/icon_catalog.rb'
4
5
 
5
6
  require 'fastlane_core'
6
7
 
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: badge
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.0
4
+ version: 0.13.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Griesser
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2021-03-31 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: fastlane
@@ -24,20 +23,6 @@ dependencies:
24
23
  - - ">="
25
24
  - !ruby/object:Gem::Version
26
25
  version: '2.0'
27
- - !ruby/object:Gem::Dependency
28
- name: fastimage
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: '1.6'
34
- type: :runtime
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- version: '1.6'
41
26
  - !ruby/object:Gem::Dependency
42
27
  name: mini_magick
43
28
  requirement: !ruby/object:Gem::Requirement
@@ -58,7 +43,7 @@ dependencies:
58
43
  - - "<"
59
44
  - !ruby/object:Gem::Version
60
45
  version: 5.0.0
61
- description: 0.13.0
46
+ description: 0.13.2
62
47
  email:
63
48
  - daniel.griesser.86@gmail.com
64
49
  executables:
@@ -76,13 +61,13 @@ files:
76
61
  - lib/badge.rb
77
62
  - lib/badge/base.rb
78
63
  - lib/badge/commands_generator.rb
64
+ - lib/badge/icon_catalog.rb
79
65
  - lib/badge/options.rb
80
66
  - lib/badge/runner.rb
81
67
  homepage: https://github.com/HazAT/badge
82
68
  licenses:
83
69
  - MIT
84
70
  metadata: {}
85
- post_install_message:
86
71
  rdoc_options: []
87
72
  require_paths:
88
73
  - lib
@@ -97,8 +82,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
97
82
  - !ruby/object:Gem::Version
98
83
  version: '0'
99
84
  requirements: []
100
- rubygems_version: 3.0.3
101
- signing_key:
85
+ rubygems_version: 4.0.8
102
86
  specification_version: 4
103
87
  summary: Add a badge overlay to your app icon
104
88
  test_files: []