unmagic-icon 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 78dfe537a660edce4ede922cf790a6b2a317c24f54143e356674925113f94bb8
4
+ data.tar.gz: 513b08d32b96b7f4da9177c814e44b8a1f85a9cb8be54d75b6e4bffb166b6a63
5
+ SHA512:
6
+ metadata.gz: 3f57f44dd2680988425b6c2e8fc195f2eca2d4647305ba9250ca5220e7b6cc6a807b641927e820ba7bb0a651ab016bf15c8e2e5165350a9e68230822a52fd417
7
+ data.tar.gz: 557cb9b7886ef6d7d0bd7ce28c40815c6b0442dd7a8ae6c79d0033b697044a8e6c930b4bd23a3d2338e18cf64ef447a8a06633b91403d70fe75dbeb25c35fef8
data/CHANGELOG.md ADDED
@@ -0,0 +1,37 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0] - 2026-06-21
11
+
12
+ ### Added
13
+ - Initial release
14
+ - `Unmagic::Icon` for loading an SVG asset and rendering it inline as
15
+ html-safe markup, with a `unmagic-icon` class, a `data-unmagic-icon` marker,
16
+ and caller-supplied attributes (`class`, `aria-*`, `data-*`, etc.) merged onto
17
+ the `<svg>` element; rendered markup is cached when no options are passed
18
+ - `Unmagic::Icon.find` to resolve a `library/name` reference (tolerating the
19
+ emoji-style `:library/name:` decoration) to an icon
20
+ - `Unmagic::Icon::Library` with on-disk icon discovery and an optional
21
+ `manifest.json` declaring a `default` icon and `aliases` (exact strings plus
22
+ glob patterns, matched longest-first)
23
+ - `Unmagic::Icon::Library::Registry` that builds libraries from configured
24
+ paths, supporting nested library names and a `prefix:path` syntax
25
+ - `Unmagic::Icon::Configuration` with `paths`, `libraries`, and a `download_path`
26
+ defaulting to `vendor/icons` under Rails
27
+ - Rails integration via `Unmagic::Icon::Engine`: registers the `unmagic_icon`
28
+ view helper and configures default icon paths (including engine-provided icons)
29
+ - `Unmagic::Icon::Library::Source`, a DSL-driven downloader for popular icon
30
+ sets — Heroicons, Devicons, Feather, Tabler, Lucide, Simple Icons, Material
31
+ File Icons, and Silk
32
+ - Rake tasks `unmagic:icons:install` (downloads the configured libraries, and
33
+ hooks into `assets:precompile`) and `unmagic:icons:download[library]`
34
+ - `Unmagic::Icon::Web`, a Rack app for browsing the configured icon libraries
35
+
36
+ [Unreleased]: https://github.com/unreasonable-magic/unmagic-icon/compare/v0.1.0...HEAD
37
+ [0.1.0]: https://github.com/unreasonable-magic/unmagic-icon/releases/tag/v0.1.0
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Keith Pitt
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,156 @@
1
+ # Unmagic::Icon
2
+
3
+ Inline SVG icons for Rails, with downloadable icon libraries.
4
+
5
+ ## Features
6
+
7
+ - Render SVG icons inline as html-safe markup, straight into your views
8
+ - Resolve icons through `library/name` references, with aliases and glob patterns
9
+ - Per-library `manifest.json` for a default icon and alias mappings
10
+ - Caller-controlled attributes (`class`, `aria-*`, `data-*`, …) merged onto the `<svg>`
11
+ - Rails engine that registers a `unmagic_icon` view helper and sensible icon paths
12
+ - One-command downloads for popular icon sets (Heroicons, Lucide, Tabler, Feather, and more)
13
+ - A Rack app for browsing your configured libraries
14
+
15
+ ## Installation
16
+
17
+ Add to your Gemfile:
18
+
19
+ ```ruby
20
+ gem 'unmagic-icon'
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ ### In Rails views
26
+
27
+ The Rails engine registers a `unmagic_icon` helper and looks for icons in
28
+ `vendor/icons` and `app/assets/icons` (plus any engine-provided
29
+ `app/assets/icons`).
30
+
31
+ ```erb
32
+ <%= unmagic_icon "heroicons/24-outline/star" %>
33
+ <%= unmagic_icon "lucide/check", class: "size-5 text-green-600" %>
34
+ <%= unmagic_icon "feather/menu", "aria-hidden": "true" %>
35
+ ```
36
+
37
+ An icon reference is `library/name`. The library is the directory the icon
38
+ lives in (relative to a configured path), and the name is the SVG file without
39
+ its extension. Nested libraries work too — `heroicons/24-outline/star` is the
40
+ `star` icon in the `heroicons/24-outline` library.
41
+
42
+ The rendered `<svg>` always gets a `unmagic-icon` class and a
43
+ `data-unmagic-icon="library/name"` marker. Any caller class is appended, and
44
+ every other option is merged verbatim as an attribute — so accessibility
45
+ (`aria-hidden`, `aria-label`, `role`), `id`, and `data-*` are entirely up to you.
46
+
47
+ ### Outside Rails
48
+
49
+ Configure the paths to look in, then resolve and render icons:
50
+
51
+ ```ruby
52
+ require "unmagic_icon"
53
+
54
+ Unmagic::Icon.init do |config|
55
+ config.paths = ["path/to/icons"]
56
+ end
57
+
58
+ icon = Unmagic::Icon.find("lucide/check")
59
+ icon.to_svg # => html-safe "<svg …>…</svg>"
60
+ icon.to_svg(class: "size-5") # => with an extra class
61
+ icon.as_json # => { name: "lucide/check", svg: "<svg …>" }
62
+ ```
63
+
64
+ References tolerate the emoji-style colon decoration, so `":lucide/check:"`
65
+ resolves the same as `"lucide/check"`.
66
+
67
+ ### Aliases and defaults
68
+
69
+ Drop a `manifest.json` into a library directory to declare a default icon and
70
+ aliases. Aliases can be exact names or glob patterns (patterns are matched
71
+ longest-first, so the most specific wins):
72
+
73
+ ```json
74
+ {
75
+ "default": "file",
76
+ "aliases": {
77
+ "*.rb": "ruby",
78
+ "*.test.tsx": "test",
79
+ "*.tsx": "react"
80
+ }
81
+ }
82
+ ```
83
+
84
+ ```ruby
85
+ Unmagic::Icon.find("material/app.rb").name # => "material/ruby"
86
+ Unmagic::Icon.find("material/unknown").name # => "material/file" (the default)
87
+ ```
88
+
89
+ ### Downloading icon libraries
90
+
91
+ The gem can fetch popular icon sets for you. List the ones you want in an
92
+ initializer:
93
+
94
+ ```ruby
95
+ # config/initializers/unmagic_icon.rb
96
+ Unmagic::Icon.configure do |config|
97
+ config.libraries = [:heroicons, :lucide, :feather]
98
+ end
99
+ ```
100
+
101
+ Then download them (this also runs automatically during `assets:precompile`):
102
+
103
+ ```bash
104
+ bin/rails unmagic:icons:install
105
+ ```
106
+
107
+ Or grab a single library on demand:
108
+
109
+ ```bash
110
+ bin/rails unmagic:icons:download[heroicons]
111
+ bin/rails unmagic:icons:download[silk,force] # re-download even if present
112
+ ```
113
+
114
+ Libraries are written to `config.download_path` (defaults to `vendor/icons`
115
+ under Rails), which keeps downloaded sets out of the asset pipeline — icons are
116
+ inlined via `File.read`, never served as assets.
117
+
118
+ Available libraries:
119
+
120
+ | Key | Title | Description |
121
+ | --------------------- | ------------------- | ---------------------------------------------------------------------------- |
122
+ | `heroicons` | Heroicons | Beautiful hand-crafted SVG icons by the makers of Tailwind CSS |
123
+ | `devicons` | Devicons | Icons representing programming languages, designing & development tools |
124
+ | `feather` | Feather Icons | Simply beautiful open source icons |
125
+ | `tabler` | Tabler Icons | Over 5400 free SVG icons |
126
+ | `lucide` | Lucide Icons | Beautiful & consistent icons |
127
+ | `simple-icons` | Simple Icons | SVG icons for popular brands |
128
+ | `material-file-icons` | Material File Icons | Material Design file icons with filename/extension aliases |
129
+ | `silk` | Silk Icons Scalable | The classic silk icon set recreated as SVG |
130
+
131
+ ### Browsing icons
132
+
133
+ `Unmagic::Icon::Web` is a small Rack app for browsing your configured
134
+ libraries. Mount it in your routes:
135
+
136
+ ```ruby
137
+ mount Unmagic::Icon::Web => "/unmagic/icons"
138
+ ```
139
+
140
+ ## Development
141
+
142
+ After checking out the repo, install dependencies and run the tests:
143
+
144
+ ```bash
145
+ bundle install
146
+ bundle exec rake spec
147
+ ```
148
+
149
+ ## Contributing
150
+
151
+ Bug reports and pull requests are welcome on GitHub at
152
+ https://github.com/unreasonable-magic/unmagic-icon.
153
+
154
+ ## License
155
+
156
+ Released under the [MIT License](LICENSE).
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :unmagic do
4
+ namespace :icons do
5
+ desc "Download popular icon libraries"
6
+ task :download, %i[library force] => :environment do |_task, args|
7
+ require_relative "../../../unmagic/icon/library/source"
8
+
9
+ library = args[:library]&.strip
10
+ force = args[:force] == "force"
11
+
12
+ if library.nil? || library.empty?
13
+ puts "Error: Please specify a library to download"
14
+ puts "Available libraries:"
15
+ Unmagic::Icon::Library::Source.all.each do |source|
16
+ puts " #{source.key.to_s.ljust(20)} - #{source.description}"
17
+ end
18
+ puts "\nUsage: rails unmagic:icons:download[heroicons]"
19
+ puts " rails unmagic:icons:download[silk,force]"
20
+ exit 1
21
+ end
22
+
23
+ Unmagic::Icon::Library::Source.find(library).new.download(force: force)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :unmagic do
4
+ namespace :icons do
5
+ desc "Download every icon library listed in Unmagic::Icon.configuration.libraries"
6
+ task install: :environment do
7
+ require_relative "../../../unmagic/icon/library/source"
8
+
9
+ libraries = Array(Unmagic::Icon.configuration.libraries)
10
+
11
+ if libraries.empty?
12
+ puts "No icon libraries configured. Set Unmagic::Icon.configuration.libraries in an initializer."
13
+ else
14
+ libraries.each { |name| Unmagic::Icon::Library::Source.find(name).new.download }
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ # Fetch the configured icon libraries as part of asset precompilation, so they
21
+ # arrive through normal Rails operations (e.g. the production image build) rather
22
+ # than a separate manual step. The download skips libraries already present, and
23
+ # it's a no-op when none are configured. The prerequisite resolves at invocation
24
+ # time, so this works regardless of when assets:precompile is defined.
25
+ task "assets:precompile" => "unmagic:icons:install"
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Unmagic
4
+ class Icon
5
+ # View helper, registered into ActionView by the engine. Mirrors the shape a
6
+ # future emoji pack would use (`unmagic_emoji`), so both share one rendering
7
+ # surface.
8
+ module ActionViewHelpers
9
+ def unmagic_icon(reference, **options)
10
+ Unmagic::Icon.find(reference).render(**options)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+
5
+ module Unmagic
6
+ class Icon
7
+ class Configuration
8
+ attr_accessor :paths, :libraries
9
+ attr_writer :download_path
10
+
11
+ def initialize
12
+ @paths = []
13
+ @libraries = []
14
+ end
15
+
16
+ # Where `Unmagic::Icon::Library::Source` writes libraries by default.
17
+ # vendor/icons keeps downloaded sets out of the asset pipeline (Propshaft
18
+ # fingerprints app/assets/*, but the icons are inlined via File.read, never
19
+ # served), and reads as vendored third-party. Must be set explicitly when
20
+ # not running under Rails.
21
+ def download_path
22
+ @download_path ||= default_download_path
23
+ end
24
+
25
+ private
26
+
27
+ def default_download_path
28
+ return unless defined?(Rails) && Rails.respond_to?(:root) && Rails.root
29
+
30
+ Rails.root.join("vendor", "icons")
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "action_view_helpers"
4
+
5
+ module Unmagic
6
+ class Icon
7
+ class Engine < ::Rails::Engine
8
+ isolate_namespace Unmagic::Icon
9
+
10
+ initializer "unmagic_icon.helpers" do
11
+ ActiveSupport.on_load(:action_view) do
12
+ include Unmagic::Icon::ActionViewHelpers
13
+ end
14
+ end
15
+
16
+ initializer "unmagic_icon.init" do
17
+ unless Unmagic::Icon.initialized?
18
+ Unmagic::Icon.init do |config|
19
+ # vendor/icons is the default home for downloaded sets (out of the
20
+ # asset pipeline); app/assets/icons stays supported for committed,
21
+ # hand-authored icons.
22
+ paths = [
23
+ Rails.root.join("vendor", "icons").to_s,
24
+ Rails.root.join("app", "assets", "icons").to_s
25
+ ]
26
+
27
+ # Also look for icons in engines
28
+ Rails.application.railties.select do |r|
29
+ r.is_a?(Rails::Engine) && r.class != Rails::Application
30
+ end.each do |engine|
31
+ engine_path = engine.root.join("app", "assets", "icons")
32
+ next unless engine_path.exist?
33
+
34
+ prefix = engine.class.name.underscore.gsub(%r{/engine$}, "").tr("/", "_")
35
+ paths << "#{prefix}:#{engine_path}"
36
+ end
37
+
38
+ config.paths = paths
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Unmagic
4
+ class Icon
5
+ class Library
6
+ class Registry
7
+ class << self
8
+ def all
9
+ _all.values
10
+ end
11
+
12
+ def exists?(name)
13
+ !_all[name].nil?
14
+ end
15
+
16
+ def find(name)
17
+ return name if name.is_a?(Unmagic::Icon::Library)
18
+
19
+ _all[name] or
20
+ raise Unmagic::Icon::LibraryNotFoundError.new("Can't find library #{name.inspect}")
21
+ end
22
+
23
+ def reset!
24
+ @_all = nil
25
+ end
26
+
27
+ private
28
+
29
+ def _all
30
+ @_all ||= find_libraries
31
+ end
32
+
33
+ def find_libraries
34
+ libs = {}
35
+ Unmagic::Icon.configuration.paths.each do |path|
36
+ build_libraries(path: path).each do |lib|
37
+ libs[lib.name] = lib
38
+ end
39
+ end
40
+ libs
41
+ end
42
+
43
+ def build_libraries(path:)
44
+ discovered = []
45
+
46
+ # If the path has a `blah:` at the start, extract it and use it as a
47
+ # prefix for the library name
48
+ prefix, path = path.to_s.split(":", 2)
49
+ if path.blank?
50
+ path = prefix
51
+ prefix = nil
52
+ end
53
+
54
+ Dir.glob(File.join(path, "**/")).each do |dir_path|
55
+ next unless File.directory?(dir_path)
56
+
57
+ # Check if this directory has any SVG files
58
+ svg_files = Dir.glob(File.join(dir_path, "*.svg"))
59
+ next if svg_files.empty?
60
+
61
+ # Build library name from path relative to icons root
62
+ library_path = Pathname.new(dir_path)
63
+ relative = library_path.relative_path_from(path).to_s.chomp("/")
64
+
65
+ # Skip empty library names (root directory)
66
+ next if relative.empty? || relative == "."
67
+
68
+ # Add name prefix if we had one
69
+ name = prefix ? "#{prefix}:#{relative}" : relative
70
+
71
+ # Add it to our list
72
+ discovered << Unmagic::Icon::Library.new(name: name, path: library_path)
73
+ end
74
+
75
+ discovered
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Unmagic
4
+ class Icon
5
+ class Library
6
+ class Source
7
+ class Devicons < Source
8
+ key :devicons
9
+ title "Devicons"
10
+ description "Icons representing programming languages, designing & development tools"
11
+ url "https://github.com/devicons/devicon/archive/refs/tags/v2.17.0.zip"
12
+ archive :zip
13
+ extract "devicon-2.17.0/icons/**/*.svg"
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Unmagic
4
+ class Icon
5
+ class Library
6
+ class Source
7
+ class Feather < Source
8
+ key :feather
9
+ title "Feather Icons"
10
+ description "Simply beautiful open source icons"
11
+ url "https://github.com/feathericons/feather/archive/refs/tags/v4.29.1.zip"
12
+ archive :zip
13
+ extract "feather-4.29.1/icons/*.svg"
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Unmagic
4
+ class Icon
5
+ class Library
6
+ class Source
7
+ class Heroicons < Source
8
+ key :heroicons
9
+ title "Heroicons"
10
+ description "Beautiful hand-crafted SVG icons by the makers of Tailwind CSS"
11
+ url "https://github.com/tailwindlabs/heroicons/archive/refs/tags/v2.2.0.zip"
12
+ archive :zip
13
+ extract_into(
14
+ "heroicons-2.2.0/optimized/16/solid" => "16-solid",
15
+ "heroicons-2.2.0/optimized/20/solid" => "20-solid",
16
+ "heroicons-2.2.0/optimized/24/solid" => "24-solid",
17
+ "heroicons-2.2.0/optimized/24/outline" => "24-outline"
18
+ )
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Unmagic
4
+ class Icon
5
+ class Library
6
+ class Source
7
+ class Lucide < Source
8
+ key :lucide
9
+ title "Lucide Icons"
10
+ description "Beautiful & consistent icons"
11
+ url "https://github.com/lucide-icons/lucide/releases/download/1.21.0/lucide-icons-1.21.0.zip"
12
+ archive :zip
13
+ extract "icons/*.svg"
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Unmagic
4
+ class Icon
5
+ class Library
6
+ class Source
7
+ # PKief's material-icon-theme, sourced from the npm tarball so we also get
8
+ # dist/material-icons.json — the file→icon mapping — which we turn into a
9
+ # manifest.json of aliases.
10
+ class MaterialFileIcons < Source
11
+ key :"material-file-icons"
12
+ title "Material File Icons"
13
+ description "Material Design file icons with filename/extension aliases (PKief material-icon-theme)"
14
+ url "https://registry.npmjs.org/material-icon-theme/-/material-icon-theme-5.35.0.tgz"
15
+ archive :tgz
16
+ dir "material"
17
+ extract "package/icons/*.svg"
18
+
19
+ private
20
+
21
+ def write_manifest(tmpdir, target_dir)
22
+ json_path = File.join(tmpdir, "package", "dist", "material-icons.json")
23
+ return unless File.exist?(json_path)
24
+
25
+ data = JSON.parse(File.read(json_path))
26
+ definitions = data["iconDefinitions"] || {}
27
+
28
+ rename_clones(definitions, target_dir)
29
+
30
+ aliases = {}
31
+ (data["fileExtensions"] || {}).each { |extension, name| aliases["*.#{extension.downcase}"] = name }
32
+ (data["fileNames"] || {}).each { |filename, name| aliases[filename.downcase] = name }
33
+
34
+ manifest = {
35
+ "name" => "material",
36
+ "description" => "Material Design file icons (PKief material-icon-theme)",
37
+ "provider" => "material-icon-theme",
38
+ "default" => data["file"] || "file",
39
+ "aliases" => aliases
40
+ }
41
+
42
+ File.write(Pathname(target_dir).join("manifest.json"), JSON.pretty_generate(manifest))
43
+ puts " ✓ Wrote manifest.json (#{aliases.size} aliases)"
44
+ end
45
+
46
+ # Material ships ~72 "*.clone.svg" files; rename them to the clean icon
47
+ # name the aliases point at, so on-disk names match the manifest targets.
48
+ def rename_clones(definitions, target_dir)
49
+ target_dir = Pathname(target_dir)
50
+
51
+ definitions.each do |name, info|
52
+ basename = File.basename(info["iconPath"].to_s, ".svg")
53
+ next if basename == name
54
+
55
+ source = target_dir.join("#{basename}.svg")
56
+ destination = target_dir.join("#{name}.svg")
57
+ FileUtils.mv(source, destination) if source.exist? && !destination.exist?
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Unmagic
4
+ class Icon
5
+ class Library
6
+ class Source
7
+ # No release archive: fetch the svgs directly from the repo via the
8
+ # GitHub contents API, so #acquire is overridden entirely.
9
+ class Silk < Source
10
+ key :silk
11
+ title "Silk Icons Scalable"
12
+ description "The classic silk icon set recreated as SVG"
13
+
14
+ REPO = "frhun/silk-icon-scalable"
15
+ BRANCH = "main"
16
+ PATHS = [ "baseicons", "extra" ].freeze
17
+
18
+ private
19
+
20
+ def acquire(target_dir)
21
+ FileUtils.mkdir_p(target_dir)
22
+
23
+ headers = {
24
+ "Accept" => "application/vnd.github.v3+json",
25
+ "User-Agent" => "unmagic-icon-downloader"
26
+ }
27
+
28
+ PATHS.each do |dir_path|
29
+ uri = URI("https://api.github.com/repos/#{REPO}/contents/#{dir_path}?ref=#{BRANCH}")
30
+ response = fetch_with_redirect(uri, headers)
31
+ raise DownloadError, "Failed to list #{dir_path}: HTTP #{response.code}" if response.code != "200"
32
+
33
+ files = JSON.parse(response.body).select { |file| file["name"].end_with?(".svg") }
34
+ puts " Downloading #{files.size} icons from #{dir_path}..."
35
+ each_with_progress(files, show_time: true) do |file|
36
+ download_file(file["download_url"], File.join(target_dir, file["name"]))
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Unmagic
4
+ class Icon
5
+ class Library
6
+ class Source
7
+ class SimpleIcons < Source
8
+ key :"simple-icons"
9
+ title "Simple Icons"
10
+ description "SVG icons for popular brands"
11
+ url "https://registry.npmjs.org/simple-icons/-/simple-icons-14.2.0.tgz"
12
+ archive :tgz
13
+ extract "package/icons/*.svg"
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end