badge 0.12.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: 9f352f9dd1f8b38ee80a7a4d047c2344bb827a03a4f8a15d00207172949f7491
4
- data.tar.gz: a97066b187f968895e957724cd5de7ec9648f7f06406c40e727ba9f3d1cd2a4a
3
+ metadata.gz: 8a4c10ea1fd4b7302fb8cb869a73a8d6d7aeff77a79867ecfb760d29c4873aed
4
+ data.tar.gz: eabc7362486497a4791523828ab4ded504c564bd47714d1a9c5d6ddeb45fc7ad
5
5
  SHA512:
6
- metadata.gz: 3c8961c9b60dc5a036408a1583e3b49e13eacc921a67f773181d00883ca6e6eb43e76141211a0eecaa7685da4782794b992b5a1bf98aef46873428123f985cfa
7
- data.tar.gz: fcdb04c1e0cba18ff95bd710aca0c4c1b8129d7d475df38da1ab174cab6dd6e98e4cae4e978abf54317d2d238c6757000ac835b4ada5d241967f7e4f2a6de138
6
+ metadata.gz: 45838e608fccf63d3b74d521622eada2f8cbe3c80fd8163336a37668ff53eb0af64e3c218044fb8480bde61dd75dcbdc7d794978a3b4b863b46bc8b33a1c8d11
7
+ data.tar.gz: ba986cc9c58b598dc470331ef5d532d42d5131876175ae1d6021141bcb6f983b7d8fdf6cfd8d28813ec886a0a1d62b22e24d2078b75fa0f30b37f953c0133922
data/README.md CHANGED
@@ -106,7 +106,7 @@ Sometimes the response from shields.io takes a long time and can timeout. You ca
106
106
 
107
107
  `--shield_parameters "colorA=abcdef&style=flat"` changes the parameters of the shield image. It uses a string of key-value pairs separated by ampersand as specified on shields.io, eg: colorA=abcdef&style=flat.
108
108
 
109
- In version [0.4.0](https://github.com/HazAT/badge/releases/tag/0.4.0) the default behavior of the shield graphic has been changed. The shield graphic will always be resized to **aspect fill** the icon instead of just adding the shield on the icon. The disable to new behaviour use `--shield_no_resize` which now only puts the shield on the icon again.
109
+ In version [0.4.0](https://github.com/HazAT/badge/releases/tag/0.4.0) the default behavior of the shield graphic has been changed. The shield graphic will always be resized to **aspect fill** the icon instead of just adding the shield on the icon. To disable the new behaviour use `--shield_no_resize` which now only puts the shield on the icon again.
110
110
 
111
111
  Add ```--no_badge``` as an option to hide the beta badge completely if you just want to add a shield.
112
112
 
data/lib/badge/base.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module Badge
2
2
 
3
- VERSION = "0.12.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,
14
- description: "Uses the work alpha instead of beta",
15
+ env_name: "BADGE_ALPHA",
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,
48
- description: "The timeout in seconds we should wait the get a response from shields.io",
61
+ env_name: "BADGE_SHIELD_IO_TIMEOUT",
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,
68
- description: "Shield image will no longer be resized to aspect fill the full icon. Instead it will only be shrinked to not exceed the icon graphic",
85
+ env_name: "BADGE_SHIELD_NO_RESIZE",
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,
73
- description: "Glob pattern for finding image files Default: CURRENT_PATH/**/*.appiconset/*.{png,PNG}",
91
+ env_name: "BADGE_GLOB",
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,7 +1,6 @@
1
- require 'fastimage'
2
1
  require 'timeout'
3
2
  require 'mini_magick'
4
- require 'curb'
3
+ require 'open-uri'
5
4
 
6
5
  module Badge
7
6
  class Runner
@@ -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,11 +44,11 @@ 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
42
- rescue Curl::Err::CurlError => error
51
+ rescue OpenURI::HTTPError => error
43
52
  response = error.io
44
53
  UI.error "Error loading image from shields.io response Error. Use --verbose for more info".red
45
54
  UI.verbose response.status if FastlaneCore::Globals.verbose?
@@ -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
@@ -90,12 +103,6 @@ module Badge
90
103
  else
91
104
  UI.message "Did nothing... Enable --verbose for more info.".red
92
105
  end
93
-
94
- if shield
95
- File.delete(shield) if File.exist?(shield)
96
- File.delete("#{shield.path}.png") if File.exist?("#{shield.path}.png")
97
- end
98
-
99
106
  else
100
107
  UI.error "Could not find any app icons...".red
101
108
  end
@@ -110,10 +117,17 @@ module Badge
110
117
  if @@rsvg_enabled
111
118
  new_path = "#{shield.path}.png"
112
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).
113
124
  if shield_no_resize
114
- `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)
115
127
  else
116
- `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)
117
131
  end
118
132
  rescue Exception => error
119
133
  UI.error "Other error occured. Use --verbose for more info".red
@@ -132,20 +146,18 @@ module Badge
132
146
  result = composite(result, new_shield, alpha_channel, shield_gravity || "north", shield_geometry)
133
147
  end
134
148
 
135
- def load_shield(shield_string, shield_parameters)
136
- 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")
137
153
  if shield_parameters
138
154
  url = url + "?" + shield_parameters
139
155
  end
140
- file_name = shield_string + (@@rsvg_enabled ? ".svg" : ".png")
141
156
 
142
157
  UI.verbose "Trying to load image from shields.io. Timeout: #{Badge.shield_io_timeout}s".blue
143
158
  UI.verbose "URL: #{url}".blue
144
159
 
145
- Curl::Easy.download(url, file_name)
146
- MiniMagick::Image.open(file_name) unless @@rsvg_enabled
147
-
148
- File.open(file_name)
160
+ MiniMagick::Image.open(url)
149
161
  end
150
162
 
151
163
  def check_tools!
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,29 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: badge
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.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: 2020-01-15 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: curb
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '0.9'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '0.9'
27
12
  - !ruby/object:Gem::Dependency
28
13
  name: fastlane
29
14
  requirement: !ruby/object:Gem::Requirement
@@ -38,20 +23,6 @@ dependencies:
38
23
  - - ">="
39
24
  - !ruby/object:Gem::Version
40
25
  version: '2.0'
41
- - !ruby/object:Gem::Dependency
42
- name: fastimage
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: '1.6'
48
- type: :runtime
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: '1.6'
55
26
  - !ruby/object:Gem::Dependency
56
27
  name: mini_magick
57
28
  requirement: !ruby/object:Gem::Requirement
@@ -72,7 +43,7 @@ dependencies:
72
43
  - - "<"
73
44
  - !ruby/object:Gem::Version
74
45
  version: 5.0.0
75
- description: 0.12.0
46
+ description: 0.13.2
76
47
  email:
77
48
  - daniel.griesser.86@gmail.com
78
49
  executables:
@@ -90,13 +61,13 @@ files:
90
61
  - lib/badge.rb
91
62
  - lib/badge/base.rb
92
63
  - lib/badge/commands_generator.rb
64
+ - lib/badge/icon_catalog.rb
93
65
  - lib/badge/options.rb
94
66
  - lib/badge/runner.rb
95
67
  homepage: https://github.com/HazAT/badge
96
68
  licenses:
97
69
  - MIT
98
70
  metadata: {}
99
- post_install_message:
100
71
  rdoc_options: []
101
72
  require_paths:
102
73
  - lib
@@ -111,8 +82,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
111
82
  - !ruby/object:Gem::Version
112
83
  version: '0'
113
84
  requirements: []
114
- rubygems_version: 3.0.3
115
- signing_key:
85
+ rubygems_version: 4.0.8
116
86
  specification_version: 4
117
87
  summary: Add a badge overlay to your app icon
118
88
  test_files: []