ragerender 0.1.1

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,77 @@
1
+ require 'rsec'
2
+ require 'strscan'
3
+
4
+ module RageRender
5
+ module Language
6
+ extend Rsec::Helpers
7
+
8
+ Variable = Struct.new 'Variable', :path
9
+ Loop = Struct.new 'Loop', :path
10
+ Conditional = Struct.new 'Conditional', :reversed, :lhs, :operator, :rhs
11
+ Function = Struct.new 'Function', :name, :params
12
+ Layout = Struct.new 'Layout', :name
13
+ EndTag = Struct.new 'EndTag'
14
+
15
+ def self.optional p, default=nil
16
+ p | ''.r.map {|_| default}
17
+ end
18
+
19
+ TEXT = /[^\[\]\|\<\>]+/.r
20
+
21
+ # IDENT parses names: 'names' => 'names'
22
+ # IDENT parses underscores: 'underscore_name' => 'underscore_name'
23
+ # IDENT parses numbers: 'name_with_1_number' => 'name_with_1_number'
24
+ # IDENT parses capitals: 'name_with_Capital' => 'name_with_Capital'
25
+ IDENT = /[a-zA-Z0-9_]+/.r
26
+
27
+ # PATH parses names: 'names' => ['names']
28
+ # PATH parses dotted paths: 'dotted.name' => ['dotted', 'name']
29
+ PATH = IDENT.join('.'.r).even
30
+
31
+ OPERATOR = %w{= != ~ !~ < > <= >= % !%}.map(&:r).reduce {|a, b| a | b }
32
+
33
+ # VARIABLE parses names: 'v:name' => Variable.new(['name'])
34
+ # VARIABLE parses dotted paths: 'v:dotted.name' => Variable.new(['dotted', 'name'])
35
+ VARIABLE = ('v:'.r >> PATH).map {|path| Variable.new path }
36
+ LOOP = ('l:'.r >> PATH).map {|path| Loop.new path }
37
+
38
+ # CONDITIONAL tests for truthiness: 'c:variable' => Conditional.new(false, Variable.new(['variable']), nil, nil)
39
+ # CONDITIONAL tests for falsiness: 'c:!variable' => Conditional.new(true, Variable.new(['variable']), nil, nil)
40
+ # CONDITIONAL tests for equality: 'c:variable=My comic about bees' => Conditional.new(false, Variable.new(['variable']), '=', 'My comic about bees')
41
+ # CONDITIONAL tests for inequality: 'c:variable!=My comic about bees' => Conditional.new(false, Variable.new(['variable']), '!=', 'My comic about bees')
42
+ CONDITIONAL = ('c:'.r >> seq_(
43
+ /!?/.r.map {|c| c == '!' },
44
+ PATH.map {|p| Variable.new(p) },
45
+ optional(OPERATOR),
46
+ optional(VARIABLE | TEXT))
47
+ ).map {|(reversed, lhs, operator, rhs)| Conditional.new reversed, lhs, operator, rhs }
48
+
49
+ # FUNCTION with no arguments: 'f:cowsay' => Function.new("cowsay", [])
50
+ # FUNCTION with a variable argument: 'f:js|v:foo' => Function.new('js', [Variable.new(['foo'])])
51
+ # FUNCTION with literal arguments: 'f:add|2|3' => Function.new('add', ['2', '3'])
52
+ FUNCTION = ('f:'.r >> seq(IDENT, ('|'.r >> (VARIABLE | TEXT)).star)).map {|(name, params)| Function.new name, params }
53
+
54
+ # TAG matches variable tags: '[v:value]' => Variable.new(["value"])
55
+ # TAG matches loop tags: '[l:loop]' => Loop.new(["loop"])
56
+ # TAG matches conditional tags: '[c:authorname=Simon W]' => Conditional.new(false, Variable.new(['authorname']), '=', 'Simon W')
57
+ # TAG matches function tags: '[f:js|v:dench]' => Function.new('js', [Variable.new(['dench'])])
58
+ TAG = '['.r >> (VARIABLE | CONDITIONAL | LOOP | FUNCTION) << ']'.r
59
+
60
+ # LAYOUT_TAG with content: '<!--layout:[content]-->' => Layout.new('content')
61
+ # LAYOUT_TAG with css: '<!--layout:[css]-->' => Layout.new('css')
62
+ LAYOUT_TAG = ('<!--layout:['.r >> IDENT << ']-->'.r).map {|(name)| Layout.new name }
63
+
64
+ # END_TAG matches, er, end tags: '[/]' => EndTag.new
65
+ END_TAG = '[/]'.r.map {|_| EndTag.new }
66
+
67
+ DOCUMENT = (TAG | END_TAG | LAYOUT_TAG | TEXT | /[\[\]\|\<\>]/.r).star.eof
68
+
69
+ def self.parse io
70
+ DOCUMENT.parse! io.read
71
+ end
72
+ end
73
+ end
74
+
75
+ if __FILE__ == $0
76
+ puts RageRender::Language::parse(ARGF).inspect
77
+ end
@@ -0,0 +1,50 @@
1
+ require_relative 'language'
2
+
3
+ module RageRender
4
+ def self.to_erb document
5
+ document.map do |chunk|
6
+ case chunk
7
+ when String
8
+ chunk
9
+
10
+ when Language::Variable
11
+ "<%= #{chunk.path.join('.')} %>"
12
+
13
+ when Language::Conditional
14
+ rhs = case chunk.rhs
15
+ when String
16
+ "\"#{chunk.rhs}\""
17
+ when Language::Variable
18
+ chunk.rhs.path.join('.')
19
+ when nil
20
+ ""
21
+ end
22
+ "<% if #{chunk.reversed ? 'not ' : ''} #{chunk.lhs.path.join('.')} #{chunk.operator} #{rhs} %>"
23
+
24
+ when Language::Function
25
+ params = chunk.params.map do |param|
26
+ case param
27
+ when Language::Variable
28
+ param.path.join('.')
29
+ else
30
+ "\"#{param}\""
31
+ end
32
+ end
33
+ "<%= #{chunk.name}(#{params.join(', ')}) %>"
34
+
35
+ when Language::Loop
36
+ "<% for l in #{chunk.path.join('.')} %>"
37
+
38
+ when Language::Layout
39
+ "<%= #{chunk.name} %>"
40
+
41
+ when Language::EndTag
42
+ '<% end %>'
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ if __FILE__ == $0
49
+ puts RageRender.to_erb(RageRender::Language::parse(ARGF)).join
50
+ end
@@ -0,0 +1,105 @@
1
+ require_relative 'language'
2
+
3
+ module RageRender
4
+ LIQUID_FUNCTIONS = {
5
+ 'add' => 'plus',
6
+ 'subtract' => 'minus',
7
+ 'multiply' => 'times',
8
+ 'divide' => 'divided_by',
9
+ 'js' => 'escape', # TODO: check these do the same thing!
10
+ }
11
+
12
+ def self.render_value value
13
+ case value
14
+ when String
15
+ value =~ /^[0-9]+$/ ? value : "\"#{value}\""
16
+ when Language::Variable
17
+ if value.path.first == 'l' && value.path.last == 'iteration'
18
+ 'forloop.index0'
19
+ elsif value.path.first == 'l' && value.path.last == 'aiteration'
20
+ 'forloop.index'
21
+ else
22
+ value.path.join('.')
23
+ end
24
+ when nil
25
+ ""
26
+ end
27
+ end
28
+
29
+ def self.to_liquid document
30
+ tag_stack = Array.new
31
+
32
+ document.map do |chunk|
33
+ case chunk
34
+ when String
35
+ chunk
36
+
37
+ when Language::Variable
38
+ "{{ #{render_value chunk} }}"
39
+
40
+ when Language::Conditional
41
+ tag_stack << (chunk.reversed ? :endunless : :endif)
42
+
43
+ lhs = render_value chunk.lhs
44
+ rhs = render_value chunk.rhs
45
+ operator = chunk.operator
46
+
47
+ if chunk.lhs.is_a?(Language::Variable) && chunk.lhs.path.first == "l"
48
+ case chunk.lhs.path.last
49
+ when "is_first", "is_last"
50
+ lhs = "forloop.#{chunk.lhs.path.last[3..]}"
51
+ when "is_even", "is_odd"
52
+ lhs = 'forloop.index0'
53
+ rhs = '2'
54
+ operator = (chunk.lhs.path.last == 'is_even' ? '%' : '!%')
55
+ end
56
+ end
57
+
58
+ output = Array.new
59
+ case operator
60
+ when '~', '!~'
61
+ # case insensitive comparison: we need to manually do this
62
+ output << "{% assign lhs = #{lhs} | downcase %}"
63
+ output << "{% assign rhs = #{rhs} | downcase %}"
64
+ lhs = "lhs"
65
+ rhs = "rhs"
66
+ operator = {'~' => '==', '!~' => '!='}[operator]
67
+ when '%', '!%'
68
+ # modulo comparison with zero
69
+ output << "{% assign lhs = #{lhs} | modulo: #{rhs} %}"
70
+ lhs = "lhs"
71
+ rhs = "0"
72
+ operator = {'%' => '==', '!%' => '!='}[operator]
73
+ end
74
+
75
+ operator = (operator == '=' ? '==' : operator)
76
+ output << "{% #{chunk.reversed ? 'unless' : 'if'} #{lhs} #{operator} #{rhs} %}"
77
+ output.join
78
+
79
+ when Language::Function
80
+ params = chunk.params.map {|p| render_value p }
81
+ name = LIQUID_FUNCTIONS.fetch(chunk.name, chunk.name)
82
+ args = params.drop(1).map {|p| "#{name}: #{p}" }.join(' | ')
83
+ "{{ #{params.first} | #{args.empty? ? name : args} }}"
84
+
85
+ when Language::Loop
86
+ tag_stack << :endfor
87
+ "{% for l in #{chunk.path.join('.')} %}"
88
+
89
+ when Language::Layout
90
+ "{{ #{chunk.name} }}"
91
+
92
+ when Language::EndTag
93
+ if tag_stack.empty?
94
+ '[/]'
95
+ else
96
+ "{% #{tag_stack.pop.to_s} %}"
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+
103
+ if __FILE__ == $0
104
+ puts RageRender.to_liquid(RageRender::Language::parse(ARGF)).join
105
+ end
data/lib/ragerender.rb ADDED
@@ -0,0 +1,7 @@
1
+ require_relative 'ragerender/language'
2
+ require_relative 'ragerender/to_liquid'
3
+ require_relative 'ragerender/to_erb'
4
+
5
+ if defined?(Jekyll)
6
+ require_relative 'ragerender/jekyll'
7
+ end
metadata ADDED
@@ -0,0 +1,348 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ragerender
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Simon Worthington
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2025-05-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rsec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: dimensions
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.3'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '13'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '13'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '5'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '5'
69
+ - !ruby/object:Gem::Dependency
70
+ name: jekyll
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '4'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '4'
83
+ description: |-
84
+ == What's this?
85
+
86
+ {ComicFury}[https://comicfury.com] is an excellent no-bullshit webcomic hosting
87
+ site created and maintained by the legend Kyo. You should support them on
88
+ {Patreon}[https://www.patreon.com/comicfury]!
89
+
90
+ {Jekyll}[https://jekyllrb.com] is a highly regarded and widespread static site
91
+ generator. It builds simple slowly-changing content into HTML files using
92
+ templates.
93
+
94
+ RageRender allows you to use your ComicFury templates to generate a static
95
+ version of your webcomic site using Jekyll. You just supply your templates,
96
+ comics and blogs, and RageRender will output a site that mimics your ComicFury
97
+ site.
98
+
99
+ Well, I say "mimics". Output is a static site, which means all of the
100
+ interactive elements of ComicFury don't work. This includes comments,
101
+ subscriptions, search, and comic management.
102
+
103
+ === But why?!
104
+
105
+ RageRender allows those of us who work on making changes to ComicFury site
106
+ templates to test our changes before we put them live.
107
+
108
+ With RageRender, you can edit your CSS, HTML templates and site settings before
109
+ you upload them to ComicFury. This makes the process of testing changes quicker
110
+ and makes it much more likely that you catch mistakes before any comic readers
111
+ have a chance to see them.
112
+
113
+ RageRender doesn't compete with the most excellent ComicFury (who's Patreon you
114
+ should contribute to, as I do!) – you should continue to use ComicFury for all
115
+ your day-to-day artistic rage management needs. But if you find yourself making
116
+ changes to a site design, RageRender may be able to help you.
117
+
118
+ == Getting started
119
+
120
+ First, you need to have {Ruby}[https://www.ruby-lang.org/] and
121
+ {Bundler}[https://bundle.io/] installed. The Jekyll site has {good guides on how
122
+ to do that}[https://jekyllrb.com/docs/installation/] depending on your operating
123
+ system.
124
+
125
+ To set up a new site, open a terminal and type:
126
+
127
+ mkdir mycomic && cd mycomic
128
+ bundle init
129
+ bundle add jekyll
130
+ bundle add ragerender
131
+
132
+ Now you can add comics! Add the image into an <tt>images</tt> folder:
133
+
134
+ mkdir images
135
+ cp 'cool comic.jpg' 'images/My first page.jpg'
136
+
137
+ The file name of the image will be the title of your comic page. And that's it,
138
+ you added your first comic!
139
+
140
+ If you want to add an author note, create a text file in a folder called
141
+ <tt>_comics</tt> that has the same file name, but with a <tt>.md</tt> extension:
142
+
143
+ mkdir _comics
144
+ echo "Check out my cool comic y'all!" > '_comics/My first page.md'
145
+
146
+ Generate the site using:
147
+
148
+ bundle exec jekyll build
149
+
150
+ Or start a local website to see it in your browser:
151
+
152
+ bundle exec jekyll serve
153
+ # Now visit http://localhost:4000!
154
+
155
+ === Customising your site
156
+
157
+ You'll notice a few things that might be off about your site, including that the
158
+ webcomic title and author name are probably not what you were expecting.
159
+
160
+ You can create a configuration file to tell RageRender the important details.
161
+ Put something like this in your webcomic folder and call it
162
+ <tt>_config.yml</tt>:
163
+
164
+ title: "My awesome webcomic!"
165
+ slogan: "It's the best!"
166
+ description: >
167
+ My epic story about how him and her
168
+ fell into a romantic polycule with they and them
169
+
170
+ defaults:
171
+ - scope:
172
+ path: ''
173
+ values:
174
+ author: "John smith"
175
+
176
+ theme: ragerender
177
+
178
+ Your webcomic now has its basic information set up.
179
+
180
+ === Adding your layouts
181
+
182
+ If you want to use your own layout code, then create a <tt>_layouts</tt>
183
+ directory and put the contents of each of your ComicFury layout tabs in there,
184
+ and then put your CSS in the main folder. You should end up with a full set of
185
+ files like:
186
+
187
+ _layouts
188
+ archive.html
189
+ blog-archive.html
190
+ blog-display.html
191
+ comic-page.html
192
+ error-page.html
193
+ overall.html
194
+ overview.html
195
+ search.html
196
+ layout.css
197
+
198
+ Now when you build your site, your custom templates and styles will be used
199
+ instead.
200
+
201
+ === Adding blogs
202
+
203
+ Add your blogs into a folder called `_posts`:
204
+
205
+ cat _posts/2025-05-29-my-new-comic.md
206
+ Hey guys, welcome to my new comic! It's gonna be so sick!
207
+
208
+ Note that the name of your blog post has to include the date and the title, or
209
+ it'll be ignored.
210
+
211
+ === Customising comics and blogs
212
+
213
+ You can add {Front Matter}[https://jekyllrb.com/docs/front-matter/] to set the
214
+ details of your author notes and blogs manually:
215
+
216
+ ---
217
+ title: "spooky comic page"
218
+ date: "2025-03-05 16:20"
219
+ image: "images/ghost.png"
220
+ author: "Jane doe"
221
+ custom:
222
+ # use yes and no for tickbox settings
223
+ spooky: yes
224
+ # use text in quotes for short texts
225
+ mantra: "live long and prosper"
226
+ # use indented text for long texts
227
+ haiku: >
228
+ Testing webcomics
229
+ Now easier than ever
230
+ Thanks to RageRender
231
+ comments:
232
+ - author: "Skippy"
233
+ date: "13 Mar 2025, 3.45 PM"
234
+ comment: "Wow this is so sick!"
235
+ ---
236
+ Your author note still goes at the end, like this!
237
+
238
+ === Adding extra pages
239
+
240
+ You can add extra pages just by adding new HTML files to your webcomic folder.
241
+ The name of the file becomes the URL that it will use.
242
+
243
+ Pages by default won't be embedded into your 'Overall' layout. You can change
244
+ that and more with optional Front Matter:
245
+
246
+ ---
247
+ # Include this line to set the page title
248
+ title: "Bonus content"
249
+ # Include this line to hide the page from the navigation menu
250
+ hidden: yes
251
+ # Include this line to embed this page in the overall layout
252
+ layout: Overall
253
+ ---
254
+ <h1>yo check out my bonus content!</h1>
255
+
256
+ === Stuff that doesn't work
257
+
258
+ Here is a probably incomplete list of things you can expect to be different
259
+ about your local site compared to ComicFury:
260
+
261
+ - Any comments you specify in Front Matter will be present, but you can't add
262
+ new ones
263
+ - Search doesn't do anything at all
264
+ - Saving and loading your place in the comic isn't implemented
265
+ - GET and POST variables in templates are ignored and will always be blank
266
+ - Random numbers in templates will be random only once per site build, not once
267
+ per page call
268
+
269
+ == Without Jekyll
270
+
271
+ RageRender can also be used without Jekyll to turn ComicFury templates into
272
+ templates in other languages.
273
+
274
+ E.g:
275
+
276
+ gem install ragerender
277
+ echo "[c:iscomicpage]<div>[f:js|v:comictitle]</div>[/]" > template.html
278
+ ruby $(gem which ragerender/to_liquid) template.html
279
+ # {% if iscomicpage %}<div>{{ comictitle | escape }}</div>{% endif %}
280
+ ruby $(gem which ragerender/to_erb) template.html
281
+ # <% if iscomicpage %><div><%= js(comictitle) %></div><% end %>
282
+
283
+ You still need to pass the correct variables to these templates; browse {this
284
+ unofficial documentation}[https://github.com/heyeinin/comicfury-documentation]
285
+ or RageRender::ComicDrop etc. to see which variables work on which templates.
286
+
287
+ == Get help
288
+
289
+ That's not a proclamation but an invitation! Reach out if you're having trouble
290
+ by {raising an issue}[https://github.com/simonwo/ragerender/issues] or posting
291
+ in the ComicFury forums.
292
+ email:
293
+ - simon@simonwo.net
294
+ executables: []
295
+ extensions: []
296
+ extra_rdoc_files: []
297
+ files:
298
+ - README.rdoc
299
+ - Rakefile
300
+ - assets/404.html
301
+ - assets/archive-comics.html
302
+ - assets/archive.html
303
+ - assets/blog.html
304
+ - assets/overview.html
305
+ - assets/search.html
306
+ - lib/ragerender.rb
307
+ - lib/ragerender/date_formats.rb
308
+ - lib/ragerender/jekyll.rb
309
+ - lib/ragerender/jekyll/archive.rb
310
+ - lib/ragerender/jekyll/blog.rb
311
+ - lib/ragerender/jekyll/blog_archive.rb
312
+ - lib/ragerender/jekyll/chapter.rb
313
+ - lib/ragerender/jekyll/comics.rb
314
+ - lib/ragerender/jekyll/error.rb
315
+ - lib/ragerender/jekyll/named_data_delegator.rb
316
+ - lib/ragerender/jekyll/overview.rb
317
+ - lib/ragerender/jekyll/pagination.rb
318
+ - lib/ragerender/jekyll/search.rb
319
+ - lib/ragerender/language.rb
320
+ - lib/ragerender/to_erb.rb
321
+ - lib/ragerender/to_liquid.rb
322
+ homepage: https://github.com/simonwo/ragerender
323
+ licenses:
324
+ - ''
325
+ metadata:
326
+ homepage_uri: https://github.com/simonwo/ragerender
327
+ source_code_uri: https://github.com/simonwo/ragerender
328
+ post_install_message:
329
+ rdoc_options: []
330
+ require_paths:
331
+ - lib
332
+ required_ruby_version: !ruby/object:Gem::Requirement
333
+ requirements:
334
+ - - ">="
335
+ - !ruby/object:Gem::Version
336
+ version: 3.0.0
337
+ required_rubygems_version: !ruby/object:Gem::Requirement
338
+ requirements:
339
+ - - ">="
340
+ - !ruby/object:Gem::Version
341
+ version: '0'
342
+ requirements: []
343
+ rubygems_version: 3.4.1
344
+ signing_key:
345
+ specification_version: 4
346
+ summary: A template parser and site generator that can render a webcomic site using
347
+ ComicFury templates, using the Jekyll static site generator.
348
+ test_files: []