share_with 1.0.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.
data/README.md ADDED
@@ -0,0 +1,54 @@
1
+ # ShareWith
2
+
3
+ This is a Ruby gem thought to render the sharing links for the most popular networks.
4
+
5
+ *ShareWith* also introduces a syntax, built upon a YAML file, to declare parameters and templates, and
6
+ elements to self-describe the service itself.
7
+
8
+ This YAML file will be used to generate the object that renders the templates in plain HTML text.
9
+
10
+ [![Ruby](https://github.com/fabiomux/share_with/actions/workflows/main.yml/badge.svg)][wf_main]
11
+ [![Gem Version](https://badge.fury.io/rb/share_with.svg)][gem_version]
12
+
13
+ ## Installation
14
+
15
+ Install the gem and add to the application's Gemfile by executing:
16
+
17
+ $ bundle add share_with
18
+
19
+ If bundler is not being used to manage dependencies, install the gem by executing:
20
+
21
+ $ gem install share_with
22
+
23
+ ## Usage
24
+
25
+ As first step must *require* the gem:
26
+ ```ruby
27
+ require "share_with"
28
+ ```
29
+
30
+ Then we can instantiate the *Collection* class and the list of services to load:
31
+ ```ruby
32
+ @collection = ShareWith::Collection.new(services: ["twitter", "facebook"])
33
+ ```
34
+
35
+ Once filled the *params* required to render the template:
36
+ ```ruby
37
+ @collection.set_value_to_all("url", "https://freeaptitude.altervista.org/projects/share-with.html")
38
+ ```
39
+
40
+ Finally we get the HTML code as a text string:
41
+ ```ruby
42
+ @collection.render_all("icon")
43
+ ```
44
+
45
+ ## More Help
46
+
47
+ More info is available at:
48
+ - the [project page on the Freeaptitude blog][project_page];
49
+ - the [ShareWith Github wiki][share_with_wiki].
50
+
51
+ [project_page]: https://freeaptitude.altervista.org/projects/share-with.html "Project page on the Freeaptitude blog"
52
+ [share_with_wiki]: https://github.com/fabiomux/share_with/wiki "ShareWith wiki page on GitHub"
53
+ [wf_main]: https://github.com/fabiomux/share_with/actions/workflows/main.yml
54
+ [gem_version]: https://badge.fury.io/rb/share_with
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShareWith
4
+ #
5
+ # Entry point class that handles the service collection.
6
+ #
7
+ class Collection
8
+ attr_reader :services
9
+
10
+ def initialize(args = {})
11
+ @paths = args[:paths] || []
12
+ @extend_with = args[:extend_with] || []
13
+ @services = {}
14
+ return unless args[:services]
15
+
16
+ args[:services].each do |s|
17
+ @services[s.to_sym] = Service.new(s, { paths: @paths, extend_with: @extend_with })
18
+ end
19
+ end
20
+
21
+ def inspect(service, var_path)
22
+ @services[service.to_sym].inspect(var_path)
23
+ end
24
+
25
+ def param(service, key)
26
+ @services[service.to_sym].get_param(key) if @services.key?(service.to_sym)
27
+ end
28
+
29
+ def params_list(service)
30
+ @services[service.to_sym].params_list if @services.key?(service.to_sym)
31
+ end
32
+
33
+ def params_lists
34
+ res = []
35
+ services.each do |s|
36
+ res.push(*params_list(s))
37
+ end
38
+ res.uniq
39
+ end
40
+
41
+ def set_param(service, key, value)
42
+ @services[service.to_sym].set_param(key, value) if @services.key?(service.to_sym)
43
+ end
44
+
45
+ def set_param_to(services, key, value)
46
+ services.each do |s|
47
+ set_param s, key, value if @services.key? s.to_sym
48
+ end
49
+ end
50
+
51
+ def set_param_to_all(key, value)
52
+ @services.each_key do |s|
53
+ set_param(s, key, value)
54
+ end
55
+ end
56
+
57
+ def set_conditional_param(key, value)
58
+ if key.include? ":"
59
+ services, param = key.split(":")
60
+ set_param_to(services.split(","), param, value)
61
+ else
62
+ set_param_to_all(key, value)
63
+ end
64
+ end
65
+
66
+ def get_value(service, key)
67
+ @services[service.to_sym].get_value(key)
68
+ end
69
+
70
+ def set_value(service, key, value)
71
+ @services[service.to_sym].set_value(key, value)
72
+ end
73
+
74
+ def set_value_to(services, key, value)
75
+ services.each do |s|
76
+ set_value s, key, value if @services.key? s.to_sym
77
+ end
78
+ end
79
+
80
+ def set_value_to_all(key, value)
81
+ @services.each_key do |s|
82
+ set_value(s, key, value)
83
+ end
84
+ end
85
+
86
+ def create_or_reset(service)
87
+ if @services.include? service.to_sym
88
+ @services[service.to_sym].reset!
89
+ else
90
+ @services[service.to_sym] = Service.new(service, { paths: @paths, extend_with: @extend_with })
91
+ end
92
+ end
93
+
94
+ def reset_all!
95
+ @services.each do |_k, s|
96
+ s.reset!
97
+ end
98
+ end
99
+
100
+ def render(service, template)
101
+ @services[service.to_sym].render(template)
102
+ end
103
+
104
+ def render_all(template)
105
+ res = {}
106
+ @services.each do |k, _v|
107
+ res[k] = render(k, template)
108
+ end
109
+
110
+ res
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShareWith
4
+ #
5
+ # Methods to create the HTML code to represent the sharing links.
6
+ #
7
+ module LinkFactory
8
+ def render(tpl_name)
9
+ raise InvalidTemplate, tpl_name unless templates.key? tpl_name.to_sym
10
+
11
+ template = templates[tpl_name.to_sym]
12
+
13
+ merge_template!(template[:inherit_from], tpl_name) if template.key? :inherit_from
14
+
15
+ includes&.each do |k, _v|
16
+ i = includes[k]
17
+ i[:cache] = traverse_node(i[:content]) unless i.key? :cache
18
+ end
19
+
20
+ traverse_node(template[:content])
21
+ end
22
+
23
+ private
24
+
25
+ def traverse_node(node)
26
+ res = []
27
+
28
+ if node.instance_of? Hash
29
+ node.each do |k, v|
30
+ attr = ""
31
+ attr = expand_attributes(v) if v.key? :attr
32
+ res << (v.key?(:content) ? "<#{k} #{attr}>#{traverse_node(v[:content])}</#{k}>" : "<#{k} #{attr} />")
33
+ end
34
+ elsif node.instance_of? String
35
+ res << expand_all(node)
36
+ end
37
+
38
+ res.join
39
+ end
40
+
41
+ def expand_attributes(value)
42
+ value[:attr].keys.map do |k|
43
+ if value[:attr][k].nil?
44
+ str = k
45
+ else
46
+ str = expand_all(value[:attr][k])
47
+ str = "#{k}=\"#{str}\"" unless str.empty?
48
+ end
49
+ str
50
+ end.delete_if(&:empty?).join " "
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShareWith
4
+ #
5
+ # It provides the methods to replace the placeholders
6
+ # with the correct data pointed.
7
+ #
8
+ module Placeholders
9
+ def expand_all(str)
10
+ loop do
11
+ # Conditional params, if null remove the string
12
+ str = str.gsub(/\[([^ \]]+)\]/) do |x|
13
+ res = expand_all(x.delete("[]"))
14
+ @empty ? "" : res
15
+ end
16
+
17
+ str = str.gsub(%r{<([^ />]+)>}) do |x|
18
+ res = inspect(x.delete("<>"))
19
+ @empty = res.to_s.empty?
20
+ res.nil? ? x : res
21
+ end
22
+
23
+ break unless str =~ %r{<[^ />]+>}
24
+ end
25
+
26
+ str
27
+ end
28
+
29
+ def inspect(var_path)
30
+ context, var_name, var_select, attr_key = var_path.split(".")
31
+ var_select ||= :value
32
+ # puts @data,var_name,var_select if context.to_sym == :https
33
+ res = send(context.to_sym)
34
+
35
+ raise InvalidContext, context if res.nil?
36
+
37
+ return res.send(var_name.to_sym) if %i[keys values].include? var_name.to_sym
38
+
39
+ raise InvalidParam, [context, var_name].join(".") unless res.key? var_name.to_sym
40
+
41
+ res = res[var_name.to_sym]
42
+ case context.to_sym
43
+ when :params
44
+ value_selection(res, attr_key, var_select)
45
+ when :includes
46
+ res[:cache]
47
+ else
48
+ res
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def value_selection(val, attr_key, var_select)
55
+ case var_select.to_s.to_sym
56
+ when :key
57
+ val.key
58
+ when :attr
59
+ val.attr(attr_key)
60
+ else
61
+ val.value
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+
5
+ module ShareWith
6
+ #
7
+ # The single service classification.
8
+ #
9
+ class Service
10
+ include Placeholders
11
+ include LinkFactory
12
+
13
+ def initialize(service_name, args = {})
14
+ @name = service_name
15
+ @paths = [].concat args[:paths].to_a
16
+ @extend_with = args[:extend_with] || []
17
+ @paths << File.join(File.dirname(__FILE__), "services")
18
+ @data = load(@name)
19
+
20
+ @data = inherit_from(info[:inherit_from]) if info.key?(:inherit_from)
21
+
22
+ @extend_with = (info[:extend_with] || []).concat(@extend_with) if info.key?(:extend_with)
23
+
24
+ # if info.key?(:extend_with)
25
+ if @extend_with
26
+ # extend_with_layers(info[:extend_with])
27
+ extend_with_layers(@extend_with)
28
+ else
29
+ # Create the params when no more extensions are requested.
30
+ create_params
31
+ end
32
+ end
33
+
34
+ def param?(key)
35
+ params.key?(key.to_sym)
36
+ end
37
+
38
+ def params_list
39
+ params.keys
40
+ end
41
+
42
+ def get_param(key)
43
+ return params[key.to_sym].value if param?(key)
44
+ end
45
+
46
+ def set_param(key, value)
47
+ params[key.to_sym].value = value if param?(key)
48
+ end
49
+
50
+ def set_conditional_param(key, value)
51
+ if key.include? ":"
52
+ services, param = key.split(":")
53
+ set_param(param, value) if services.split(",").include?(@name)
54
+ else
55
+ set_param(key, value)
56
+ end
57
+ end
58
+
59
+ def get_value(key)
60
+ raise InvalidParam, "params.#{key}" unless param?(key.to_sym)
61
+
62
+ params[key.to_sym].value
63
+ end
64
+
65
+ def set_value(key, value)
66
+ raise InvalidParam, "params.#{key}" unless param?(key.to_sym)
67
+
68
+ params[key.to_sym].value = value
69
+ end
70
+
71
+ def reset!
72
+ params.each { |_k, v| v.reset! }
73
+ includes.each { |_k, v| v.delete :cache }
74
+ # extend_with_layers(@extend_with)
75
+ end
76
+
77
+ def add_layer(layer)
78
+ @extend_with << layer
79
+ extend_with_layers(layer)
80
+ end
81
+
82
+ private
83
+
84
+ def info
85
+ @data[:info]
86
+ end
87
+
88
+ def templates
89
+ @data[:templates]
90
+ end
91
+
92
+ def params
93
+ @data[:params]
94
+ end
95
+
96
+ def includes
97
+ @data[:includes]
98
+ end
99
+
100
+ def create_params
101
+ params.each do |k, v|
102
+ @data[:params][k] = ServiceDataFactory.create(v) if [String, Hash].include? v.class
103
+ end
104
+ end
105
+
106
+ def extend_with_layers(layers)
107
+ layers = [layers] if layers.instance_of? String
108
+ layers.each do |name|
109
+ name = name.split(".")
110
+ layer = load("_#{name[0]}")[name[1]]
111
+
112
+ raise InvalidLayer, [name[0], name[1]] if layer.nil?
113
+
114
+ @data[:params].merge!(layer[:params]) if layer.key? :params
115
+ @data[:includes].merge!(layer[:includes]) if layer.key? :includes
116
+ @data[:templates].merge!(layer[:templates]) if layer.key? :templates
117
+ end
118
+
119
+ create_params
120
+ end
121
+
122
+ def inherit_from(service_name)
123
+ data = load(service_name)
124
+ data[:info] = {}.merge! @data[:info]
125
+ data[:params].merge!(params) unless params.empty?
126
+ data[:includes].merge!(includes) unless includes.empty?
127
+ data[:templates].merge!(templates) unless templates.empty?
128
+
129
+ data
130
+ end
131
+
132
+ def merge_template!(service_name, tpl_name)
133
+ source = load(service_name)
134
+ @data[:includes].merge! source[:includes]
135
+ @data[:templates][tpl_name.to_sym].merge! source[:templates][tpl_name.to_sym]
136
+ # to don't merge again and again
137
+ @data[:templates][tpl_name.to_sym].delete :inherit_from
138
+ end
139
+
140
+ def load(service_name)
141
+ @paths.each do |d|
142
+ fname = File.join(File.expand_path(d), "#{service_name}.yaml")
143
+ next unless File.exist? fname
144
+
145
+ res = YAML.load_file(fname)
146
+ res[:includes] ||= {}
147
+ res[:params] ||= {}
148
+ res[:templates] ||= {}
149
+ return res
150
+ end
151
+ raise InvalidService, service_name
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,179 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShareWith
4
+ #
5
+ # Define the data type allowed.
6
+ #
7
+ module ServiceDataType
8
+ #
9
+ # This works as an abstract class to inherit.
10
+ #
11
+ class BasicDataType
12
+ attr_reader :label
13
+ attr_writer :value
14
+
15
+ def initialize(data)
16
+ @data = data
17
+ @default = data[:default]
18
+ @value = @default
19
+ end
20
+
21
+ def value(encoding = nil)
22
+ res = @value || @default || ""
23
+ res = res.send(encoding.to_sym) unless encoding.nil?
24
+
25
+ res
26
+ end
27
+
28
+ def attr(attr_value)
29
+ @data[:attr][attr_value]
30
+ end
31
+
32
+ def reset!
33
+ self.value = @default
34
+ end
35
+ end
36
+
37
+ #
38
+ # Define a boolean type.
39
+ #
40
+ class Boolean < BasicDataType
41
+ def value=(val)
42
+ @value = [true, "true", 1, "1"].include? val
43
+ end
44
+ end
45
+
46
+ #
47
+ # Define an integer type.
48
+ #
49
+ class Integer < BasicDataType
50
+ def value=(val)
51
+ @value = val.to_i
52
+ end
53
+ end
54
+
55
+ #
56
+ # Define a list of string type.
57
+ #
58
+ class StringList < BasicDataType
59
+ def initialize(data)
60
+ super
61
+ @value = []
62
+ @separator = data[:separator] || ","
63
+ reset!
64
+ end
65
+
66
+ def value
67
+ @value.to_text_list @separator
68
+ end
69
+
70
+ def value=(val)
71
+ val = val.split(@separator) if val.instance_of?(String)
72
+ val = "" if val.nil?
73
+
74
+ if val.empty?
75
+ clear
76
+ else
77
+ @value.concat val
78
+ end
79
+ end
80
+
81
+ def delete(val)
82
+ @value.delete val
83
+ end
84
+
85
+ def clear
86
+ @value.clear
87
+ end
88
+
89
+ def reset!
90
+ clear
91
+ @value = @default.instance_of?(String) ? @default.split(@separator).map(&:trim) : @default.clone
92
+ end
93
+ end
94
+
95
+ #
96
+ # Define a select value type.
97
+ #
98
+ class Select < BasicDataType
99
+ # aliasing the ancestor method to meet the new meanings
100
+ alias key value
101
+
102
+ def value
103
+ @data[:options][key].instance_of?(String) ? @data[:options][key] : @data[:options][key][:value]
104
+ end
105
+
106
+ def value=(val)
107
+ raise InvalidValue, [val, @data[:options].keys] unless @data[:options].key? val
108
+
109
+ @value = val
110
+ end
111
+
112
+ def attr(attr_value)
113
+ @data[:options][key][:attr][attr_value]
114
+ end
115
+ end
116
+
117
+ #
118
+ # Define an encoded text type.
119
+ #
120
+ class EncodedText < BasicDataType
121
+ def value
122
+ super :to_escaped_html
123
+ end
124
+ end
125
+
126
+ #
127
+ # Define a plain text type.
128
+ #
129
+ class PlainText < BasicDataType
130
+ def value
131
+ super :to_plain_text
132
+ end
133
+ end
134
+
135
+ #
136
+ # Define an encoded URL type.
137
+ #
138
+ class EncodedUrlParam < BasicDataType
139
+ def value
140
+ super :to_encoded_url_param
141
+ end
142
+ end
143
+
144
+ #
145
+ # Define a reference type.
146
+ #
147
+ class Reference < BasicDataType
148
+ def initialize(data)
149
+ super type: :reference, default: data
150
+ end
151
+
152
+ def value=(val)
153
+ # No value can be assigned being only a reference
154
+ end
155
+
156
+ def value
157
+ "<#{@value.gsub(/[^a-z._]+/, "")}>"
158
+ end
159
+ end
160
+ end
161
+
162
+ #
163
+ # Convert an argument to a ServiceDataType object.
164
+ #
165
+ class ServiceDataFactory
166
+ def self.create(args)
167
+ if args.instance_of? String
168
+ res = ServiceDataType::Reference.new(args)
169
+ else
170
+ type = args[:type].to_s.split("_").map(&:capitalize).join
171
+ raise InvalidDataType, type unless ShareWith::ServiceDataType.constants.include? type.to_sym
172
+
173
+ res = Object.const_get("ShareWith::ServiceDataType::#{type}").new(args)
174
+ end
175
+
176
+ res
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,77 @@
1
+ ---
2
+ :info:
3
+ :name: 'Service Name'
4
+ :classname: 'service-name'
5
+ :home_page: 'https://...'
6
+ :language_area: :general
7
+ :link: 'https://sharing-url.com/plus/path/and/tags?url=<params.url>&title=<params.title>[&nullable=<params.null>]'
8
+ :description: 'Service description used only as documentation'
9
+
10
+ :params:
11
+ :icon: '<params.icon_size.value>'
12
+ :icon_size:
13
+ :type: :select
14
+ :default: 'large'
15
+ :options:
16
+ small:
17
+ :value: 'https://cdn...'
18
+ :label: 'Small'
19
+ :attr:
20
+ url: ''
21
+ width: 20
22
+ height: 20
23
+ medium:
24
+ :value: '...'
25
+ :label: 'Medium'
26
+ :attr:
27
+ width: 30
28
+ height: 30
29
+ large:
30
+ :value: '...'
31
+ :label: 'Large'
32
+ :attr:
33
+ width: 48
34
+ height: 48
35
+ :icon_width: '<params.icon_size.attr.width>'
36
+ :icon_height: '<params.icon_size.attr.height>'
37
+ :title:
38
+ :default: ''
39
+ :type: :encoded_url_param
40
+ :url:
41
+ :default: ''
42
+ :type: :encoded_url_param
43
+
44
+ :includes:
45
+ :service_icon:
46
+ :content:
47
+ img:
48
+ :attr:
49
+ src: '<params.icon>'
50
+ alt: 'Share with <info.name>'
51
+ width: '<params.icon_width>'
52
+ height: '<params.icon_height>'
53
+ :templates:
54
+ :icon:
55
+ :content:
56
+ a:
57
+ :attr:
58
+ href: '<info.link>'
59
+ title: 'Share with <info.name>'
60
+ class: 'share-with-<info.classname>'
61
+ :content: '<includes.service_icon>'
62
+ :icon_and_text:
63
+ :content:
64
+ a:
65
+ :attr:
66
+ href: '<info.link>'
67
+ title: 'Share with <info.name>'
68
+ class: 'share-with-<info.classname>'
69
+ :content: '<includes.service_icon> <info.name>'
70
+ :text:
71
+ :content:
72
+ a:
73
+ :attr:
74
+ href: '<info.link>'
75
+ title: 'Share with <info.name>'
76
+ class: 'share-with-<info.classname>'
77
+ :content: '<info.name>'