appcask 0.5.0 → 0.6.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: 48679d79eff66c3c34e8ee886f5449a6c7919a114fb39472b132f1492cca43c1
4
- data.tar.gz: 33588e1ff753e1515c8da38da478c201d11462e7419f4f36f36a0e4bfd91f20e
3
+ metadata.gz: f5e40008e983710a2983ba8a2d4005b2105319ef44f95b9509159ee348e1557c
4
+ data.tar.gz: 299b0bdf02be893883883ea60020bc766582e3d697c512aa2d05df3d98b26500
5
5
  SHA512:
6
- metadata.gz: 95120b0ef5a1aed7f65affdfcbcf1e981e3d1479481edb9550e602ebb2acb8871d49304503f75670128f7a4fb4c507bc90d19ee8c730aab0e7582f6a5829e724
7
- data.tar.gz: 5752039b68b0257ace847d0487799d28d16a589338e8b2d5ccccc51a8e4c64e0051176b3cf5f372fbb9ecb2164850a5ed1361253e22ebbc44d317f2d21a67dda
6
+ metadata.gz: bca199b0f6436e2754151b5ffaa75dccebaab9e294fc3f0e4b7b3c4dde0c70abf6a84c65580f13db2a2516232310614ed336f018695107cfbc5564f2d31055cc
7
+ data.tar.gz: 26b451d84723e83ec2b7afa815b14571396818bc3abd70501fa81a93458fa5ca84b95e8ac7f7cfecda95396c0ce9d43e38612439016df1522135480f35f4a8e9
data/.idea/workspace.xml CHANGED
@@ -4,14 +4,7 @@
4
4
  <option name="autoReloadType" value="SELECTIVE" />
5
5
  </component>
6
6
  <component name="ChangeListManager">
7
- <list default="true" id="1bd4c817-c426-471c-bdf5-e80649db2ae2" name="更改" comment="Release v0.4.0">
8
- <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
9
- <change beforePath="$PROJECT_DIR$/CHANGELOG.md" beforeDir="false" afterPath="$PROJECT_DIR$/CHANGELOG.md" afterDir="false" />
10
- <change beforePath="$PROJECT_DIR$/QUICKSTART.md" beforeDir="false" afterPath="$PROJECT_DIR$/QUICKSTART.md" afterDir="false" />
11
- <change beforePath="$PROJECT_DIR$/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/README.md" afterDir="false" />
12
- <change beforePath="$PROJECT_DIR$/lib/appcask/version.rb" beforeDir="false" afterPath="$PROJECT_DIR$/lib/appcask/version.rb" afterDir="false" />
13
- <change beforePath="$PROJECT_DIR$/sig/appcask.rbs" beforeDir="false" afterPath="$PROJECT_DIR$/sig/appcask.rbs" afterDir="false" />
14
- </list>
7
+ <list default="true" id="1bd4c817-c426-471c-bdf5-e80649db2ae2" name="更改" comment="update CHANGELOG.md" />
15
8
  <option name="SHOW_DIALOG" value="false" />
16
9
  <option name="HIGHLIGHT_CONFLICTS" value="true" />
17
10
  <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
@@ -80,7 +73,7 @@
80
73
  <workItem from="1769697520161" duration="4958000" />
81
74
  <workItem from="1769702829159" duration="2415000" />
82
75
  <workItem from="1769709405343" duration="432000" />
83
- <workItem from="1769754602300" duration="460000" />
76
+ <workItem from="1769754602300" duration="1322000" />
84
77
  </task>
85
78
  <task id="LOCAL-00001" summary="delete gem">
86
79
  <option name="closed" value="true" />
@@ -98,7 +91,23 @@
98
91
  <option name="project" value="LOCAL" />
99
92
  <updated>1769709497761</updated>
100
93
  </task>
101
- <option name="localTasksCounter" value="3" />
94
+ <task id="LOCAL-00003" summary="Release v0.5.0">
95
+ <option name="closed" value="true" />
96
+ <created>1769755115516</created>
97
+ <option name="number" value="00003" />
98
+ <option name="presentableId" value="LOCAL-00003" />
99
+ <option name="project" value="LOCAL" />
100
+ <updated>1769755115516</updated>
101
+ </task>
102
+ <task id="LOCAL-00004" summary="update CHANGELOG.md">
103
+ <option name="closed" value="true" />
104
+ <created>1769755271793</created>
105
+ <option name="number" value="00004" />
106
+ <option name="presentableId" value="LOCAL-00004" />
107
+ <option name="project" value="LOCAL" />
108
+ <updated>1769755271793</updated>
109
+ </task>
110
+ <option name="localTasksCounter" value="5" />
102
111
  <servers />
103
112
  </component>
104
113
  <component name="TypeScriptGeneratedFilesManager">
@@ -108,6 +117,8 @@
108
117
  <MESSAGE value="first commit" />
109
118
  <MESSAGE value="delete gem" />
110
119
  <MESSAGE value="Release v0.4.0" />
111
- <option name="LAST_COMMIT_MESSAGE" value="Release v0.4.0" />
120
+ <MESSAGE value="Release v0.5.0" />
121
+ <MESSAGE value="update CHANGELOG.md" />
122
+ <option name="LAST_COMMIT_MESSAGE" value="update CHANGELOG.md" />
112
123
  </component>
113
124
  </project>
data/CHANGELOG.md CHANGED
@@ -51,3 +51,27 @@ New support added for:
51
51
 
52
52
  ### 📚 Documentation
53
53
  - Update Ruby version requirement in README
54
+
55
+ ## [0.5.0] - 2026-01-30
56
+
57
+ ### 🌍 Internationalization
58
+ - **Comprehensive English Documentation**: Translated all user-facing documentation from Chinese to American English
59
+ - README.md: Complete translation of app description, features, installation guide, usage examples
60
+ - QUICKSTART.md: Full translation of quick start guide with all use case scenarios
61
+ - CHANGELOG.md: Translated all version history entries
62
+ - **Type Signature Update**: Fixed RBS type signature module name from `Appcask` to `AppCask` for consistency
63
+
64
+ ## [0.6.0] - 2026-07-24
65
+
66
+ ### 🆕 New Features
67
+ - **`appcask fetch`** command: Extract icons from locally installed macOS apps
68
+ - Reads `Info.plist` to find `CFBundleTypeIconFile` / `CFBundleIconFile`
69
+ - Parses `.icns` files and extracts the largest available PNG
70
+ - Auto-resizes to 512x512 with transparency preserved
71
+ - Saves to `~/Desktop/AppCask Downloads/Local Apps/`
72
+ - Supports Chinese app names
73
+
74
+ ### 📦 Dependencies
75
+ - Add `icns` gem for `.icns` file parsing
76
+ - Add `CFPropertyList` gem for reading binary/XML plist files
77
+ - Add `mini_magick` gem for image resizing
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  <div align="center">
4
4
 
5
- ![Version](https://img.shields.io/badge/version-0.4.0-blue.svg)
5
+ ![Version](https://img.shields.io/badge/version-0.6.0-blue.svg)
6
6
  ![Ruby](https://img.shields.io/badge/ruby-%3E%3D%203.2.0-red.svg)
7
7
  ![License](https://img.shields.io/badge/license-MIT-green.svg)
8
8
 
@@ -74,7 +74,7 @@ git clone https://github.com/yourusername/appcask.git
74
74
  cd appcask
75
75
  bundle install
76
76
  rake build
77
- gem install pkg/appcask-1.0.0.gem
77
+ gem install pkg/appcask-0.6.0.gem
78
78
  ```
79
79
 
80
80
  ---
@@ -99,7 +99,7 @@ $ appcask "Twitter"
99
99
 
100
100
  ╔═══════════════════════════════════════════╗
101
101
  ║ AppCask - App Resources Downloader ║
102
- v1.0.0 ║
102
+ v0.6.0 ║
103
103
  ╚═══════════════════════════════════════════╝
104
104
 
105
105
  🔍 Searching for "Twitter"...
@@ -1,26 +1,28 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
2
4
  # AppCask batch download example
3
5
  # Usage: ruby examples/batch_download.rb
4
6
 
5
- require 'appcask'
7
+ require "appcask"
6
8
 
7
9
  # Define the list of apps to download
8
10
  apps = [
9
- { name: 'Instagram', region: 'us', description: 'Social media app' },
10
- { name: 'Twitter', region: 'us', description: 'Microblogging platform' },
11
- { name: 'WeChat', region: 'cn', description: 'Instant messaging app' },
12
- { name: 'LINE', region: 'jp', description: 'Messaging app popular in Japan' },
13
- { name: 'KakaoTalk', region: 'kr', description: 'Messaging app popular in Korea' }
11
+ { name: "Instagram", region: "us", description: "Social media app" },
12
+ { name: "Twitter", region: "us", description: "Microblogging platform" },
13
+ { name: "WeChat", region: "cn", description: "Instant messaging app" },
14
+ { name: "LINE", region: "jp", description: "Messaging app popular in Japan" },
15
+ { name: "KakaoTalk", region: "kr", description: "Messaging app popular in Korea" }
14
16
  ]
15
17
 
16
- puts '=' * 50
17
- puts 'AppCask Batch Download Script'
18
- puts '=' * 50
18
+ puts "=" * 50
19
+ puts "AppCask Batch Download Script"
20
+ puts "=" * 50
19
21
  puts "\nPreparing to download resources for #{apps.size} apps...\n\n"
20
22
 
21
23
  apps.each_with_index do |app, index|
22
24
  puts "\n[#{index + 1}/#{apps.size}] Processing: #{app[:name]} (#{app[:description]})"
23
- puts '-' * 50
25
+ puts "-" * 50
24
26
 
25
27
  # Set command-line arguments
26
28
  ARGV.clear
@@ -38,6 +40,6 @@ apps.each_with_index do |app, index|
38
40
  sleep 2 unless index == apps.size - 1
39
41
  end
40
42
 
41
- puts "\n" + '=' * 50
42
- puts 'Batch download completed!'
43
- puts '=' * 50
43
+ puts "\n#{"=" * 50}"
44
+ puts "Batch download completed!"
45
+ puts "=" * 50
data/exe/appcask CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require "appcask"
4
5
 
5
- AppCask.main
6
+ AppCask.main
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cfpropertylist"
4
+ require "icns"
5
+ require "mini_magick"
6
+ require "fileutils"
7
+
8
+ module AppCask
9
+ class LocalApp
10
+ APPS_DIR = "/Applications"
11
+ OUTPUT_BASE = File.join(Dir.home, "Desktop", "AppCask Downloads", "Local Apps")
12
+
13
+ class << self
14
+ def fetch_all
15
+ puts "\nFetching local app icons..."
16
+ puts "Source: #{APPS_DIR}"
17
+ puts "Output: #{OUTPUT_BASE}\n\n"
18
+
19
+ FileUtils.mkdir_p(OUTPUT_BASE)
20
+
21
+ apps = Dir.glob(File.join(APPS_DIR, "*.app"))
22
+ total = apps.count
23
+ success = 0
24
+ skipped = 0
25
+
26
+ apps.each_with_index do |app_path, index|
27
+ app_name = File.basename(app_path, ".app")
28
+ print "\r[#{index + 1}/#{total}] Processing: #{app_name.ljust(30)}"
29
+
30
+ result = extract_icon(app_path)
31
+ if result
32
+ success += 1
33
+ else
34
+ skipped += 1
35
+ end
36
+ end
37
+
38
+ puts "\n\n#{"=" * 50}"
39
+ puts "Done!"
40
+ puts " Total apps: #{total}"
41
+ puts " Extracted: #{success}"
42
+ puts " Skipped: #{skipped}"
43
+ puts " Output: #{OUTPUT_BASE}"
44
+ puts "=" * 50
45
+
46
+ # Open folder on macOS
47
+ return unless RUBY_PLATFORM.include?("darwin")
48
+
49
+ print "\nOpen the folder? (y/n): "
50
+ response = $stdin.gets
51
+ system("open '#{OUTPUT_BASE}'") if response&.strip&.downcase == "y"
52
+ end
53
+
54
+ private
55
+
56
+ def extract_icon(app_path)
57
+ # 1. Read Info.plist
58
+ plist_path = File.join(app_path, "Contents", "Info.plist")
59
+ return nil unless File.exist?(plist_path)
60
+
61
+ plist = CFPropertyList::List.new(file: plist_path)
62
+ info = CFPropertyList.native_types(plist.value)
63
+
64
+ # 2. Get icon file name
65
+ icon_name = info["CFBundleTypeIconFile"] || info["CFBundleIconFile"] || info["CFBundleIconName"]
66
+ return nil unless icon_name
67
+
68
+ icon_name += ".icns" unless icon_name.end_with?(".icns")
69
+
70
+ # 3. Find .icns file
71
+ icns_path = File.join(app_path, "Contents", "Resources", icon_name)
72
+ return nil unless File.exist?(icns_path)
73
+
74
+ # 4. Parse .icns and extract largest PNG
75
+ reader = Icns::Reader.new(icns_path)
76
+
77
+ # Priority: 1024 → 512 → 256 → 128
78
+ [1024, 512, 256, 128].each do |size|
79
+ data = reader.image(size: size)
80
+ next unless data
81
+ next unless png?(data)
82
+
83
+ # 5. Save as 512x512 PNG
84
+ output_path = output_path(app_path)
85
+ save_as_512(data, size, output_path)
86
+ return output_path
87
+ end
88
+
89
+ nil
90
+ rescue StandardError
91
+ nil
92
+ end
93
+
94
+ def png?(data)
95
+ png_magic = [0x89, 0x50, 0x4e, 0x47].pack("C4")
96
+ data[0..3] == png_magic
97
+ end
98
+
99
+ def save_as_512(data, original_size, output_path)
100
+ if original_size == 512
101
+ # Direct write
102
+ File.write(output_path, data)
103
+ else
104
+ # Need to resize - use mini_magick
105
+ resize_with_mini_magick(data, output_path)
106
+ end
107
+ end
108
+
109
+ def resize_with_mini_magick(data, output_path)
110
+ # Write to temp file, resize, then save
111
+ tmp_input = File.join(Dir.tmpdir, "appcask_input_#{Process.pid}.png")
112
+ tmp_output = File.join(Dir.tmpdir, "appcask_output_#{Process.pid}.png")
113
+
114
+ File.write(tmp_input, data)
115
+
116
+ image = MiniMagick::Image.new(tmp_input)
117
+ image.resize "512x512"
118
+ image.format "png"
119
+ image.write(tmp_output)
120
+
121
+ FileUtils.cp(tmp_output, output_path)
122
+ ensure
123
+ FileUtils.rm_f(tmp_input) if tmp_input
124
+ FileUtils.rm_f(tmp_output) if tmp_output
125
+ end
126
+
127
+ def output_path(app_path)
128
+ app_name = File.basename(app_path, ".app")
129
+ filename = "#{sanitize_app_name(app_name)}.png"
130
+ File.join(OUTPUT_BASE, filename)
131
+ end
132
+
133
+ def sanitize_app_name(name)
134
+ # Keep Chinese characters, convert to lowercase, replace spaces with hyphens
135
+ name.downcase.gsub(/\s+/, "-").gsub(/[^a-z0-9\u4e00-\u9fff-]/, "").gsub(/^-|-$/, "")
136
+ end
137
+ end
138
+ end
139
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AppCask
4
- VERSION = "0.5.0"
4
+ VERSION = "0.6.0"
5
5
  end
data/lib/appcask.rb CHANGED
@@ -1,57 +1,64 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "appcask/version"
4
- require 'net/https'
5
- require 'open-uri'
6
- require 'json'
7
- require 'fileutils'
4
+ require "net/https"
5
+ require "open-uri"
6
+ require "json"
7
+ require "fileutils"
8
8
 
9
9
  module AppCask
10
10
  class Error < StandardError; end
11
11
 
12
12
  ICON_SIZES = {
13
- '0' => { display: '60x60', key: 'artworkUrl60' },
14
- '1' => { display: '100x100', key: 'artworkUrl100' },
15
- '2' => { display: '512x512', key: 'artworkUrl512' },
16
- '3' => { display: '1024x1024', key: 'artworkUrl512' }
13
+ "0" => { display: "60x60", key: "artworkUrl60" },
14
+ "1" => { display: "100x100", key: "artworkUrl100" },
15
+ "2" => { display: "512x512", key: "artworkUrl512" },
16
+ "3" => { display: "1024x1024", key: "artworkUrl512" }
17
17
  }.freeze
18
18
 
19
19
  SCREENSHOT_DEVICES = {
20
- 'iphone' => 'iPhone screenshots',
21
- 'ipad' => 'iPad screenshots'
20
+ "iphone" => "iPhone screenshots",
21
+ "ipad" => "iPad screenshots"
22
22
  }.freeze
23
23
 
24
24
  COUNTRIES = {
25
- 'us' => 'United States',
26
- 'cn' => 'China',
27
- 'jp' => 'Japan',
28
- 'kr' => 'South Korea',
29
- 'hk' => 'Hong Kong',
30
- 'tw' => 'Taiwan',
31
- 'gb' => 'United Kingdom',
32
- 'de' => 'Germany',
33
- 'fr' => 'France'
25
+ "us" => "United States",
26
+ "cn" => "China",
27
+ "jp" => "Japan",
28
+ "kr" => "South Korea",
29
+ "hk" => "Hong Kong",
30
+ "tw" => "Taiwan",
31
+ "gb" => "United Kingdom",
32
+ "de" => "Germany",
33
+ "fr" => "France"
34
34
  }.freeze
35
35
 
36
36
  DOWNLOAD_MODES = {
37
- '1' => { name: 'Icon Only', method: :download_icon },
38
- '2' => { name: 'Screenshots Only', method: :download_screenshots },
39
- '3' => { name: 'Description Only', method: :download_description },
40
- '4' => { name: 'All Assets', method: :download_all }
37
+ "1" => { name: "Icon Only", method: :download_icon },
38
+ "2" => { name: "Screenshots Only", method: :download_screenshots },
39
+ "3" => { name: "Description Only", method: :download_description },
40
+ "4" => { name: "All Assets", method: :download_all }
41
41
  }.freeze
42
42
 
43
43
  class << self
44
44
  def main
45
- begin
45
+ command = ARGV[0]&.downcase
46
+
47
+ case command
48
+ when "fetch", "local"
49
+ show_banner
50
+ require_relative "appcask/local_app"
51
+ AppCask::LocalApp.fetch_all
52
+ else
46
53
  get
47
- rescue Interrupt
48
- puts "\n\n👋 Goodbye!"
49
- exit 0
50
- rescue StandardError => e
51
- warn "\n❌ Error: #{e.message}"
52
- warn e.backtrace if ENV['DEBUG']
53
- exit 1
54
54
  end
55
+ rescue Interrupt
56
+ puts "\n\n👋 Goodbye!"
57
+ exit 0
58
+ rescue StandardError => e
59
+ warn "\n❌ Error: #{e.message}"
60
+ warn e.backtrace if ENV["DEBUG"]
61
+ exit 1
55
62
  end
56
63
 
57
64
  def get
@@ -64,7 +71,7 @@ module AppCask
64
71
 
65
72
  results = search_app(app_name, country)
66
73
 
67
- if results.nil? || results['resultCount'].to_i.zero?
74
+ if results.nil? || results["resultCount"].to_i.zero?
68
75
  warn "❌ No apps found for \"#{app_name}\"."
69
76
  return
70
77
  end
@@ -82,20 +89,20 @@ module AppCask
82
89
 
83
90
  def show_banner
84
91
  puts <<~BANNER
85
- ▄▖ ▄▖ ▌
86
- ▌▌▛▌▛▌▌ ▀▌▛▘▙▘
87
- ▛▌▙▌▙▌▙▖█▌▄▌▛▖
88
- ▌ ▌
89
-
90
- v#{AppCask::VERSION}
92
+ ▄▖ ▄▖ ▌
93
+ ▌▌▛▌▛▌▌ ▀▌▛▘▙▘
94
+ ▛▌▙▌▙▌▙▖█▌▄▌▛▖
95
+ ▌ ▌
96
+
97
+ v#{AppCask::VERSION}
91
98
  BANNER
92
99
  end
93
100
 
94
101
  def get_app_name
95
- if ARGV.count.positive?
102
+ if ARGV.any?
96
103
  ARGV[0]
97
104
  else
98
- print '📱 Enter the app name to search: '
105
+ print "📱 Enter the app name to search: "
99
106
  $stdin.gets.chomp.strip
100
107
  end
101
108
  end
@@ -110,16 +117,21 @@ module AppCask
110
117
  print "Choose one (default: us): "
111
118
 
112
119
  input = $stdin.gets.chomp.strip.downcase
113
- input.empty? ? 'us' : (COUNTRIES.key?(input) ? input : 'us')
120
+ if input.empty?
121
+ "us"
122
+ else
123
+ (COUNTRIES.key?(input) ? input : "us")
124
+ end
114
125
  end
126
+
115
127
  def search_app(app_name, country)
116
- url = URI('https://itunes.apple.com/search')
128
+ url = URI("https://itunes.apple.com/search")
117
129
  params = {
118
130
  term: app_name,
119
131
  country: country,
120
- media: 'software',
121
- entity: 'software',
122
- limit: '20'
132
+ media: "software",
133
+ entity: "software",
134
+ limit: "20"
123
135
  }
124
136
 
125
137
  http = Net::HTTP.new(url.host, url.port)
@@ -140,18 +152,17 @@ module AppCask
140
152
 
141
153
  json = JSON.parse(response.body)
142
154
 
143
- if json['resultCount'].zero?
144
- warn '😕 No apps found. Please try a different keyword.'
155
+ if json["resultCount"].zero?
156
+ warn "😕 No apps found. Please try a different keyword."
145
157
  return nil
146
158
  end
147
159
 
148
160
  json
149
-
150
161
  rescue JSON::ParserError => e
151
162
  warn "❌ Failed to parse response: #{e.message}"
152
163
  nil
153
164
  rescue Net::OpenTimeout, Net::ReadTimeout
154
- warn '❌ Network timeout. Please check your connection.'
165
+ warn "❌ Network timeout. Please check your connection."
155
166
  nil
156
167
  rescue StandardError => e
157
168
  warn "❌ Search error: #{e.message}"
@@ -159,34 +170,34 @@ module AppCask
159
170
  end
160
171
 
161
172
  def select_app(results)
162
- puts "\n📋 Found #{results['resultCount']} result(s):\n\n"
173
+ puts "\n📋 Found #{results["resultCount"]} result(s):\n\n"
163
174
 
164
- results['results'].each_with_index do |item, index|
165
- developer = item['artistName'] || 'Unknown Developer'
166
- price = item['formattedPrice'] || item['price']
167
- version = item['version'] || 'N/A'
168
- rating = item['averageUserRating'] ? "⭐ #{item['averageUserRating'].round(1)}" : 'No Rating'
175
+ results["results"].each_with_index do |item, index|
176
+ developer = item["artistName"] || "Unknown Developer"
177
+ price = item["formattedPrice"] || item["price"]
178
+ version = item["version"] || "N/A"
179
+ rating = item["averageUserRating"] ? "⭐ #{item["averageUserRating"].round(1)}" : "No Rating"
169
180
 
170
- puts " [#{index}] #{item['trackCensoredName']}"
181
+ puts " [#{index}] #{item["trackCensoredName"]}"
171
182
  puts " Developer: #{developer} | Version: #{version}"
172
183
  puts " Price: #{price} | Rating: #{rating}"
173
184
  puts
174
185
  end
175
186
 
176
- print "Select an app (0-#{results['resultCount'] - 1}, or press q to quit): "
187
+ print "Select an app (0-#{results["resultCount"] - 1}, or press q to quit): "
177
188
  input = $stdin.gets.chomp.strip
178
189
 
179
- return nil if input.downcase == 'q'
190
+ return nil if input.downcase == "q"
180
191
 
181
192
  index = input.to_i
182
193
 
183
- unless valid_index?(index, results['resultCount'])
194
+ unless valid_index?(index, results["resultCount"])
184
195
  warn "❌ Invalid selection."
185
196
  return nil
186
197
  end
187
198
 
188
- selected = results['results'][index]
189
- puts "\n✅ Selected: #{selected['trackCensoredName']}"
199
+ selected = results["results"][index]
200
+ puts "\n✅ Selected: #{selected["trackCensoredName"]}"
190
201
  selected
191
202
  end
192
203
 
@@ -207,7 +218,7 @@ module AppCask
207
218
  DOWNLOAD_MODES[input]
208
219
  end
209
220
 
210
- def download_icon(app_info, country)
221
+ def download_icon(app_info, _country)
211
222
  puts "\n🎨 Downloading app icon\n"
212
223
 
213
224
  size_info = select_icon_size
@@ -221,23 +232,21 @@ module AppCask
221
232
  end
222
233
 
223
234
  # If 1024x1024 is selected, replace 512 with 1024 in the URL
224
- if size_info[:display].include?('1024')
225
- artwork_url = artwork_url.gsub('512x512', '1024x1024')
226
- end
235
+ artwork_url = artwork_url.gsub("512x512", "1024x1024") if size_info[:display].include?("1024")
227
236
 
228
237
  puts "\n⬇️ Downloading icon..."
229
238
 
230
- download_dir = create_app_directory(app_info, 'icons')
231
- file_name = "icon-#{size_info[:display].split(' ').first}"
239
+ download_dir = create_app_directory(app_info, "icons")
240
+ file_name = "icon-#{size_info[:display].split.first}"
232
241
 
233
242
  download_image(artwork_url, download_dir, file_name)
234
243
  end
235
244
 
236
- def download_screenshots(app_info, country)
245
+ def download_screenshots(app_info, _country)
237
246
  puts "\n📸 Downloading app screenshots\n"
238
247
 
239
- iphone_urls = app_info['screenshotUrls'] || []
240
- ipad_urls = app_info['ipadScreenshotUrls'] || []
248
+ iphone_urls = app_info["screenshotUrls"] || []
249
+ ipad_urls = app_info["ipadScreenshotUrls"] || []
241
250
 
242
251
  if iphone_urls.empty? && ipad_urls.empty?
243
252
  puts "❌ This app does not provide any screenshots."
@@ -250,25 +259,25 @@ module AppCask
250
259
 
251
260
  print "\nWhich device screenshots would you like to download? (iphone/ipad/all, default: all): "
252
261
  device = $stdin.gets.chomp.strip.downcase
253
- device = 'all' if device.empty?
262
+ device = "all" if device.empty?
254
263
 
255
- download_dir = create_app_directory(app_info, 'screenshots')
264
+ download_dir = create_app_directory(app_info, "screenshots")
256
265
 
257
266
  case device
258
- when 'iphone'
259
- download_screenshot_set(iphone_urls, download_dir, 'iPhone')
260
- when 'ipad'
261
- download_screenshot_set(ipad_urls, download_dir, 'iPad')
267
+ when "iphone"
268
+ download_screenshot_set(iphone_urls, download_dir, "iPhone")
269
+ when "ipad"
270
+ download_screenshot_set(ipad_urls, download_dir, "iPad")
262
271
  else
263
- download_screenshot_set(iphone_urls, download_dir, 'iPhone') unless iphone_urls.empty?
264
- download_screenshot_set(ipad_urls, download_dir, 'iPad') unless ipad_urls.empty?
272
+ download_screenshot_set(iphone_urls, download_dir, "iPhone") unless iphone_urls.empty?
273
+ download_screenshot_set(ipad_urls, download_dir, "iPad") unless ipad_urls.empty?
265
274
  end
266
275
  end
267
276
 
268
- def download_description(app_info, country)
277
+ def download_description(app_info, _country)
269
278
  puts "\n📝 Saving app information\n"
270
279
 
271
- download_dir = create_app_directory(app_info, 'info')
280
+ download_dir = create_app_directory(app_info, "info")
272
281
 
273
282
  # Generate detailed text information
274
283
  info_text = generate_app_info_text(app_info)
@@ -278,16 +287,16 @@ module AppCask
278
287
 
279
288
  # Save as TXT
280
289
  txt_file = File.join(download_dir, "app_info.txt")
281
- File.open(txt_file, 'w:UTF-8') { |f| f.write(info_text) }
290
+ File.open(txt_file, "w:UTF-8") { |f| f.write(info_text) }
282
291
 
283
292
  # Save as JSON
284
293
  json_file = File.join(download_dir, "app_info.json")
285
- File.open(json_file, 'w:UTF-8') { |f| f.write(JSON.pretty_generate(info_json)) }
294
+ File.open(json_file, "w:UTF-8") { |f| f.write(JSON.pretty_generate(info_json)) }
286
295
 
287
296
  # Save as Markdown
288
297
  md_file = File.join(download_dir, "README.md")
289
298
  md_text = generate_app_info_markdown(app_info)
290
- File.open(md_file, 'w:UTF-8') { |f| f.write(md_text) }
299
+ File.open(md_file, "w:UTF-8") { |f| f.write(md_text) }
291
300
 
292
301
  puts "✨ App information saved successfully!"
293
302
  puts "📁 Directory: #{download_dir}"
@@ -296,48 +305,48 @@ module AppCask
296
305
  puts " - README.md (Markdown format)"
297
306
 
298
307
  # On macOS, ask whether to open the directory
299
- if RUBY_PLATFORM.include?('darwin')
300
- print "\nOpen the folder now? (y/n): "
301
- response = $stdin.gets.chomp.strip.downcase
302
- system("open '#{download_dir}'") if response == 'y'
303
- end
308
+ return unless RUBY_PLATFORM.include?("darwin")
309
+
310
+ print "\nOpen the folder now? (y/n): "
311
+ response = $stdin.gets.chomp.strip.downcase
312
+ system("open '#{download_dir}'") if response == "y"
304
313
  end
305
314
 
306
- def download_all(app_info, country)
315
+ def download_all(app_info, _country)
307
316
  puts "\n📦 Downloading full package (icons + screenshots + app info)\n"
308
317
 
309
318
  base_dir = create_app_directory(app_info)
310
319
 
311
320
  # 1. Download all icon sizes
312
321
  puts "\n[1/3] 📥 Downloading icons..."
313
- icon_dir = File.join(base_dir, 'icons')
322
+ icon_dir = File.join(base_dir, "icons")
314
323
  FileUtils.mkdir_p(icon_dir)
315
324
 
316
- ICON_SIZES.each do |_key, size_info|
325
+ ICON_SIZES.each_value do |size_info|
317
326
  artwork_url = app_info[size_info[:key]]
318
327
  next unless artwork_url
319
328
 
320
329
  url =
321
- if size_info[:display].include?('1024')
322
- artwork_url.gsub('512x512', '1024x1024')
330
+ if size_info[:display].include?("1024")
331
+ artwork_url.gsub("512x512", "1024x1024")
323
332
  else
324
333
  artwork_url
325
334
  end
326
335
 
327
- filename = "icon-#{size_info[:display].split(' ').first}"
336
+ filename = "icon-#{size_info[:display].split.first}"
328
337
  download_image(url, icon_dir, filename, silent: true)
329
338
  end
330
339
 
331
340
  # 2. Download all screenshots
332
341
  puts "\n[2/3] 📥 Downloading screenshots..."
333
- screenshot_dir = File.join(base_dir, 'screenshots')
342
+ screenshot_dir = File.join(base_dir, "screenshots")
334
343
  FileUtils.mkdir_p(screenshot_dir)
335
344
 
336
- iphone_urls = app_info['screenshotUrls'] || []
337
- ipad_urls = app_info['ipadScreenshotUrls'] || []
345
+ iphone_urls = app_info["screenshotUrls"] || []
346
+ ipad_urls = app_info["ipadScreenshotUrls"] || []
338
347
 
339
- download_screenshot_set(iphone_urls, screenshot_dir, 'iPhone', silent: true) unless iphone_urls.empty?
340
- download_screenshot_set(ipad_urls, screenshot_dir, 'iPad', silent: true) unless ipad_urls.empty?
348
+ download_screenshot_set(iphone_urls, screenshot_dir, "iPhone", silent: true) unless iphone_urls.empty?
349
+ download_screenshot_set(ipad_urls, screenshot_dir, "iPad", silent: true) unless ipad_urls.empty?
341
350
 
342
351
  # 3. Save app information
343
352
  puts "\n[3/3] 📥 Saving app information..."
@@ -346,9 +355,9 @@ module AppCask
346
355
  info_json = generate_app_info_json(app_info)
347
356
  info_md = generate_app_info_markdown(app_info)
348
357
 
349
- File.open(File.join(base_dir, 'app_info.txt'), 'w:UTF-8') { |f| f.write(info_text) }
350
- File.open(File.join(base_dir, 'app_info.json'), 'w:UTF-8') { |f| f.write(JSON.pretty_generate(info_json)) }
351
- File.open(File.join(base_dir, 'README.md'), 'w:UTF-8') { |f| f.write(info_md) }
358
+ File.open(File.join(base_dir, "app_info.txt"), "w:UTF-8") { |f| f.write(info_text) }
359
+ File.open(File.join(base_dir, "app_info.json"), "w:UTF-8") { |f| f.write(JSON.pretty_generate(info_json)) }
360
+ File.open(File.join(base_dir, "README.md"), "w:UTF-8") { |f| f.write(info_md) }
352
361
 
353
362
  puts "\n✨ Download completed!"
354
363
  puts "📁 All files saved to: #{base_dir}"
@@ -361,18 +370,18 @@ module AppCask
361
370
  puts " └── README.md (Markdown)"
362
371
 
363
372
  # Statistics
364
- files = Dir.glob(File.join(base_dir, '**', '*')).select { |f| File.file?(f) }
373
+ files = Dir.glob(File.join(base_dir, "**", "*")).select { |f| File.file?(f) }
365
374
  total_files = files.count
366
375
  total_size = (files.sum { |f| File.size(f) } / 1024.0 / 1024.0).round(2)
367
376
 
368
377
  puts "\n📊 Summary: #{total_files} files, total size #{total_size} MB"
369
378
 
370
379
  # On macOS, ask whether to open the directory
371
- if RUBY_PLATFORM.include?('darwin')
372
- print "\nOpen the folder now? (y/n): "
373
- response = $stdin.gets.chomp.strip.downcase
374
- system("open '#{base_dir}'") if response == 'y'
375
- end
380
+ return unless RUBY_PLATFORM.include?("darwin")
381
+
382
+ print "\nOpen the folder now? (y/n): "
383
+ response = $stdin.gets.chomp.strip.downcase
384
+ system("open '#{base_dir}'") if response == "y"
376
385
  end
377
386
 
378
387
  def select_icon_size
@@ -381,7 +390,7 @@ module AppCask
381
390
 
382
391
  print "\nSelect (0-3, default: 2): "
383
392
  input = $stdin.gets.chomp.strip
384
- input = '2' if input.empty?
393
+ input = "2" if input.empty?
385
394
 
386
395
  unless ICON_SIZES.key?(input)
387
396
  warn "❌ Invalid selection."
@@ -417,7 +426,7 @@ module AppCask
417
426
  filename = File.join(dir, "#{basename}.#{ext}")
418
427
  filename = get_unique_filename(filename)
419
428
 
420
- File.open(filename, 'wb') { |f| f.write(content) }
429
+ File.binwrite(filename, content)
421
430
 
422
431
  unless silent
423
432
  file_size = (File.size(filename) / 1024.0).round(2)
@@ -430,99 +439,99 @@ module AppCask
430
439
 
431
440
  def generate_app_info_text(app_info)
432
441
  <<~INFO
433
- ===================================================
434
- APPLICATION DETAILS
435
- ===================================================
436
-
437
- [Basic Information]
438
- App Name: #{app_info['trackCensoredName']}
439
- App ID: #{app_info['trackId']}
440
- Bundle ID: #{app_info['bundleId']}
441
- Developer: #{app_info['artistName']}
442
- Developer ID: #{app_info['artistId']}
443
-
444
- [Version Information]
445
- Current Version: #{app_info['version']}
446
- File Size: #{(app_info['fileSizeBytes'].to_i / 1024.0 / 1024.0).round(2)} MB
447
- Minimum OS Requirement: iOS #{app_info['minimumOsVersion']}
448
- Supported Devices: #{app_info['supportedDevices']&.join(', ') || 'N/A'}
449
-
450
- [Pricing & Ratings]
451
- Price: #{app_info['formattedPrice'] || app_info['price']}
452
- Currency: #{app_info['currency']}
453
- Rating: #{app_info['averageUserRating'] || 'N/A'} (#{app_info['userRatingCount'] || 0} ratings)
454
-
455
- [Categories]
456
- Primary Category: #{app_info['primaryGenreName']}
457
- All Categories: #{app_info['genres']&.join(', ')}
458
-
459
- [Release Information]
460
- First Released: #{app_info['releaseDate']}
461
- Last Updated: #{app_info['currentVersionReleaseDate']}
462
- Content Rating: #{app_info['contentAdvisoryRating']}
463
-
464
- [Description]
465
- #{app_info['description']}
466
-
467
- [Release Notes]
468
- #{app_info['releaseNotes'] || 'No release notes provided.'}
469
-
470
- [Developer Information]
471
- Developer Website: #{app_info['sellerUrl']}
472
-
473
- [Store Link]
474
- App Store: #{app_info['trackViewUrl']}
475
-
476
- ===================================================
477
- Exported At: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}
478
- ===================================================
479
- INFO
442
+ ===================================================
443
+ APPLICATION DETAILS
444
+ ===================================================
445
+
446
+ [Basic Information]
447
+ App Name: #{app_info["trackCensoredName"]}
448
+ App ID: #{app_info["trackId"]}
449
+ Bundle ID: #{app_info["bundleId"]}
450
+ Developer: #{app_info["artistName"]}
451
+ Developer ID: #{app_info["artistId"]}
452
+
453
+ [Version Information]
454
+ Current Version: #{app_info["version"]}
455
+ File Size: #{(app_info["fileSizeBytes"].to_i / 1024.0 / 1024.0).round(2)} MB
456
+ Minimum OS Requirement: iOS #{app_info["minimumOsVersion"]}
457
+ Supported Devices: #{app_info["supportedDevices"]&.join(", ") || "N/A"}
458
+
459
+ [Pricing & Ratings]
460
+ Price: #{app_info["formattedPrice"] || app_info["price"]}
461
+ Currency: #{app_info["currency"]}
462
+ Rating: #{app_info["averageUserRating"] || "N/A"} (#{app_info["userRatingCount"] || 0} ratings)
463
+
464
+ [Categories]
465
+ Primary Category: #{app_info["primaryGenreName"]}
466
+ All Categories: #{app_info["genres"]&.join(", ")}
467
+
468
+ [Release Information]
469
+ First Released: #{app_info["releaseDate"]}
470
+ Last Updated: #{app_info["currentVersionReleaseDate"]}
471
+ Content Rating: #{app_info["contentAdvisoryRating"]}
472
+
473
+ [Description]
474
+ #{app_info["description"]}
475
+
476
+ [Release Notes]
477
+ #{app_info["releaseNotes"] || "No release notes provided."}
478
+
479
+ [Developer Information]
480
+ Developer Website: #{app_info["sellerUrl"]}
481
+
482
+ [Store Link]
483
+ App Store: #{app_info["trackViewUrl"]}
484
+
485
+ ===================================================
486
+ Exported At: #{Time.now.strftime("%Y-%m-%d %H:%M:%S")}
487
+ ===================================================
488
+ INFO
480
489
  end
481
490
 
482
491
  def generate_app_info_json(app_info)
483
492
  {
484
493
  basic: {
485
- name: app_info['trackCensoredName'],
486
- app_id: app_info['trackId'],
487
- bundle_id: app_info['bundleId'],
488
- developer: app_info['artistName'],
489
- developer_id: app_info['artistId']
494
+ name: app_info["trackCensoredName"],
495
+ app_id: app_info["trackId"],
496
+ bundle_id: app_info["bundleId"],
497
+ developer: app_info["artistName"],
498
+ developer_id: app_info["artistId"]
490
499
  },
491
500
  version: {
492
- current_version: app_info['version'],
493
- file_size_bytes: app_info['fileSizeBytes'],
494
- file_size_mb: (app_info['fileSizeBytes'].to_i / 1024.0 / 1024.0).round(2),
495
- minimum_os_version: app_info['minimumOsVersion'],
496
- supported_devices: app_info['supportedDevices']
501
+ current_version: app_info["version"],
502
+ file_size_bytes: app_info["fileSizeBytes"],
503
+ file_size_mb: (app_info["fileSizeBytes"].to_i / 1024.0 / 1024.0).round(2),
504
+ minimum_os_version: app_info["minimumOsVersion"],
505
+ supported_devices: app_info["supportedDevices"]
497
506
  },
498
507
  pricing: {
499
- price: app_info['price'],
500
- formatted_price: app_info['formattedPrice'],
501
- currency: app_info['currency']
508
+ price: app_info["price"],
509
+ formatted_price: app_info["formattedPrice"],
510
+ currency: app_info["currency"]
502
511
  },
503
512
  ratings: {
504
- average_rating: app_info['averageUserRating'],
505
- rating_count: app_info['userRatingCount'],
506
- rating_count_current_version: app_info['userRatingCountForCurrentVersion']
513
+ average_rating: app_info["averageUserRating"],
514
+ rating_count: app_info["userRatingCount"],
515
+ rating_count_current_version: app_info["userRatingCountForCurrentVersion"]
507
516
  },
508
517
  categories: {
509
- primary_genre: app_info['primaryGenreName'],
510
- all_genres: app_info['genres']
518
+ primary_genre: app_info["primaryGenreName"],
519
+ all_genres: app_info["genres"]
511
520
  },
512
521
  release: {
513
- release_date: app_info['releaseDate'],
514
- current_version_release_date: app_info['currentVersionReleaseDate'],
515
- content_rating: app_info['contentAdvisoryRating']
522
+ release_date: app_info["releaseDate"],
523
+ current_version_release_date: app_info["currentVersionReleaseDate"],
524
+ content_rating: app_info["contentAdvisoryRating"]
516
525
  },
517
- description: app_info['description'],
518
- release_notes: app_info['releaseNotes'],
526
+ description: app_info["description"],
527
+ release_notes: app_info["releaseNotes"],
519
528
  urls: {
520
- app_store: app_info['trackViewUrl'],
521
- developer_website: app_info['sellerUrl']
529
+ app_store: app_info["trackViewUrl"],
530
+ developer_website: app_info["sellerUrl"]
522
531
  },
523
532
  screenshots: {
524
- iphone: app_info['screenshotUrls'],
525
- ipad: app_info['ipadScreenshotUrls']
533
+ iphone: app_info["screenshotUrls"],
534
+ ipad: app_info["ipadScreenshotUrls"]
526
535
  },
527
536
  exported_at: Time.now.iso8601
528
537
  }
@@ -530,76 +539,77 @@ module AppCask
530
539
 
531
540
  def generate_app_info_markdown(app_info)
532
541
  <<~MARKDOWN
533
- # #{app_info['trackCensoredName']}
542
+ # #{app_info["trackCensoredName"]}
534
543
 
535
- > Developer: #{app_info['artistName']}
544
+ > Developer: #{app_info["artistName"]}
536
545
 
537
- ![Rating](https://img.shields.io/badge/Rating-#{app_info['averageUserRating'] || 'N/A'}-blue)
538
- ![Version](https://img.shields.io/badge/Version-#{app_info['version']}-green)
539
- ![Price](https://img.shields.io/badge/Price-#{(app_info['formattedPrice'] || app_info['price']).gsub('-', '--')}-orange)
546
+ ![Rating](https://img.shields.io/badge/Rating-#{app_info["averageUserRating"] || "N/A"}-blue)
547
+ ![Version](https://img.shields.io/badge/Version-#{app_info["version"]}-green)
548
+ ![Price](https://img.shields.io/badge/Price-#{(app_info["formattedPrice"] || app_info["price"]).gsub("-", "--")}-orange)
540
549
 
541
- ## 📱 Basic Information
550
+ ## 📱 Basic Information
542
551
 
543
- | Item | Value |
544
- |------|-------|
545
- | App ID | #{app_info['trackId']} |
546
- | Bundle ID | #{app_info['bundleId']} |
547
- | Developer | #{app_info['artistName']} |
548
- | Primary Category | #{app_info['primaryGenreName']} |
549
- | Content Rating | #{app_info['contentAdvisoryRating']} |
552
+ | Item | Value |
553
+ |------|-------|
554
+ | App ID | #{app_info["trackId"]} |
555
+ | Bundle ID | #{app_info["bundleId"]} |
556
+ | Developer | #{app_info["artistName"]} |
557
+ | Primary Category | #{app_info["primaryGenreName"]} |
558
+ | Content Rating | #{app_info["contentAdvisoryRating"]} |
550
559
 
551
- ## 📊 Version Information
560
+ ## 📊 Version Information
552
561
 
553
- - **Current Version**: #{app_info['version']}
554
- - **File Size**: #{(app_info['fileSizeBytes'].to_i / 1024.0 / 1024.0).round(2)} MB
555
- - **Minimum OS**: iOS #{app_info['minimumOsVersion']}
556
- - **Release Date**: #{app_info['currentVersionReleaseDate']}
562
+ - **Current Version**: #{app_info["version"]}
563
+ - **File Size**: #{(app_info["fileSizeBytes"].to_i / 1024.0 / 1024.0).round(2)} MB
564
+ - **Minimum OS**: iOS #{app_info["minimumOsVersion"]}
565
+ - **Release Date**: #{app_info["currentVersionReleaseDate"]}
557
566
 
558
- ## ⭐ Ratings
567
+ ## ⭐ Ratings
559
568
 
560
- - **Average Rating**: #{app_info['averageUserRating'] || 'N/A'} / 5.0
561
- - **Total Ratings**: #{app_info['userRatingCount'] || 0}
569
+ - **Average Rating**: #{app_info["averageUserRating"] || "N/A"} / 5.0
570
+ - **Total Ratings**: #{app_info["userRatingCount"] || 0}
562
571
 
563
- ## 📝 Description
572
+ ## 📝 Description
564
573
 
565
- #{app_info['description']}
574
+ #{app_info["description"]}
566
575
 
567
- ## 🆕 What's New
576
+ ## 🆕 What's New
568
577
 
569
- #{app_info['releaseNotes'] || 'No release notes provided.'}
578
+ #{app_info["releaseNotes"] || "No release notes provided."}
570
579
 
571
- ## 🔗 Links
580
+ ## 🔗 Links
572
581
 
573
- - [App Store](#{app_info['trackViewUrl']})
574
- - [Developer Website](#{app_info['sellerUrl']})
582
+ - [App Store](#{app_info["trackViewUrl"]})
583
+ - [Developer Website](#{app_info["sellerUrl"]})
575
584
 
576
- ---
585
+ ---
577
586
 
578
- *Exported at: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}*
579
- MARKDOWN
587
+ *Exported at: #{Time.now.strftime("%Y-%m-%d %H:%M:%S")}*
588
+ MARKDOWN
580
589
  end
581
590
 
582
591
  def detect_image_extension(content)
583
- return 'png' if content[0..7] == "\x89PNG\r\n\x1A\n".b
584
- return 'jpg' if content[0..1] == "\xFF\xD8".b
585
- return 'gif' if content[0..2] == "GIF".b
586
- return 'webp' if content[8..11] == "WEBP".b
587
- 'jpg'
592
+ return "png" if content[0..7] == "\x89PNG\r\n\x1A\n".b
593
+ return "jpg" if content[0..1] == "\xFF\xD8".b
594
+ return "gif" if content[0..2] == "GIF".b
595
+ return "webp" if content[8..11] == "WEBP".b
596
+
597
+ "jpg"
588
598
  end
589
599
 
590
600
  def sanitize_filename(filename)
591
- filename.gsub(/[\/\\:*?"<>|]/, '_').strip
601
+ filename.gsub(%r{[/\\:*?"<>|]}, "_").strip
592
602
  end
593
603
 
594
604
  def create_app_directory(app_info, subdir = nil)
595
- app_name = sanitize_filename(app_info['trackCensoredName'])
605
+ app_name = sanitize_filename(app_info["trackCensoredName"])
596
606
 
597
- desktop = File.join(Dir.home, 'Desktop')
598
- if Dir.exist?(desktop)
599
- base_dir = File.join(desktop, 'AppCask Downloads', app_name)
600
- else
601
- base_dir = File.join(Dir.pwd, 'AppCask Downloads', app_name)
602
- end
607
+ desktop = File.join(Dir.home, "Desktop")
608
+ base_dir = if Dir.exist?(desktop)
609
+ File.join(desktop, "AppCask Downloads", app_name)
610
+ else
611
+ File.join(Dir.pwd, "AppCask Downloads", app_name)
612
+ end
603
613
 
604
614
  final_dir = subdir ? File.join(base_dir, subdir) : base_dir
605
615
  FileUtils.mkdir_p(final_dir)
@@ -610,13 +620,14 @@ module AppCask
610
620
  return filename unless File.exist?(filename)
611
621
 
612
622
  dir = File.dirname(filename)
613
- basename = File.basename(filename, '.*')
623
+ basename = File.basename(filename, ".*")
614
624
  ext = File.extname(filename)
615
625
 
616
626
  counter = 1
617
627
  loop do
618
628
  new_filename = File.join(dir, "#{basename}_#{counter}#{ext}")
619
629
  return new_filename unless File.exist?(new_filename)
630
+
620
631
  counter += 1
621
632
  end
622
633
  end
metadata CHANGED
@@ -1,14 +1,56 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: appcask
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Billow Wang
8
8
  bindir: exe
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
- dependencies: []
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: CFPropertyList
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '4.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '4.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: icns
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '0.2'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '0.2'
40
+ - !ruby/object:Gem::Dependency
41
+ name: mini_magick
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '4.0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '4.0'
12
54
  description: AppCask is a CLI tool for downloading App Store icons, screenshots and
13
55
  metadata.
14
56
  email:
@@ -33,6 +75,7 @@ files:
33
75
  - examples/batch_download.rb
34
76
  - exe/appcask
35
77
  - lib/appcask.rb
78
+ - lib/appcask/local_app.rb
36
79
  - lib/appcask/version.rb
37
80
  - sig/appcask.rbs
38
81
  homepage: https://github.com/gamepunk/appcask
@@ -43,6 +86,7 @@ metadata:
43
86
  homepage_uri: https://github.com/gamepunk/appcask
44
87
  source_code_uri: https://github.com/gamepunk/appcask
45
88
  changelog_uri: https://github.com/gamepunk/appcask/blob/main/CHANGELOG.md
89
+ rubygems_mfa_required: 'true'
46
90
  rdoc_options: []
47
91
  require_paths:
48
92
  - lib
@@ -50,14 +94,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
50
94
  requirements:
51
95
  - - ">="
52
96
  - !ruby/object:Gem::Version
53
- version: 3.2.0
97
+ version: 3.4.10
54
98
  required_rubygems_version: !ruby/object:Gem::Requirement
55
99
  requirements:
56
100
  - - ">="
57
101
  - !ruby/object:Gem::Version
58
102
  version: '0'
59
103
  requirements: []
60
- rubygems_version: 4.0.3
104
+ rubygems_version: 3.6.9
61
105
  specification_version: 4
62
106
  summary: Download App Store app assets easily
63
107
  test_files: []