lucide-ruby 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/LICENSE.txt +21 -0
- data/README.md +113 -0
- data/lib/generators/lucide_ruby/install_generator.rb +27 -0
- data/lib/generators/lucide_ruby/templates/initializer.rb +24 -0
- data/lib/lucide_ruby/cache.rb +51 -0
- data/lib/lucide_ruby/configuration.rb +33 -0
- data/lib/lucide_ruby/errors.rb +19 -0
- data/lib/lucide_ruby/icon.rb +107 -0
- data/lib/lucide_ruby/railtie.rb +15 -0
- data/lib/lucide_ruby/tasks/lucide.rake +171 -0
- data/lib/lucide_ruby/version.rb +5 -0
- data/lib/lucide_ruby/view_helpers.rb +9 -0
- data/lib/lucide_ruby.rb +33 -0
- metadata +97 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 7121a5e1bf057cb09391cf6425b903e51ceccc8372604727cda37d0d4b0958da
|
|
4
|
+
data.tar.gz: 035bec245fc1d9194ec40fbc3559eb4493813e0488072d0c7f1f22d8621d475f
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 3a4efc318ae365fec0836206b9930d846a876ec0d08f18bc6c6f724a09d66d35ce771dcc1338ac8d183fb946d9cfd539762615de1e9b9e4d236dab978fa74636
|
|
7
|
+
data.tar.gz: 016af3c5224cea4fb2804216862532e05998e8d834b6d0f8a74707015531e525e0f8990ba19f8eb4bd83f1cf68a4e9fee26839bde07cdfa9a1db2fcafd6ebbe8
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 lucide-ruby contributors
|
|
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,113 @@
|
|
|
1
|
+
# LucideRuby
|
|
2
|
+
|
|
3
|
+
Rails view helpers for rendering [Lucide](https://lucide.dev) SVG icons inline.
|
|
4
|
+
|
|
5
|
+
Icons are **not bundled** in the gem. A rake task syncs them from official Lucide GitHub releases into your Rails app's filesystem.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
Add to your Gemfile:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
gem "lucide-ruby"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Then run:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
bundle install
|
|
19
|
+
rails generate lucide_ruby:install
|
|
20
|
+
rake lucide:sync
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
This will:
|
|
24
|
+
|
|
25
|
+
1. Create a config initializer at `config/initializers/lucide_ruby.rb`
|
|
26
|
+
2. Create the icon directory at `app/assets/icons/lucide/`
|
|
27
|
+
3. Download all Lucide SVG icons from the latest release
|
|
28
|
+
|
|
29
|
+
Commit the icons to version control so deploys don't need to re-sync.
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
```erb
|
|
34
|
+
<%= lucide_icon("arrow-down") %>
|
|
35
|
+
<%= lucide_icon(:check, size: 16, class: "text-green-500") %>
|
|
36
|
+
<%= lucide_icon("menu", data: { action: "click->menu#toggle" }) %>
|
|
37
|
+
<%= lucide_icon("user", aria: { label: "User profile" }) %>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Options
|
|
41
|
+
|
|
42
|
+
| Option | Description |
|
|
43
|
+
|--------|-------------|
|
|
44
|
+
| `size` | Sets both `width` and `height` |
|
|
45
|
+
| `class` | CSS class (appended to `default_class` if configured) |
|
|
46
|
+
| `data` | Data attributes hash |
|
|
47
|
+
| `aria` | Aria attributes hash (disables auto `aria-hidden`) |
|
|
48
|
+
| `stroke-width` | Override stroke width |
|
|
49
|
+
| `fill` | Override fill color |
|
|
50
|
+
| `stroke` | Override stroke color |
|
|
51
|
+
| Any SVG attribute | Passed through to the `<svg>` element |
|
|
52
|
+
|
|
53
|
+
### Accessibility
|
|
54
|
+
|
|
55
|
+
By default, icons render with `aria-hidden="true"`. When you pass any `aria` attribute (e.g., `aria: { label: "..." }`), `aria-hidden` is omitted so screen readers can access the icon.
|
|
56
|
+
|
|
57
|
+
## Configuration
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
# config/initializers/lucide_ruby.rb
|
|
61
|
+
LucideRuby.configure do |config|
|
|
62
|
+
config.default_class = "icon"
|
|
63
|
+
config.default_size = 20
|
|
64
|
+
config.default_stroke_width = 1.5
|
|
65
|
+
config.default_fill = "none"
|
|
66
|
+
config.default_stroke = "currentColor"
|
|
67
|
+
config.icon_path = Rails.root.join("app/assets/icons/lucide").to_s
|
|
68
|
+
config.default_attributes = {}
|
|
69
|
+
end
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Rake Tasks
|
|
73
|
+
|
|
74
|
+
### `rake lucide:sync`
|
|
75
|
+
|
|
76
|
+
Downloads and extracts Lucide icons from GitHub releases.
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# Sync latest version
|
|
80
|
+
rake lucide:sync
|
|
81
|
+
|
|
82
|
+
# Pin to a specific version
|
|
83
|
+
LUCIDE_VERSION=0.575.0 rake lucide:sync
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### `rake lucide:info`
|
|
87
|
+
|
|
88
|
+
Shows info about currently synced icons.
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
rake lucide:info
|
|
92
|
+
# Lucide Icons
|
|
93
|
+
# Path: /path/to/app/assets/icons/lucide
|
|
94
|
+
# Version: 0.575.0
|
|
95
|
+
# Icons: 1458
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Caching
|
|
99
|
+
|
|
100
|
+
Icons are cached in memory after first use. For production, you can preload all icons at boot:
|
|
101
|
+
|
|
102
|
+
```ruby
|
|
103
|
+
# config/initializers/lucide_ruby.rb
|
|
104
|
+
LucideRuby.configure do |config|
|
|
105
|
+
# ...
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
LucideRuby.cache.preload! if Rails.env.production?
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## License
|
|
112
|
+
|
|
113
|
+
MIT License. See [LICENSE.txt](LICENSE.txt).
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LucideRuby
|
|
4
|
+
module Generators
|
|
5
|
+
class InstallGenerator < Rails::Generators::Base
|
|
6
|
+
source_root File.expand_path("templates", __dir__)
|
|
7
|
+
desc "Creates a LucideRuby initializer"
|
|
8
|
+
|
|
9
|
+
def copy_initializer
|
|
10
|
+
template "initializer.rb", "config/initializers/lucide_ruby.rb"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def create_icon_directory
|
|
14
|
+
empty_directory "app/assets/icons/lucide"
|
|
15
|
+
create_file "app/assets/icons/lucide/.keep"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def show_next_steps
|
|
19
|
+
say ""
|
|
20
|
+
say "LucideRuby installed! Next steps:", :green
|
|
21
|
+
say " 1. Run `rake lucide:sync` to download Lucide icons"
|
|
22
|
+
say " 2. Use `lucide_icon(\"icon-name\")` in your views"
|
|
23
|
+
say ""
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
LucideRuby.configure do |config|
|
|
4
|
+
# Path where Lucide icons are stored (default: app/assets/icons/lucide)
|
|
5
|
+
# config.icon_path = Rails.root.join("app", "assets", "icons", "lucide").to_s
|
|
6
|
+
|
|
7
|
+
# Default CSS class added to all icons
|
|
8
|
+
# config.default_class = "icon"
|
|
9
|
+
|
|
10
|
+
# Default icon size in pixels (default: 24)
|
|
11
|
+
# config.default_size = 24
|
|
12
|
+
|
|
13
|
+
# Default stroke width (default: 2)
|
|
14
|
+
# config.default_stroke_width = 2
|
|
15
|
+
|
|
16
|
+
# Default fill color (default: "none")
|
|
17
|
+
# config.default_fill = "none"
|
|
18
|
+
|
|
19
|
+
# Default stroke color (default: "currentColor")
|
|
20
|
+
# config.default_stroke = "currentColor"
|
|
21
|
+
|
|
22
|
+
# Additional default attributes applied to all icons
|
|
23
|
+
# config.default_attributes = {}
|
|
24
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LucideRuby
|
|
4
|
+
class Cache
|
|
5
|
+
def initialize
|
|
6
|
+
@store = {}
|
|
7
|
+
@mutex = Mutex.new
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def read(name)
|
|
11
|
+
@mutex.synchronize { @store[name] }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def write(name, value)
|
|
15
|
+
@mutex.synchronize { @store[name] = value }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def fetch(name)
|
|
19
|
+
cached = read(name)
|
|
20
|
+
return cached if cached
|
|
21
|
+
|
|
22
|
+
value = yield
|
|
23
|
+
write(name, value)
|
|
24
|
+
value
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def clear!
|
|
28
|
+
@mutex.synchronize { @store.clear }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def size
|
|
32
|
+
@mutex.synchronize { @store.size }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def preload!
|
|
36
|
+
icon_path = LucideRuby.configuration.resolved_icon_path
|
|
37
|
+
|
|
38
|
+
unless Dir.exist?(icon_path)
|
|
39
|
+
raise IconsNotSynced.new(icon_path)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
Dir.glob(File.join(icon_path, "*.svg")).each do |file|
|
|
43
|
+
name = File.basename(file, ".svg")
|
|
44
|
+
inner_html = Icon.extract_inner_html(File.read(file))
|
|
45
|
+
write(name, inner_html)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
size
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LucideRuby
|
|
4
|
+
class Configuration
|
|
5
|
+
attr_accessor :icon_path, :default_class, :default_size,
|
|
6
|
+
:default_stroke_width, :default_fill, :default_stroke,
|
|
7
|
+
:default_attributes
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
@icon_path = nil
|
|
11
|
+
@default_class = nil
|
|
12
|
+
@default_size = 24
|
|
13
|
+
@default_stroke_width = 2
|
|
14
|
+
@default_fill = "none"
|
|
15
|
+
@default_stroke = "currentColor"
|
|
16
|
+
@default_attributes = {}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def resolved_icon_path
|
|
20
|
+
@icon_path || default_icon_path
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def default_icon_path
|
|
26
|
+
if defined?(Rails)
|
|
27
|
+
Rails.root.join("app", "assets", "icons", "lucide").to_s
|
|
28
|
+
else
|
|
29
|
+
File.join(Dir.pwd, "app", "assets", "icons", "lucide")
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LucideRuby
|
|
4
|
+
class Error < StandardError; end
|
|
5
|
+
|
|
6
|
+
class IconNotFound < Error
|
|
7
|
+
def initialize(name, path)
|
|
8
|
+
super("Lucide icon '#{name}' not found at #{path}")
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class IconsNotSynced < Error
|
|
13
|
+
def initialize(path)
|
|
14
|
+
super("Lucide icons not found at #{path}. Run `rake lucide:sync` to download icons.")
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class SyncError < Error; end
|
|
19
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LucideRuby
|
|
4
|
+
class Icon
|
|
5
|
+
SVG_CONTENT_REGEX = /<svg[^>]*>(.*)<\/svg>/m
|
|
6
|
+
|
|
7
|
+
attr_reader :name, :options
|
|
8
|
+
|
|
9
|
+
def initialize(name, **options)
|
|
10
|
+
@name = self.class.normalize_name(name)
|
|
11
|
+
@options = options
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def render
|
|
15
|
+
inner_html = load_inner_html
|
|
16
|
+
attributes = svg_attributes
|
|
17
|
+
build_svg(inner_html, attributes)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.normalize_name(name)
|
|
21
|
+
name.to_s.strip.downcase.tr("_", "-")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.extract_inner_html(svg_content)
|
|
25
|
+
match = svg_content.match(SVG_CONTENT_REGEX)
|
|
26
|
+
match ? match[1].strip : ""
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def load_inner_html
|
|
32
|
+
LucideRuby.cache.fetch(name) do
|
|
33
|
+
icon_path = LucideRuby.configuration.resolved_icon_path
|
|
34
|
+
file_path = File.join(icon_path, "#{name}.svg")
|
|
35
|
+
|
|
36
|
+
unless Dir.exist?(icon_path)
|
|
37
|
+
raise IconsNotSynced.new(icon_path)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
unless File.exist?(file_path)
|
|
41
|
+
raise IconNotFound.new(name, file_path)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
self.class.extract_inner_html(File.read(file_path))
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def svg_attributes
|
|
49
|
+
config = LucideRuby.configuration
|
|
50
|
+
attrs = {
|
|
51
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
52
|
+
width: config.default_size,
|
|
53
|
+
height: config.default_size,
|
|
54
|
+
viewBox: "0 0 24 24",
|
|
55
|
+
fill: config.default_fill,
|
|
56
|
+
stroke: config.default_stroke,
|
|
57
|
+
"stroke-width": config.default_stroke_width,
|
|
58
|
+
"stroke-linecap": "round",
|
|
59
|
+
"stroke-linejoin": "round"
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# Merge default_attributes from config
|
|
63
|
+
attrs.merge!(config.default_attributes) if config.default_attributes.any?
|
|
64
|
+
|
|
65
|
+
# Merge caller options
|
|
66
|
+
caller_opts = options.dup
|
|
67
|
+
|
|
68
|
+
# Handle size shorthand
|
|
69
|
+
if caller_opts[:size]
|
|
70
|
+
size = caller_opts.delete(:size)
|
|
71
|
+
caller_opts[:width] = size
|
|
72
|
+
caller_opts[:height] = size
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Handle class merging (append, don't replace)
|
|
76
|
+
if caller_opts[:class] && config.default_class
|
|
77
|
+
caller_opts[:class] = "#{config.default_class} #{caller_opts[:class]}"
|
|
78
|
+
elsif config.default_class && !caller_opts.key?(:class)
|
|
79
|
+
caller_opts[:class] = config.default_class
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Auto aria-hidden unless aria attributes provided
|
|
83
|
+
has_aria = caller_opts.key?(:aria) ||
|
|
84
|
+
caller_opts.keys.any? { |k| k.to_s.start_with?("aria") }
|
|
85
|
+
attrs[:"aria-hidden"] = "true" unless has_aria
|
|
86
|
+
|
|
87
|
+
attrs.merge!(caller_opts)
|
|
88
|
+
attrs
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def build_svg(inner_html, attributes)
|
|
92
|
+
attr_string = attributes.map { |k, v|
|
|
93
|
+
if v.is_a?(Hash)
|
|
94
|
+
v.map { |sub_k, sub_v| "#{k}-#{sub_k}=\"#{escape(sub_v.to_s)}\"" }.join(" ")
|
|
95
|
+
else
|
|
96
|
+
"#{k}=\"#{escape(v.to_s)}\""
|
|
97
|
+
end
|
|
98
|
+
}.join(" ")
|
|
99
|
+
|
|
100
|
+
"<svg #{attr_string}>#{inner_html}</svg>".html_safe
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def escape(value)
|
|
104
|
+
ERB::Util.html_escape(value)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LucideRuby
|
|
4
|
+
class Railtie < Rails::Railtie
|
|
5
|
+
initializer "lucide_ruby.view_helpers" do
|
|
6
|
+
ActiveSupport.on_load(:action_view) do
|
|
7
|
+
include LucideRuby::ViewHelpers
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
rake_tasks do
|
|
12
|
+
load File.expand_path("tasks/lucide.rake", __dir__)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "uri"
|
|
5
|
+
require "json"
|
|
6
|
+
require "fileutils"
|
|
7
|
+
require "tmpdir"
|
|
8
|
+
require "zip"
|
|
9
|
+
|
|
10
|
+
namespace :lucide do
|
|
11
|
+
desc "Sync Lucide icons from GitHub releases"
|
|
12
|
+
task sync: :environment do
|
|
13
|
+
version = ENV["LUCIDE_VERSION"]
|
|
14
|
+
icon_path = LucideRuby.configuration.resolved_icon_path
|
|
15
|
+
|
|
16
|
+
puts "Fetching Lucide icons..."
|
|
17
|
+
|
|
18
|
+
# Determine download URL
|
|
19
|
+
if version
|
|
20
|
+
download_url = "https://github.com/lucide-icons/lucide/releases/download/#{version}/lucide-icons-#{version}.zip"
|
|
21
|
+
puts "Pinned version: #{version}"
|
|
22
|
+
else
|
|
23
|
+
puts "Fetching latest release..."
|
|
24
|
+
latest_url = fetch_latest_release_url
|
|
25
|
+
version = latest_url[/\/([^\/]+)\/lucide-icons-/, 1]
|
|
26
|
+
download_url = latest_url
|
|
27
|
+
puts "Latest version: #{version}"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
Dir.mktmpdir("lucide-sync") do |tmpdir|
|
|
31
|
+
zip_path = File.join(tmpdir, "lucide-icons.zip")
|
|
32
|
+
extract_path = File.join(tmpdir, "extracted")
|
|
33
|
+
|
|
34
|
+
# Download
|
|
35
|
+
puts "Downloading #{download_url}..."
|
|
36
|
+
download_file(download_url, zip_path)
|
|
37
|
+
puts "Downloaded #{File.size(zip_path)} bytes"
|
|
38
|
+
|
|
39
|
+
# Extract
|
|
40
|
+
FileUtils.mkdir_p(extract_path)
|
|
41
|
+
extract_zip(zip_path, extract_path)
|
|
42
|
+
|
|
43
|
+
# Find SVG files in extracted archive
|
|
44
|
+
svg_source = find_svg_directory(extract_path)
|
|
45
|
+
|
|
46
|
+
if svg_source.nil?
|
|
47
|
+
raise LucideRuby::SyncError, "No SVG files found in the downloaded archive"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
svg_files = Dir.glob(File.join(svg_source, "*.svg"))
|
|
51
|
+
puts "Found #{svg_files.size} icons"
|
|
52
|
+
|
|
53
|
+
# Atomic replace: write to temp dir, then swap
|
|
54
|
+
staging_path = "#{icon_path}.staging"
|
|
55
|
+
backup_path = "#{icon_path}.backup"
|
|
56
|
+
|
|
57
|
+
FileUtils.rm_rf(staging_path)
|
|
58
|
+
FileUtils.mkdir_p(staging_path)
|
|
59
|
+
|
|
60
|
+
svg_files.each do |svg_file|
|
|
61
|
+
FileUtils.cp(svg_file, staging_path)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Write version file
|
|
65
|
+
File.write(File.join(staging_path, ".lucide-version"), version)
|
|
66
|
+
|
|
67
|
+
# Swap directories
|
|
68
|
+
FileUtils.rm_rf(backup_path)
|
|
69
|
+
FileUtils.mv(icon_path, backup_path) if Dir.exist?(icon_path)
|
|
70
|
+
FileUtils.mv(staging_path, icon_path)
|
|
71
|
+
FileUtils.rm_rf(backup_path)
|
|
72
|
+
|
|
73
|
+
# Clear cache
|
|
74
|
+
LucideRuby.cache.clear!
|
|
75
|
+
|
|
76
|
+
puts "Synced #{svg_files.size} Lucide icons (#{version}) to #{icon_path}"
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
desc "Show info about synced Lucide icons"
|
|
81
|
+
task info: :environment do
|
|
82
|
+
icon_path = LucideRuby.configuration.resolved_icon_path
|
|
83
|
+
|
|
84
|
+
unless Dir.exist?(icon_path)
|
|
85
|
+
puts "No icons found at #{icon_path}"
|
|
86
|
+
puts "Run `rake lucide:sync` to download icons."
|
|
87
|
+
next
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
svg_count = Dir.glob(File.join(icon_path, "*.svg")).size
|
|
91
|
+
version_file = File.join(icon_path, ".lucide-version")
|
|
92
|
+
version = File.exist?(version_file) ? File.read(version_file).strip : "unknown"
|
|
93
|
+
|
|
94
|
+
puts "Lucide Icons"
|
|
95
|
+
puts " Path: #{icon_path}"
|
|
96
|
+
puts " Version: #{version}"
|
|
97
|
+
puts " Icons: #{svg_count}"
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def fetch_latest_release_url
|
|
102
|
+
uri = URI("https://api.github.com/repos/lucide-icons/lucide/releases/latest")
|
|
103
|
+
response = make_request(uri)
|
|
104
|
+
|
|
105
|
+
data = JSON.parse(response.body)
|
|
106
|
+
asset = data["assets"]&.find { |a| a["name"]&.match?(/^lucide-icons-.*\.zip$/) }
|
|
107
|
+
|
|
108
|
+
unless asset
|
|
109
|
+
raise LucideRuby::SyncError, "Could not find lucide-icons zip in latest release"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
asset["browser_download_url"]
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def download_file(url, destination, redirect_limit = 5)
|
|
116
|
+
raise LucideRuby::SyncError, "Too many redirects" if redirect_limit == 0
|
|
117
|
+
|
|
118
|
+
uri = URI(url)
|
|
119
|
+
response = make_request(uri)
|
|
120
|
+
|
|
121
|
+
case response
|
|
122
|
+
when Net::HTTPSuccess
|
|
123
|
+
File.binwrite(destination, response.body)
|
|
124
|
+
when Net::HTTPRedirection
|
|
125
|
+
download_file(response["location"], destination, redirect_limit - 1)
|
|
126
|
+
else
|
|
127
|
+
raise LucideRuby::SyncError, "Download failed: #{response.code} #{response.message}"
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def make_request(uri)
|
|
132
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
133
|
+
http.use_ssl = (uri.scheme == "https")
|
|
134
|
+
http.open_timeout = 15
|
|
135
|
+
http.read_timeout = 60
|
|
136
|
+
|
|
137
|
+
request = Net::HTTP::Get.new(uri)
|
|
138
|
+
request["User-Agent"] = "lucide-ruby/#{LucideRuby::VERSION}"
|
|
139
|
+
request["Accept"] = "application/octet-stream, application/json"
|
|
140
|
+
|
|
141
|
+
http.request(request)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def extract_zip(zip_path, extract_path)
|
|
145
|
+
Zip::File.open(zip_path) do |zip_file|
|
|
146
|
+
zip_file.each do |entry|
|
|
147
|
+
entry_path = File.join(extract_path, entry.name)
|
|
148
|
+
|
|
149
|
+
# Prevent zip slip
|
|
150
|
+
unless entry_path.start_with?(File.realpath(extract_path))
|
|
151
|
+
raise LucideRuby::SyncError, "Zip slip detected: #{entry.name}"
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
if entry.directory?
|
|
155
|
+
FileUtils.mkdir_p(entry_path)
|
|
156
|
+
else
|
|
157
|
+
FileUtils.mkdir_p(File.dirname(entry_path))
|
|
158
|
+
entry.extract(entry_path)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def find_svg_directory(extract_path)
|
|
165
|
+
# Look for SVGs directly or in subdirectories
|
|
166
|
+
if Dir.glob(File.join(extract_path, "*.svg")).any?
|
|
167
|
+
return extract_path
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
Dir.glob(File.join(extract_path, "**", "*.svg")).first&.then { |f| File.dirname(f) }
|
|
171
|
+
end
|
data/lib/lucide_ruby.rb
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "erb"
|
|
4
|
+
require "active_support/core_ext/string/output_safety"
|
|
5
|
+
|
|
6
|
+
require_relative "lucide_ruby/version"
|
|
7
|
+
require_relative "lucide_ruby/configuration"
|
|
8
|
+
require_relative "lucide_ruby/errors"
|
|
9
|
+
require_relative "lucide_ruby/cache"
|
|
10
|
+
require_relative "lucide_ruby/icon"
|
|
11
|
+
require_relative "lucide_ruby/view_helpers"
|
|
12
|
+
require_relative "lucide_ruby/railtie" if defined?(Rails)
|
|
13
|
+
|
|
14
|
+
module LucideRuby
|
|
15
|
+
class << self
|
|
16
|
+
def configuration
|
|
17
|
+
@configuration ||= Configuration.new
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def configure
|
|
21
|
+
yield(configuration)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def reset_configuration!
|
|
25
|
+
@configuration = Configuration.new
|
|
26
|
+
cache.clear!
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def cache
|
|
30
|
+
@cache ||= Cache.new
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: lucide-ruby
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- lucide-ruby contributors
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: actionview
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '6.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '6.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: railties
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '6.0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '6.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rubyzip
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '2.3'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '2.3'
|
|
54
|
+
description: Provides Rails view helpers for rendering Lucide SVG icons inline. Icons
|
|
55
|
+
are synced from official Lucide GitHub releases via a rake task.
|
|
56
|
+
executables: []
|
|
57
|
+
extensions: []
|
|
58
|
+
extra_rdoc_files: []
|
|
59
|
+
files:
|
|
60
|
+
- LICENSE.txt
|
|
61
|
+
- README.md
|
|
62
|
+
- lib/generators/lucide_ruby/install_generator.rb
|
|
63
|
+
- lib/generators/lucide_ruby/templates/initializer.rb
|
|
64
|
+
- lib/lucide_ruby.rb
|
|
65
|
+
- lib/lucide_ruby/cache.rb
|
|
66
|
+
- lib/lucide_ruby/configuration.rb
|
|
67
|
+
- lib/lucide_ruby/errors.rb
|
|
68
|
+
- lib/lucide_ruby/icon.rb
|
|
69
|
+
- lib/lucide_ruby/railtie.rb
|
|
70
|
+
- lib/lucide_ruby/tasks/lucide.rake
|
|
71
|
+
- lib/lucide_ruby/version.rb
|
|
72
|
+
- lib/lucide_ruby/view_helpers.rb
|
|
73
|
+
homepage: https://github.com/lucide-ruby/lucide-ruby
|
|
74
|
+
licenses:
|
|
75
|
+
- MIT
|
|
76
|
+
metadata:
|
|
77
|
+
homepage_uri: https://github.com/lucide-ruby/lucide-ruby
|
|
78
|
+
source_code_uri: https://github.com/lucide-ruby/lucide-ruby
|
|
79
|
+
changelog_uri: https://github.com/lucide-ruby/lucide-ruby/blob/main/CHANGELOG.md
|
|
80
|
+
rdoc_options: []
|
|
81
|
+
require_paths:
|
|
82
|
+
- lib
|
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
84
|
+
requirements:
|
|
85
|
+
- - ">="
|
|
86
|
+
- !ruby/object:Gem::Version
|
|
87
|
+
version: 2.7.0
|
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
89
|
+
requirements:
|
|
90
|
+
- - ">="
|
|
91
|
+
- !ruby/object:Gem::Version
|
|
92
|
+
version: '0'
|
|
93
|
+
requirements: []
|
|
94
|
+
rubygems_version: 4.0.6
|
|
95
|
+
specification_version: 4
|
|
96
|
+
summary: Rails view helpers for rendering Lucide SVG icons inline
|
|
97
|
+
test_files: []
|