yard_ghurt 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,262 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+ # frozen_string_literal: true
4
+
5
+ #--
6
+ # This file is part of YardGhurt.
7
+ # Copyright (c) 2019 Jonathan Bradley Whited (@esotericpig)
8
+ #
9
+ # YardGhurt is free software: you can redistribute it and/or modify
10
+ # it under the terms of the GNU Lesser General Public License as published by
11
+ # the Free Software Foundation, either version 3 of the License, or
12
+ # (at your option) any later version.
13
+ #
14
+ # YardGhurt is distributed in the hope that it will be useful,
15
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
+ # GNU Lesser General Public License for more details.
18
+ #
19
+ # You should have received a copy of the GNU Lesser General Public License
20
+ # along with YardGhurt. If not, see <https://www.gnu.org/licenses/>.
21
+ #++
22
+
23
+
24
+ require 'set'
25
+ require 'uri'
26
+
27
+ module YardGhurt
28
+ ###
29
+ # A "database" of anchor links specific to GitHub Flavored Markdown (GFM) & YARDoc.
30
+ #
31
+ # You can use this by itself to view what anchor IDs would be generated:
32
+ # al = YardGhurt::AnchorLinks.new()
33
+ #
34
+ # puts al.to_github_anchor_id('This is a test!')
35
+ # puts al.to_yard_anchor_id('This is a test!')
36
+ #
37
+ # # Output:
38
+ # # ---
39
+ # # this-is-a-test
40
+ # # This_is_a_test_
41
+ #
42
+ # Be aware that YARDoc depends on a common number that will be incremented for all duplicates,
43
+ # while GFM's number is only local to each specific duplicate:
44
+ # al = YardGhurt::AnchorLinks.new()
45
+ # name = 'This is a test!'
46
+ #
47
+ # puts al.to_yard_anchor_id(name) # This_is_a_test_
48
+ # puts al.to_yard_anchor_id(name) # This_is_a_test_
49
+ #
50
+ # puts al.to_github_anchor_id(name) # this-is-a-test
51
+ # puts al.to_github_anchor_id(name) # this-is-a-test
52
+ #
53
+ # al << name # Officially add it to the database
54
+ #
55
+ # # Instead of being 0 & 0, will be 0 & 1 (incremented),
56
+ # # even without being added to the database
57
+ # puts al.to_yard_anchor_id(name) # This_is_a_test_0
58
+ # puts al.to_yard_anchor_id(name) # This_is_a_test_1
59
+ #
60
+ # puts al.to_github_anchor_id(name) # this-is-a-test-1
61
+ # puts al.to_github_anchor_id(name) # this-is-a-test-1
62
+ #
63
+ # name = 'This is another test!'
64
+ # al << name # Officially add it to the database
65
+ #
66
+ # # Instead of being 0 & 1, will be 2 & 3 (global increment),
67
+ # # even without being added to the database
68
+ # puts al.to_yard_anchor_id(name) # This_is_another_test_2
69
+ # puts al.to_yard_anchor_id(name) # This_is_another_test_3
70
+ #
71
+ # puts al.to_github_anchor_id(name) # this-is-another-test-1
72
+ # puts al.to_github_anchor_id(name) # this-is-another-test-1
73
+ #
74
+ # @author Jonathan Bradley Whited (@esotericpig)
75
+ # @since 1.0.0
76
+ #
77
+ # @see GFMFixerTask
78
+ ###
79
+ class AnchorLinks
80
+ # @return [Hash] the GFM-style anchor IDs pointing to their YARDoc ID equivalents that have been added
81
+ attr_reader :anchor_ids
82
+
83
+ # @return [Set] the YARDoc anchor IDs that have been added
84
+ attr_accessor :yard_anchor_ids
85
+
86
+ # @return [Integer] the next YARDoc number to use if there is a duplicate anchor ID
87
+ attr_accessor :yard_dup_num
88
+
89
+ def initialize()
90
+ reset()
91
+ end
92
+
93
+ # Reset the database back to its fresh, pristine self,
94
+ # including common numbers for duplicates.
95
+ def reset()
96
+ @anchor_ids = {}
97
+ @yard_anchor_ids = Set.new()
98
+ @yard_dup_num = 0
99
+ end
100
+
101
+ # (see #add_anchor)
102
+ def <<(name)
103
+ return add_anchor(name)
104
+ end
105
+
106
+ # (see #store_anchor)
107
+ def []=(github_anchor_id,yard_anchor_id)
108
+ return store_anchor(github_anchor_id,yard_anchor_id)
109
+ end
110
+
111
+ # Convert +name+ (header text) to a GFM and YARDoc anchor ID and add the IDs to the database.
112
+ #
113
+ # @param name [String] the name (header text) to convert to anchor IDs and add to the database
114
+ #
115
+ # @return [self]
116
+ def add_anchor(name)
117
+ store_anchor(to_github_anchor_id(name),to_yard_anchor_id(name))
118
+
119
+ return self
120
+ end
121
+
122
+ # Merge +anchor_ids+ with {anchor_ids} and {yard_anchor_ids}.
123
+ #
124
+ # @param anchor_ids [Hash] the anchor IDs (of GFM anchor IDs to YARDoc anchor IDs) to merge
125
+ def merge_anchor_ids!(anchor_ids)
126
+ @anchor_ids.merge!(anchor_ids)
127
+ @yard_anchor_ids.merge(anchor_ids.values)
128
+
129
+ return @anchor_ids
130
+ end
131
+
132
+ # Store & associate key +github_anchor_id+ with value +yard_anchor_id+,
133
+ # and add +yard_anchor_id+ to the YARDoc anchor IDs.
134
+ #
135
+ # @param github_anchor_id [String] the GitHub anchor ID; the key
136
+ # @param yard_anchor_id [String] the YARDoc anchor ID; the value
137
+ #
138
+ # @return [String] the +yard_anchor_id+ added
139
+ def store_anchor(github_anchor_id,yard_anchor_id)
140
+ @anchor_ids[github_anchor_id] = yard_anchor_id
141
+ @yard_anchor_ids << yard_anchor_id
142
+
143
+ return yard_anchor_id
144
+ end
145
+
146
+ def anchor_ids=(anchor_ids)
147
+ @anchor_ids = anchor_ids
148
+ @yard_anchor_ids.merge(anchor_ids.values)
149
+
150
+ return @anchor_ids
151
+ end
152
+
153
+ # (see #anchor_id)
154
+ def [](github_anchor_id)
155
+ return anchor_id(github_anchor_id)
156
+ end
157
+
158
+ # Get the YARDoc anchor ID associated with this +github_anchor_id+.
159
+ #
160
+ # @param github_anchor_id [String] the GitHub anchor ID key to look up
161
+ #
162
+ # @return [String,nil] the YARDoc anchor ID associated with +github_anchor_id+
163
+ def anchor_id(github_anchor_id)
164
+ return @anchor_ids[github_anchor_id]
165
+ end
166
+
167
+ # Check if +id+ exists in the database of YARDoc anchor IDs
168
+ #
169
+ # @param id [String] the YARDoc anchor ID to check
170
+ #
171
+ # @return [true,false] whether this ID exists in the database of YARDoc anchor IDs
172
+ def yard_anchor_id?(id)
173
+ return @yard_anchor_ids.include?(id)
174
+ end
175
+
176
+ # Convert +name+ (header text) to a GitHub anchor ID.
177
+ #
178
+ # If the converted ID already exists in the database,
179
+ # then the ID will be updated according to GFM rules.
180
+ #
181
+ # @param name [String] the name (header text) to convert
182
+ #
183
+ # @return [String] the converted GitHub anchor ID for this database
184
+ #
185
+ # @see https://gist.github.com/asabaylus/3071099#gistcomment-2834467
186
+ # @see https://github.com/jch/html-pipeline/blob/master/lib/html/pipeline/toc_filter.rb
187
+ def to_github_anchor_id(name)
188
+ id = name.dup()
189
+
190
+ id.strip!()
191
+ id.gsub!(/&[^;]+;/,'') # Remove entities: &...;
192
+ id.gsub!(/[^\p{Word}\- ]/u,'')
193
+ id.tr!(' ','-')
194
+
195
+ if RUBY_VERSION >= '2.4'
196
+ id.downcase!(:ascii)
197
+ else
198
+ id.downcase!()
199
+ end
200
+
201
+ id = URI.escape(id) # For non-English languages
202
+
203
+ # Duplicates
204
+ dup_num = 1
205
+ orig_id = id.dup()
206
+
207
+ while @anchor_ids.key?(id)
208
+ id = "#{orig_id}-#{dup_num}"
209
+ dup_num += 1
210
+ end
211
+
212
+ return id
213
+ end
214
+
215
+ # Dumps the key-value database of GFM & YARDoc anchor IDs.
216
+ #
217
+ # @return [String] the database of anchor IDs as a String
218
+ def to_s()
219
+ s = ''.dup()
220
+
221
+ @anchor_ids.keys.sort_by{|key| key.to_s()}.each do |key|
222
+ s << "[#{key}] => '#{@anchor_ids[key]}'\n"
223
+ end
224
+
225
+ return s
226
+ end
227
+
228
+ # Convert +name+ (header text) to a YARDoc anchor ID.
229
+ #
230
+ # If the converted ID already exists in the database,
231
+ # then the ID will be updated according to YARDoc rules,
232
+ # which requires incrementing a common number variable.
233
+ #
234
+ # The logic for this is pulled from +doc/app.js#generateTOC()+,
235
+ # which you can generate using +rake yard+ or +yardoc+ on the command line.
236
+ #
237
+ # @note Be aware that this will increment a common number variable
238
+ # every time you call this with a duplicate.
239
+ #
240
+ # @param name [String] the name (header text) to convert
241
+ #
242
+ # @return [String] the converted YARDoc anchor ID for this database
243
+ def to_yard_anchor_id(name)
244
+ id = name.dup()
245
+
246
+ id.strip!()
247
+ id.gsub!(/&[^;]+;/,'_') # Replace entities: &...;
248
+ id.gsub!(/[^a-z0-9-]/i,'_')
249
+ id = URI.escape(id) # For non-English languages
250
+
251
+ # Duplicates
252
+ orig_id = id.dup()
253
+
254
+ while @yard_anchor_ids.include?(id)
255
+ id = "#{orig_id}#{@yard_dup_num}"
256
+ @yard_dup_num += 1
257
+ end
258
+
259
+ return id
260
+ end
261
+ end
262
+ end