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 +4 -4
- data/lib/badge/base.rb +1 -1
- data/lib/badge/icon_catalog.rb +126 -0
- data/lib/badge/options.rb +20 -0
- data/lib/badge/runner.rb +33 -11
- data/lib/badge.rb +1 -0
- metadata +5 -21
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8a4c10ea1fd4b7302fb8cb869a73a8d6d7aeff77a79867ecfb760d29c4873aed
|
|
4
|
+
data.tar.gz: eabc7362486497a4791523828ab4ded504c564bd47714d1a9c5d6ddeb45fc7ad
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 45838e608fccf63d3b74d521622eada2f8cbe3c80fd8163336a37668ff53eb0af64e3c218044fb8480bde61dd75dcbdc7d794978a3b4b863b46bc8b33a1c8d11
|
|
7
|
+
data.tar.gz: ba986cc9c58b598dc470331ef5d532d42d5131876175ae1d6021141bcb6f983b7d8fdf6cfd8d28813ec886a0a1d62b22e24d2078b75fa0f30b37f953c0133922
|
data/lib/badge/base.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
125
|
+
system('rsvg-convert', '-z', shield_scale.to_s,
|
|
126
|
+
'-o', new_path, '--', shield.path)
|
|
109
127
|
else
|
|
110
|
-
|
|
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
|
-
|
|
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
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.
|
|
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:
|
|
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.
|
|
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:
|
|
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: []
|