card-mod-collection 0.11.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,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: cabe6e7e22075e4414381fa6929b7c012c03049d1eb477eca3bda54eb0cf4508
4
+ data.tar.gz: 815252c283d17851de677f1692b98733524387b2f8fc07e3fa3decc24480aaa7
5
+ SHA512:
6
+ metadata.gz: dbf957e00b6dd53cef2cd622497a84fad0ad24f1bde19f77cb7c1dfc79df8a3c890d401e59ce422ff8c5dd50e926177314f74f3441d27c13182a33a8104f70c4
7
+ data.tar.gz: 8978ebdad15565e224b64de457b6c5dafb5bc7172bcaec56412bbe64cb4adf7d4593e97eff60b49b131a301cf53232210dccbf0749dbb9c16956bebce393e82a
@@ -0,0 +1,202 @@
1
+ # ~~~~~~~~~~~~ READING ITEMS ~~~~~~~~~~~~
2
+
3
+ # While each of the three main methods for returning lists of items can handle arguments,
4
+ # they are most commonly used without them.
5
+
6
+ # @return [Array] list of Card::Name objects
7
+ # @param args [Hash]
8
+ # @option args [String] :content override card content
9
+ # @option args [String, Card::Name, Symbol] :context name in whose context relative items
10
+ # will be interpreted. For example. +A in context of B is interpreted as B+A
11
+ # context defaults to pointer card's name. If value is `:raw`, then name is not
12
+ # contextualized
13
+ # @option args [String, Integer] :limit max number of cards to return
14
+ # @option args [String, Integer] :offset begin after the offset-th item
15
+ def item_names args={}
16
+ context = args[:context]
17
+ item_strings(args).map do |item|
18
+ clean_item_name item, context
19
+ end.compact
20
+ end
21
+
22
+ def first_name args={}
23
+ item_names(args).first
24
+ end
25
+
26
+ def first_card args={}
27
+ return unless (name = first_name)
28
+ fetch_item_card name, args
29
+ end
30
+
31
+ def first_code
32
+ first_card&.codename
33
+ end
34
+
35
+ def first_id
36
+ first_card&.id
37
+ end
38
+
39
+ # @return [Array] list of integers (card ids of items)
40
+ # @param args [Hash] see #item_names
41
+ def item_ids args={}
42
+ item_names(args).map { |name| Card.fetch_id name }.compact
43
+ end
44
+
45
+ # @return [Array] list of Card objects
46
+ # @param args [Hash] see #item_names for additional options
47
+ # @option args [String] :complete keyword to use in searching among items
48
+ # @option args [True/False] :known_only if true, return only known cards
49
+ # @option args [String] :type name of type to be used for unknown cards
50
+ def item_cards args={}
51
+ return item_cards_search(args) if args[:complete]
52
+ return known_item_cards(args) if args[:known_only]
53
+ all_item_cards args
54
+ end
55
+
56
+ # #item_name, #item_id, and #item_card each return a single item, rather than an array.
57
+ %i[name id card].each do |obj|
58
+ define_method "item_#{obj}" do |args={}|
59
+ send("item_#{obj}s", args.merge(limit: 1)).first
60
+ end
61
+ end
62
+
63
+ # for override, eg by json
64
+ def item_value item_name
65
+ item_name
66
+ end
67
+
68
+ # ~~~~~~~~~~~~ ALTERING ITEMS ~~~~~~~~~~~~
69
+
70
+ # set card content based on array and save card
71
+ # @param array [Array] list of strings/names (Cardish)
72
+ def items= array
73
+ items_to_content array
74
+ save!
75
+ end
76
+
77
+ # append item to list (does not save)
78
+ # @param cardish [Cardish]
79
+ def << cardish
80
+ add_item cardish
81
+ end
82
+
83
+ # append item to list (does not save)
84
+ # @param cardish [String, Card::Name] item name
85
+ # @param allow_duplicates [True/False] permit duplicate items (default is False)
86
+ def add_item cardish, allow_duplicates=false
87
+ return if !allow_duplicates && include_item?(cardish)
88
+
89
+ items = item_strings << cardish
90
+ items_to_content items
91
+ end
92
+
93
+ # append item to list and save card
94
+ # @param name [String, Card::Name] item name
95
+ def add_item! name
96
+ add_item(name) && save!
97
+ end
98
+
99
+ # remove item from list
100
+ # @param cardish [String, Card::Name] item to drop
101
+ def drop_item cardish
102
+ drop_item_name = Card::Name[cardish]
103
+ items_to_content(item_names.reject { |item_name| item_name == drop_item_name })
104
+ end
105
+
106
+ # remove item from list and save card
107
+ # @param cardish [String, Card::Name] item to drop
108
+ def drop_item! cardish
109
+ drop_item cardish
110
+ save!
111
+ end
112
+
113
+ # insert item into list at specified location
114
+ # @param index [Integer] Array index in which to insert item (0 is first)
115
+ # @param name [String, Card::Name] item name
116
+ def insert_item index, name
117
+ new_names = item_names
118
+ new_names.delete name
119
+ new_names.insert index, name
120
+ items_to_content new_names
121
+ end
122
+
123
+ # insert item into list at specified location and save
124
+ # @param index [Integer] Array index in which to insert item (0 is first)
125
+ # @param name [String, Card::Name] item name
126
+ def insert_item! index, name
127
+ insert_item index, name
128
+ save!
129
+ end
130
+
131
+ # ~~~~~~~~~~~~ READING ITEM HELPERS ~~~~~~~~~~~~
132
+
133
+ # Warning: the following methods, while available for use, may be subject to change
134
+
135
+ # #item_cards helpers
136
+
137
+ def item_cards_search query
138
+ Card::Query.run query.reverse_merge(referred_to_by: name, limit: 0)
139
+ end
140
+
141
+ def known_item_cards args={}
142
+ item_names(args).map { |name| Card.fetch name }.compact
143
+ end
144
+
145
+ def all_item_cards args={}
146
+ names = args[:item_names] || item_names(args)
147
+ names.map { |name| fetch_item_card name, args }
148
+ end
149
+
150
+ # TODO: support type_code and type_id. (currently type)
151
+ # uses name, because its most common use is from CQL
152
+ def item_type
153
+ opt = options_rule_card
154
+ # FIXME: need better recursion prevention
155
+ return if !opt || opt == self
156
+ opt.item_type
157
+ end
158
+
159
+ def item_strings args={}
160
+ items = raw_item_strings(args[:content] || content)
161
+ return items unless args.present?
162
+
163
+ filtered_items items, args.slice(:limit, :offset)
164
+ end
165
+
166
+ def raw_item_strings content
167
+ content.to_s.split(/\n+/).map { |i| strip_item i }
168
+ end
169
+
170
+ private
171
+
172
+ def filtered_items items, limit: 0, offset: 0
173
+ limit = limit.to_i
174
+ offset = offset.to_i
175
+ return items unless limit.positive? || offset.positive?
176
+
177
+ items[offset, (limit.zero? ? items.size : limit)] || []
178
+ end
179
+
180
+ def fetch_item_card name, args={}
181
+ Card.fetch name, new: new_unknown_item_args(args)
182
+ end
183
+
184
+ def new_unknown_item_args args
185
+ itype = args[:type] || item_type
186
+ itype ? { type: itype } : {}
187
+ end
188
+
189
+ def clean_item_name item, context
190
+ item = item.to_name
191
+ return item if context == :raw
192
+ context ||= context_card.name
193
+ item.absolute_name context
194
+ rescue Card::Error::NotFound
195
+ # eg for invalid ids or codenames
196
+ # "Invalid Item: #{item}".to_name
197
+ nil
198
+ end
199
+
200
+ def strip_item item
201
+ item.gsub(/\[\[|\]\]/, "").strip
202
+ end
@@ -0,0 +1,64 @@
1
+ MAX_ANONYMOUS_SEARCH_PARAM = 1000
2
+
3
+ format do
4
+ def limit
5
+ @limit ||= contextual_param(:limit) || default_limit
6
+ end
7
+
8
+ def offset
9
+ @offset ||= contextual_param(:offset) || 0
10
+ end
11
+
12
+ def search_with_params
13
+ @search_with_params ||= card.item_names
14
+ end
15
+
16
+ def count_with_params
17
+ @count_with_params ||= card.item_names.count
18
+ end
19
+
20
+ def total_pages
21
+ return 1 if limit.zero?
22
+ ((count_with_params - 1) / limit).to_i
23
+ end
24
+
25
+ def current_page
26
+ (offset / limit).to_i
27
+ end
28
+
29
+ # for override
30
+ def extra_paging_path_args
31
+ {}
32
+ end
33
+
34
+ private
35
+
36
+ def contextual_param param
37
+ env_search_param(param) || voo_search_param(param)
38
+ end
39
+
40
+ def env_search_param param
41
+ enforcing_legal_limit param do
42
+ val = Env.params[param]
43
+ val.to_i if focal? && val.present?
44
+ end
45
+ end
46
+
47
+ def enforcing_legal_limit param
48
+ yield.tap do |val|
49
+ enforce_legal_limit! val if param == :limit
50
+ end
51
+ end
52
+
53
+ def enforce_legal_limit! val
54
+ return if Card::Auth.signed_in? || !val || val <= MAX_ANONYMOUS_SEARCH_PARAM
55
+
56
+ raise Card::Error::PermissionDenied,
57
+ "limit parameter exceeds maximum for anonymous users " \
58
+ "(#{MAX_ANONYMOUS_SEARCH_PARAM})"
59
+ end
60
+
61
+ def voo_search_param param
62
+ voo&.cql&.dig(param)&.to_i
63
+ end
64
+ end
@@ -0,0 +1,90 @@
1
+ #! no set module
2
+
3
+ # render paging links
4
+ class PagingLinks
5
+ def initialize total_pages, current_page
6
+ @total = total_pages
7
+ @current = current_page
8
+ end
9
+
10
+ # @param window [integer] number of page links shown left and right
11
+ # of the current page
12
+ # @example: current page = 5, window = 2
13
+ # |<<|1|...|3|4|[5]|6|7|...|10|>>|
14
+ # @yield [text, page, status, options] block to build single paging link
15
+ # @yieldparam status [Symbol] :active (for current page) or :disabled
16
+ # @yieldparam page [Integer] page number, first page is 0
17
+ # @return [Array<String>]
18
+ def build window=2, &block
19
+ @render_item = block
20
+ links window
21
+ end
22
+
23
+ private
24
+
25
+ def links window
26
+ @window_start = [@current - window, 0].max
27
+ @window_end = [@current + window, @total].min
28
+ left_part + window_part + right_part
29
+ end
30
+
31
+ # the links around the current page
32
+ def window_part
33
+ (@window_start..@window_end).map do |page|
34
+ direct_page_link page
35
+ end.compact
36
+ end
37
+
38
+ def left_part
39
+ [
40
+ previous_page_link,
41
+ (direct_page_link 0 if @window_start.positive?),
42
+ (ellipse if @window_start > 1)
43
+ ].compact
44
+ end
45
+
46
+ def right_part
47
+ [
48
+ (ellipse if @total > @window_end + 1),
49
+ (direct_page_link @total if @total > @window_end),
50
+ next_page_link
51
+ ].compact
52
+ end
53
+
54
+ def previous_page_link
55
+ paging_item '<span aria-hidden="true">&laquo;</span>', previous_page,
56
+ "aria-label" => "Previous", status: :previous
57
+ end
58
+
59
+ def next_page_link
60
+ paging_item '<span aria-hidden="true">&raquo;</span>', next_page,
61
+ "aria-label" => "Next", status: :next
62
+ end
63
+
64
+ def direct_page_link page
65
+ return unless page >= 0 && page <= @total
66
+ paging_item page + 1, page
67
+ end
68
+
69
+ def ellipse
70
+ paging_item "<span>...</span>", nil, status: :ellipses
71
+ end
72
+
73
+ def paging_item text, page, options={}
74
+ status =
75
+ if page == @current
76
+ :current
77
+ else
78
+ options.delete :status
79
+ end
80
+ @render_item.call text, page, status, options
81
+ end
82
+
83
+ def previous_page
84
+ @current.positive? ? @current - 1 : false
85
+ end
86
+
87
+ def next_page
88
+ @current < @total ? @current + 1 : false
89
+ end
90
+ end
@@ -0,0 +1,111 @@
1
+ format :html do
2
+ PAGE_LI_CLASS = { ellipses: "disabled", current: "active" }.freeze
3
+
4
+ def with_paging path_args={}
5
+ with_paging_path_args path_args do
6
+ output [yield(@paging_path_args), _render_paging]
7
+ end
8
+ end
9
+
10
+ view :paging, cache: :never do
11
+ return "" unless paging_needed?
12
+ <<-HTML
13
+ <nav>
14
+ <ul class="pagination paging">
15
+ #{paging_links.join}
16
+ </ul>
17
+ </nav>
18
+ HTML
19
+ end
20
+
21
+ def paging_links
22
+ PagingLinks.new(total_pages, current_page).build do |text, page, status, options|
23
+ page_link_li text, page, status, options
24
+ end
25
+ end
26
+
27
+ # First page is 0 (not 1)
28
+ def page_link_li text, page, status, options={}
29
+ wrap_with :li, class: page_link_li_class(status) do
30
+ page_link text, page, options
31
+ end
32
+ end
33
+
34
+ def page_link_li_class status
35
+ ["page-item", PAGE_LI_CLASS[status]].compact.join " "
36
+ end
37
+
38
+ def page_link text, page, options
39
+ return content_tag(:div, text.html_safe, class: "page-link") unless page
40
+
41
+ options.merge! class: "card-paging-link slotter page-link",
42
+ remote: true,
43
+ path: page_link_path_args(page)
44
+ link_to raw(text), options
45
+ end
46
+
47
+ def with_paging_path_args args
48
+ tmp = @paging_path_args
49
+ @paging_path_args = paging_path_args args
50
+ yield
51
+ ensure
52
+ @paging_path_args = tmp
53
+ end
54
+
55
+ def paging_path_args local_args={}
56
+ @paging_path_args ||= {}
57
+ @paging_path_args.reverse_merge!(limit: limit, offset: offset)
58
+ @paging_path_args.merge! extra_paging_path_args
59
+ @paging_path_args.merge local_args
60
+ end
61
+
62
+ def page_link_path_args page
63
+ paging_path_args.merge offset: page * limit
64
+ end
65
+
66
+ def paging_needed?
67
+ return false if limit < 1
68
+ return false if fewer_results_than_limit? # avoid extra count search
69
+
70
+ # count search result instead
71
+ limit < count_with_params
72
+ end
73
+
74
+ # clear we don't need paging even before running count query
75
+ def fewer_results_than_limit?
76
+ return false unless offset.zero?
77
+
78
+ limit > offset + search_with_params.length
79
+ end
80
+ end
81
+
82
+ format :json do
83
+ def page_link_path_args page
84
+ {
85
+ limit: limit,
86
+ offset: page * limit,
87
+ item: default_item_view, # hack. need standard voo handling
88
+ format: :json
89
+ }.merge extra_paging_path_args
90
+ end
91
+
92
+ view :paging_urls, cache: :never do
93
+ return {} unless total_pages > 1
94
+
95
+ { paging: paging_urls_hash }
96
+ end
97
+
98
+ def paging_urls_hash
99
+ hash = {}
100
+ PagingLinks.new(total_pages, current_page).build do |_text, page, status, _options|
101
+ add_paging_url hash, page, status
102
+ end
103
+ hash
104
+ end
105
+
106
+ def add_paging_url hash, page, status
107
+ return unless page && status.in?(%i[next previous])
108
+
109
+ hash[status] = path page_link_path_args(page)
110
+ end
111
+ end
@@ -0,0 +1,49 @@
1
+
2
+ def extended_item_cards context=nil
3
+ items = item_cards limit: "", context: (context || self).name
4
+ list = []
5
+ book = ::Set.new # avoid loops
6
+ extend_item_list items, list, book until items.empty?
7
+ list
8
+ end
9
+
10
+ def extended_item_contents context=nil
11
+ extended_item_cards(context).map(&:item_names).flatten
12
+ end
13
+
14
+ format do
15
+ delegate :extended_item_contents, to: :card
16
+ end
17
+
18
+ private
19
+
20
+ def extend_item_list items, list, book
21
+ item = items.shift
22
+ return if already_extended? item, book
23
+ if item.collection?
24
+ # keep items in order
25
+ items.unshift(*item.item_cards)
26
+ else # no further level of items
27
+ list << item
28
+ end
29
+ end
30
+
31
+ def already_extended? item, book
32
+ return true if book.include? item
33
+ book << item
34
+ false
35
+ end
36
+
37
+ # def extended_list context=nil
38
+ # context = (context ? context.name : name)
39
+ # args = { limit: "" }
40
+ # item_cards(args.merge(context: context)).map do |x|
41
+ # x.item_cards(args)
42
+ # end.flatten.map do |x|
43
+ # x.item_cards(args)
44
+ # end.flatten.map do |y|
45
+ # y.item_names(args)
46
+ # end.flatten
47
+ # # this could go on and on. more elegant to recurse until you don't have
48
+ # # a collection
49
+ # end
@@ -0,0 +1,130 @@
1
+ def item_names _args={}
2
+ format._render_raw.split(/[,\n]/)
3
+ end
4
+
5
+ def item_cards _args={} # FIXME: this is inconsistent with item_names
6
+ [self]
7
+ end
8
+
9
+ def item_type
10
+ nil
11
+ end
12
+
13
+ def item_keys args={}
14
+ item_names(args).map do |item|
15
+ item.to_name.key
16
+ end
17
+ end
18
+
19
+ def item_count args={}
20
+ item_names(args).size
21
+ end
22
+
23
+ def items_to_content array
24
+ items = array.map { |i| standardize_item i }.reject(&:blank?)
25
+ self.content = items.to_pointer_content
26
+ end
27
+
28
+ def standardize_item item
29
+ Card::Name[item]
30
+ end
31
+
32
+ def include_item? item
33
+ item_names.include? Card::Name[item]
34
+ end
35
+
36
+ def add_item item
37
+ return if include_item? item
38
+ items_to_content(items_strings << item)
39
+ end
40
+
41
+ def drop_item item
42
+ item = Card::Name[item]
43
+ return unless include_item? item
44
+ items_to_content(item_names.reject { |i| i == item })
45
+ end
46
+
47
+ def insert_item index, name
48
+ new_names = item_names
49
+ new_names.delete name
50
+ new_names.insert index, name
51
+ items_to_content new_names
52
+ end
53
+
54
+ def replace_item old, new
55
+ return unless include_item? old
56
+ drop_item old
57
+ add_item new
58
+ end
59
+
60
+ # I think the following should work as add_item...
61
+ #
62
+ def add_id id
63
+ add_item "~#{id}"
64
+ end
65
+
66
+ def drop_id id
67
+ drop_item "~#{id}"
68
+ end
69
+
70
+ def insert_id index, id
71
+ insert_item index, "~#{id}"
72
+ end
73
+
74
+ format do
75
+ def item_links _args={}
76
+ raw(render_core).split(/[,\n]/)
77
+ end
78
+
79
+ def nest_item cardish, options={}, &block
80
+ options = item_view_options options
81
+ options[:nest_name] = Card::Name[cardish].s
82
+ nest cardish, options, &block
83
+ end
84
+
85
+ def implicit_item_view
86
+ view = voo_items_view || default_item_view
87
+ Card::View.normalize view
88
+ end
89
+
90
+ def voo_items_view
91
+ return unless voo && (items = voo.items)
92
+ items[:view]
93
+ end
94
+
95
+ def default_item_view
96
+ :name
97
+ end
98
+
99
+ def item_view_options new_options={}
100
+ options = (voo.items || {}).clone
101
+ options = options.merge new_options
102
+ options[:view] ||= implicit_item_view
103
+ determine_item_view_options_type options
104
+ options
105
+ end
106
+
107
+ def determine_item_view_options_type options
108
+ return if options[:type]
109
+ type_from_rule = card.item_type
110
+ options[:type] = type_from_rule if type_from_rule
111
+ end
112
+
113
+ def listing listing_cards, item_args={}
114
+ listing_cards.map do |item_card|
115
+ nest_item item_card, item_args do |rendered, item_view|
116
+ wrap_item rendered, item_view
117
+ end
118
+ end
119
+ end
120
+
121
+ def wrap_item item, _args={}
122
+ item # no wrap in base
123
+ end
124
+ end
125
+
126
+ format :html do
127
+ def wrap_item rendered, item_view
128
+ %(<div class="item-#{item_view}">#{rendered}</div>)
129
+ end
130
+ end
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: card-mod-collection
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.11.0
5
+ platform: ruby
6
+ authors:
7
+ - Ethan McCutchen
8
+ - Philipp Kühl
9
+ - Gerry Gleason
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2020-12-24 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: card
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - '='
20
+ - !ruby/object:Gem::Version
21
+ version: 1.101.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - '='
27
+ - !ruby/object:Gem::Version
28
+ version: 1.101.0
29
+ description: ''
30
+ email:
31
+ - info@decko.org
32
+ executables: []
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - set/abstract/items.rb
37
+ - set/abstract/paging.rb
38
+ - set/abstract/paging/paging_links.rb
39
+ - set/abstract/paging/paging_views.rb
40
+ - set/all/extended.rb
41
+ - set/all/item.rb
42
+ homepage: http://decko.org
43
+ licenses:
44
+ - GPL-3.0
45
+ metadata:
46
+ card-mod: collection
47
+ post_install_message:
48
+ rdoc_options: []
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '2.5'
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubygems_version: 3.0.3
63
+ signing_key:
64
+ specification_version: 4
65
+ summary: collection (list and search)
66
+ test_files: []