icons 0.0.2 → 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.
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Icons
4
+ class Icon
5
+ class Attributes
6
+ def initialize(default_attributes: {}, arguments: {})
7
+ @merged_attributes = default_attributes.merge(arguments)
8
+ end
9
+
10
+ def attach(to:)
11
+ @merged_attributes.each do |key, value|
12
+ if key == :class
13
+ class_attribute(key, value, to)
14
+ elsif value.is_a?(Hash)
15
+ hash_attributes(key, value, to)
16
+ else
17
+ string_attributes(key, value, to)
18
+ end
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def class_attribute(_, value, to)
25
+ to[:class] = token_list(value)
26
+ end
27
+
28
+ def token_list(value)
29
+ case value
30
+ when Array
31
+ # Flatten and process each element
32
+ value.flatten.map { |v|
33
+ if v.is_a?(Hash)
34
+ v.select { |_, val| val }.keys.map(&:to_s).join(" ")
35
+ else
36
+ v.to_s
37
+ end
38
+ }.compact.reject(&:empty?).join(" ")
39
+ when Hash
40
+ # Handle both string and symbol keys, filter truthy values
41
+ value.select { |_, v| v }.keys.map(&:to_s).join(" ")
42
+ else
43
+ value.to_s
44
+ end
45
+ end
46
+
47
+ def hash_attributes(key, value, to)
48
+ value.each do |nested_key, nested_value|
49
+ nested_attribute_name = format_attribute_name("#{key}-#{nested_key}")
50
+
51
+ to[nested_attribute_name] = nested_value
52
+ end
53
+ end
54
+
55
+ def string_attributes(key, value, to)
56
+ normalized_key = format_attribute_name(key.to_s)
57
+
58
+ to[normalized_key] = value
59
+ end
60
+
61
+ def format_attribute_name(name)
62
+ name.tr("_", "-")
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+
5
+ module Icons
6
+ class Icon
7
+ class FilePath
8
+ def initialize(name:, library:, variant:)
9
+ @name = name
10
+ @library = library.to_s
11
+ @variant = variant
12
+ end
13
+
14
+ def call
15
+ if animated_library?
16
+ path = animated_icons_path
17
+ raise Icons::IconNotFound if path.nil?
18
+ return path
19
+ end
20
+
21
+ return custom_library_path if custom_library?
22
+
23
+ icon_path = icons_path_in_app
24
+
25
+ raise Icons::IconNotFound if icon_path.nil?
26
+
27
+ icon_path
28
+ end
29
+
30
+ private
31
+
32
+ def animated_library?
33
+ @library == "animated"
34
+ end
35
+
36
+ def animated_icons_path
37
+ # Animated icons are bundled with the gem at lib/icons/assets/animated/
38
+ # __dir__ is lib/icons/icon, so go up one level to lib/icons
39
+ gem_icons_dir = Pathname.new(__dir__).join("..")
40
+ path = gem_icons_dir.join("assets", "animated", "#{@name}.svg")
41
+
42
+ File.exist?(path) ? path : nil
43
+ end
44
+
45
+ def custom_library?
46
+ Icons.libraries[@library.to_sym]&.respond_to?(:custom_path)
47
+ end
48
+
49
+ def custom_library_path
50
+ library_config = Icons.libraries[@library.to_sym]
51
+ Icons.config.base_path.join(library_config.custom_path, "#{@name}.svg")
52
+ end
53
+
54
+ def icons_path_in_app
55
+ path = app_path
56
+
57
+ path if File.exist?(path)
58
+ end
59
+
60
+ def app_path
61
+ Icons.config.base_path.join(*parts)
62
+ end
63
+
64
+ def parts
65
+ [
66
+ Icons.configuration.icons_path,
67
+ @library,
68
+ (@variant unless @variant == "."), # Don't include "." as a directory
69
+ "#{@name}.svg"
70
+ ].compact.reject { |p| p.to_s.empty? }
71
+ end
72
+ end
73
+ end
74
+ end
data/lib/icons/icon.rb ADDED
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "nokogiri"
4
+
5
+ require "icons/icon/file_path"
6
+ require "icons/icon/attributes"
7
+
8
+ class Icons::Icon
9
+ def initialize(name:, library:, arguments:, variant: nil)
10
+ @config = Icons.configuration
11
+
12
+ @name = name
13
+ @library = library.to_s
14
+ @variant = (variant || set_variant).to_s
15
+ @arguments = arguments
16
+ end
17
+
18
+ def svg
19
+ raise Icons::IconNotFound, error_message unless File.exist?(file_path)
20
+
21
+ Nokogiri::HTML::DocumentFragment.parse(File.read(file_path))
22
+ .at_css("svg")
23
+ .tap { |svg| attach_attributes(to: svg) }
24
+ .to_html
25
+ end
26
+
27
+ private
28
+
29
+ def set_variant
30
+ value = @config.libraries.dig(@library.to_sym, :default_variant) ||
31
+ @config.default_variant
32
+
33
+ value.to_s.empty? ? nil : value
34
+ end
35
+
36
+ def error_message
37
+ attributes = [
38
+ @library,
39
+ @variant,
40
+ @name
41
+ ].compact
42
+
43
+ "Icon not found: `#{attributes.join(" / ")}`"
44
+ end
45
+
46
+ def file_path
47
+ Icons::Icon::FilePath.new(name: @name, library: @library, variant: @variant).call
48
+ end
49
+
50
+ def attach_attributes(to:)
51
+ Icons::Icon::Attributes
52
+ .new(default_attributes: default_attributes, arguments: @arguments)
53
+ .attach(to: to)
54
+ end
55
+
56
+ def default_attributes
57
+ {
58
+ "stroke-width": default(:stroke_width),
59
+ data: default(:data),
60
+ class: default(:css)
61
+ }
62
+ end
63
+
64
+ def default(key)
65
+ library_attributes.dig(:default, key)
66
+ end
67
+
68
+ def library_attributes
69
+ keys = [@library, @variant].compact.reject { |k| k.to_s.empty? }
70
+ @config.libraries.dig(*keys) || {}
71
+ end
72
+ end
@@ -0,0 +1,32 @@
1
+ require "icons/configuration/animated"
2
+ require "icons/configuration/boxicons"
3
+ require "icons/configuration/feather"
4
+ require "icons/configuration/flags"
5
+ require "icons/configuration/heroicons"
6
+ require "icons/configuration/linear"
7
+ require "icons/configuration/lucide"
8
+ require "icons/configuration/phosphor"
9
+ require "icons/configuration/radix"
10
+ require "icons/configuration/sidekickicons"
11
+ require "icons/configuration/tabler"
12
+ require "icons/configuration/weather"
13
+
14
+ module Icons
15
+ extend self
16
+
17
+ def libraries
18
+ {
19
+ boxicons: Icons::Configuration::Boxicons,
20
+ feather: Icons::Configuration::Feather,
21
+ flags: Icons::Configuration::Flags,
22
+ heroicons: Icons::Configuration::Heroicons,
23
+ linear: Icons::Configuration::Linear,
24
+ lucide: Icons::Configuration::Lucide,
25
+ phosphor: Icons::Configuration::Phosphor,
26
+ radix: Icons::Configuration::Radix,
27
+ sidekickicons: Icons::Configuration::Sidekickicons,
28
+ tabler: Icons::Configuration::Tabler,
29
+ weather: Icons::Configuration::Weather
30
+ }
31
+ end
32
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "icons/sync/transformations"
5
+
6
+ module Icons
7
+ class Sync
8
+ class ProcessVariants
9
+ def initialize(temp_directory, name, library)
10
+ @temp_directory, @name, @library = temp_directory, name, library
11
+ end
12
+
13
+ def process
14
+ original_variants = Dir.children(@temp_directory)
15
+ excluded_variants = Icons.configuration.libraries.dig(@name.to_sym)&.exclude_variants || []
16
+
17
+ @library[:variants].each do |variant_name, variant_source_path|
18
+ next if excluded_variants.include?(variant_name)
19
+
20
+ source = File.join(@temp_directory, variant_source_path)
21
+ destination = File.join(@temp_directory, variant_name.to_s)
22
+
23
+ original_variants.delete(variant_name.to_s)
24
+
25
+ raise "[Icons] Failed to find the icons directory: '#{source}'" unless Dir.exist?(source)
26
+
27
+ move_icons(source, destination)
28
+
29
+ apply_transformations_to(destination)
30
+ end
31
+
32
+ remove_files_and_folders_for(original_variants)
33
+ remove_previously_downloaded(excluded_variants)
34
+
35
+ puts "[Icons] Icon variants processed successfully"
36
+ end
37
+
38
+ private
39
+
40
+ def move_icons(source, destination)
41
+ FileUtils.mkdir_p(destination)
42
+
43
+ Dir.each_child(source).each do |item|
44
+ FileUtils.mv(File.join(source, item), destination)
45
+ end
46
+ end
47
+
48
+ def apply_transformations_to(destination)
49
+ Dir.each_child(destination) do |filename|
50
+ File.rename(
51
+ File.join(destination, filename),
52
+ File.join(destination, Sync::Transformations.transform(filename, transformations.fetch(:filenames, {})))
53
+ )
54
+ end
55
+ end
56
+
57
+ def remove_files_and_folders_for(paths)
58
+ paths.each do |path|
59
+ FileUtils.rm_rf(File.join(@temp_directory, path))
60
+ end
61
+ end
62
+
63
+ def remove_previously_downloaded(variants)
64
+ variants.each do |variant|
65
+ FileUtils.rm_rf(File.join(Icons.configuration.icons_path, @name, variant.to_s))
66
+ end
67
+ end
68
+
69
+ def transformations
70
+ library_config = Icons.libraries.dig(@name.to_sym)
71
+
72
+ if library_config&.respond_to?(:transformations)
73
+ library_config.transformations
74
+ else
75
+ {}
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Icons
4
+ class Sync
5
+ class Transformations
6
+ def self.transform(filename, rules = {})
7
+ basename = File.basename(filename, File.extname(filename))
8
+
9
+ transformed = rules.reduce(basename) do |fn, (type, value)|
10
+ TRANSFORMERS.fetch(type).call(fn, value)
11
+ end
12
+
13
+ [transformed, File.extname(filename)].join
14
+ end
15
+
16
+ private
17
+
18
+ TRANSFORMERS = {
19
+ delete_prefix: ->(filename, prefixes) {
20
+ Array(prefixes).reduce(filename) { |fn, prefix| fn.delete_prefix(prefix) }
21
+ },
22
+
23
+ delete_suffix: ->(filename, suffixes) {
24
+ Array(suffixes).reduce(filename) { |fn, suffix| fn.delete_suffix(suffix) }
25
+ }
26
+ }
27
+ end
28
+ end
29
+ end
data/lib/icons/sync.rb ADDED
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "icons/sync/process_variants"
5
+
6
+ module Icons
7
+ class Sync
8
+ def initialize(name)
9
+ raise "[Icons] Not a valid library" if Icons.libraries.keys.exclude?(name.to_sym)
10
+
11
+ @name = name
12
+ @library = Icons.libraries.fetch(name.to_sym).source
13
+ @temp_directory = File.join(temp_directory_root, name)
14
+ end
15
+
16
+ def now
17
+ clone_repository
18
+ process_variants
19
+ remove_non_svg_files
20
+
21
+ move_library
22
+
23
+ purge_temp_directory
24
+ rescue => error
25
+ puts "[Icons] Failed to sync icons: #{error.message}"
26
+
27
+ post_error_clean_up
28
+
29
+ raise
30
+ end
31
+
32
+ private
33
+
34
+ def temp_directory_root
35
+ Icons.config.base_path.join("tmp/icons")
36
+ end
37
+
38
+ def clone_repository
39
+ raise "[Icons] Failed to clone repository" unless system("git clone '#{@library[:url]}' '#{@temp_directory}'")
40
+
41
+ puts "[Icons] '#{@name}' repository cloned"
42
+ end
43
+
44
+ def process_variants
45
+ Sync::ProcessVariants.new(@temp_directory, @name, @library).process
46
+ end
47
+
48
+ def remove_non_svg_files
49
+ require "pathname"
50
+
51
+ Pathname.glob("#{@temp_directory}/**/*")
52
+ .select { |path| path.file? && path.extname != ".svg" }
53
+ .each(&:delete)
54
+
55
+ puts "[Icons] Non-SVG files removed"
56
+ end
57
+
58
+ def move_library
59
+ destination = File.join(Icons.configuration.icons_path, @name)
60
+
61
+ FileUtils.mkdir_p(destination)
62
+ FileUtils.mv(Dir.glob("#{@temp_directory}/*"), destination, force: true)
63
+
64
+ puts "[Icons] Synced '#{@name}' library #{%w[😃 🎉 ✨].sample}"
65
+ end
66
+
67
+ def purge_temp_directory
68
+ FileUtils.rm_rf(temp_directory_root)
69
+ end
70
+
71
+ def post_error_clean_up
72
+ print "Do you want to remove the temp files? ('#{@temp_directory}') [y/n]: "
73
+ response = $stdin.gets.chomp.downcase
74
+
75
+ if response == "y" || response == "yes"
76
+ puts "[Icons] Cleaning up…"
77
+ FileUtils.rm_rf(@temp_directory)
78
+ else
79
+ puts "[Icons] Keeping files at: '#{@temp_directory}'"
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,3 @@
1
+ module Icons
2
+ VERSION = "0.6.0"
3
+ end
data/lib/icons.rb CHANGED
@@ -1,3 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "icons/version"
4
+ require "icons/errors"
5
+ require "icons/libraries"
6
+ require "icons/configuration"
7
+ require "icons/icon"
8
+ require "icons/sync"
9
+
1
10
  module Icons
2
- # TBD
11
+ class << self
12
+ attr_accessor :configuration
13
+
14
+ def configure
15
+ self.configuration ||= Configuration.new
16
+ yield(configuration) if block_given?
17
+ end
18
+
19
+ def config
20
+ configuration || configure {}
21
+ end
22
+ end
3
23
  end
metadata CHANGED
@@ -1,27 +1,83 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: icons
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
- - Rails Designer Developers
7
+ - Rails Designer
8
8
  bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
- dependencies: []
12
- description: Coming soon. Add any icon library to a Ruby app, from Heroicons, to Lucide
13
- to Tabler (and others). Icons is library-agnostic, so you can add any library while
14
- using the same interface.
15
- email: devs@railsdeigner.com
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: nokogiri
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.16'
19
+ - - ">="
20
+ - !ruby/object:Gem::Version
21
+ version: 1.16.4
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - "~>"
27
+ - !ruby/object:Gem::Version
28
+ version: '1.16'
29
+ - - ">="
30
+ - !ruby/object:Gem::Version
31
+ version: 1.16.4
32
+ description: Add any icon library to a Ruby app, from Heroicons, to Lucide to Phosphor
33
+ (and others). Icons is library-agnostic, so you can add any library while using
34
+ the same interface.
35
+ email:
36
+ - devs@railsdesigner.com
16
37
  executables: []
17
38
  extensions: []
18
39
  extra_rdoc_files: []
19
40
  files:
41
+ - Gemfile
42
+ - Gemfile.lock
43
+ - README.md
44
+ - Rakefile
45
+ - bin/release
46
+ - icons.gemspec
20
47
  - lib/icons.rb
21
- homepage: https://github.com/Rails-Designer/icons
48
+ - lib/icons/assets/animated/bouncing-dots.svg
49
+ - lib/icons/assets/animated/faded-spinner.svg
50
+ - lib/icons/assets/animated/fading-dots.svg
51
+ - lib/icons/assets/animated/trailing-spinner.svg
52
+ - lib/icons/configuration.rb
53
+ - lib/icons/configuration/animated.rb
54
+ - lib/icons/configuration/boxicons.rb
55
+ - lib/icons/configuration/feather.rb
56
+ - lib/icons/configuration/flags.rb
57
+ - lib/icons/configuration/heroicons.rb
58
+ - lib/icons/configuration/linear.rb
59
+ - lib/icons/configuration/lucide.rb
60
+ - lib/icons/configuration/options.rb
61
+ - lib/icons/configuration/phosphor.rb
62
+ - lib/icons/configuration/radix.rb
63
+ - lib/icons/configuration/sidekickicons.rb
64
+ - lib/icons/configuration/tabler.rb
65
+ - lib/icons/configuration/weather.rb
66
+ - lib/icons/errors.rb
67
+ - lib/icons/icon.rb
68
+ - lib/icons/icon/attributes.rb
69
+ - lib/icons/icon/file_path.rb
70
+ - lib/icons/libraries.rb
71
+ - lib/icons/sync.rb
72
+ - lib/icons/sync/process_variants.rb
73
+ - lib/icons/sync/transformations.rb
74
+ - lib/icons/version.rb
75
+ homepage: https://railsdesigner.com/icons/
22
76
  licenses:
23
77
  - MIT
24
- metadata: {}
78
+ metadata:
79
+ homepage_uri: https://railsdesigner.com/icons/
80
+ source_code_uri: https://github.com/Rails-Designer/icons/
25
81
  rdoc_options: []
26
82
  require_paths:
27
83
  - lib
@@ -36,7 +92,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
36
92
  - !ruby/object:Gem::Version
37
93
  version: '0'
38
94
  requirements: []
39
- rubygems_version: 3.6.7
95
+ rubygems_version: 4.0.4
40
96
  specification_version: 4
41
97
  summary: Add any icon library to a Ruby app
42
98
  test_files: []