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 +7 -0
- data/CHANGELOG.md +37 -0
- data/LICENSE +21 -0
- data/README.md +156 -0
- data/lib/tasks/unmagic/icon/download.rake +26 -0
- data/lib/tasks/unmagic/icon/install.rake +25 -0
- data/lib/unmagic/icon/action_view_helpers.rb +14 -0
- data/lib/unmagic/icon/configuration.rb +34 -0
- data/lib/unmagic/icon/engine.rb +44 -0
- data/lib/unmagic/icon/library/registry.rb +81 -0
- data/lib/unmagic/icon/library/source/devicons.rb +18 -0
- data/lib/unmagic/icon/library/source/feather.rb +18 -0
- data/lib/unmagic/icon/library/source/heroicons.rb +23 -0
- data/lib/unmagic/icon/library/source/lucide.rb +18 -0
- data/lib/unmagic/icon/library/source/material_file_icons.rb +64 -0
- data/lib/unmagic/icon/library/source/silk.rb +44 -0
- data/lib/unmagic/icon/library/source/simple_icons.rb +18 -0
- data/lib/unmagic/icon/library/source/tabler.rb +18 -0
- data/lib/unmagic/icon/library/source.rb +242 -0
- data/lib/unmagic/icon/library.rb +114 -0
- data/lib/unmagic/icon/version.rb +7 -0
- data/lib/unmagic/icon/web/public/favicon.png +0 -0
- data/lib/unmagic/icon/web/views/layout.html.erb +329 -0
- data/lib/unmagic/icon/web.rb +70 -0
- data/lib/unmagic/icon.rb +134 -0
- data/lib/unmagic_icon.rb +3 -0
- metadata +143 -0
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
|