synful 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (7) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +3 -0
  3. data/LICENSE +21 -0
  4. data/README.md +66 -0
  5. data/bin/synful +291 -0
  6. data/synful.gemspec +16 -0
  7. metadata +76 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e868e7d2521446e9bee4d813d8e15580b5ccfb58f71c0b7fad2459d212585a13
4
+ data.tar.gz: 1b2b05e76f6718625c94b77fa43e55c3d808a2c3c22ded2a83c9f4a24d8d99e9
5
+ SHA512:
6
+ metadata.gz: f7c6834c135e52ed6aa55f5ea9422a566957a3b046f02a94ff9a32391c34f927513801a866f8d2ec919fc7b721dddc7ef80d9e314b4977d0bb971ef54fed7287
7
+ data.tar.gz: 50d63b3c5b7af27adbc8b4e38765a8c60d183d1cc2c413e3e1edf5a4982184dad28f090e9518ddcc5b46c26641d1be042f11197d83eb0728d0f628207158f87b
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Steve Shreeve
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,66 @@
1
+ # synful
2
+
3
+ Ruby utility that shows syntax highlighted code in your browser
4
+
5
+ ## Goals
6
+
7
+ 1. Provide an easy way to view and print syntax highlighted source code
8
+ 2. Enable file selection or rejection in an intuitive way
9
+
10
+ ## Overview
11
+
12
+ Synful is a command line utility that will render syntax highlighted
13
+ source code in your browser. Simply indicate the file or directory
14
+ path names with optional file extensions to include or exclude. Synful
15
+ will generate a nice index and an entry for each file and it will
16
+ serve up the content and launch you web browser to view it. Once the
17
+ request has been served, the server will close down and exit. If you'd
18
+ like to keep the server running so that you can make changes and hit
19
+ refresh, there is an option to do that also. All syntax highlighting
20
+ is made possible by the amazing Rouge library.
21
+
22
+ ## Examples
23
+
24
+ ```
25
+ $ synful winr/bin/winr censive/lib/censive.rb
26
+ ```
27
+
28
+ A more complicated example is:
29
+
30
+ ```
31
+ synful -f -x spec,ru lib test /tmp/example.rb -- -test/boring-results.txt
32
+ ```
33
+
34
+ This will keep `synful` in the foreground (the `-f` option) and will
35
+ exclude all files with the `.spec` or `.ru` extensions. It will also
36
+ include files from the `lib/` and `test/` directories as well as the
37
+ `/tmp/example.rb` file. Even though the `test/boring-results.txt` file
38
+ should be rendered, we have disabled it via the `-` (minus sign or dash)
39
+ in front of it's filename. Note that to use this type of negation, we
40
+ need to precede it with a "double-dash" `--` to tell `synful` that we
41
+ are done with the normal command options.
42
+
43
+ ## Install
44
+
45
+ Install via `rubygems` with:
46
+
47
+ ```
48
+ gem install synful
49
+ ```
50
+
51
+ ## Options
52
+
53
+ ```
54
+ $ synful -h
55
+
56
+ usage: synful [options] <paths ...> [--] <-paths_to_skip ...>
57
+ -b, --bypass Bypass (skip over) top comments (begin with "#") in files
58
+ -f, --foreground Stay in the foreground, allows more requests
59
+ -h, --help Show help and command usage
60
+ -i, --include <exts> Comma list of extensions to include (incompatible with -x)
61
+ -x, --exclude <exts> Comma list of extensions to exclude (incompatible with -i)
62
+ ```
63
+
64
+ ## License
65
+
66
+ This software is licensed under terms of the MIT License.
data/bin/synful ADDED
@@ -0,0 +1,291 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # ============================================================================
4
+ # synful - Ruby utility that shows syntax highlighted code in your browser
5
+ #
6
+ # Author: Steve Shreeve (steve.shreeve@gmail.com)
7
+ # Date: Feb 18, 2023
8
+ #
9
+ # Thanks to Julie Evans for creating the amazing rouge library!
10
+ # ============================================================================
11
+ # GOALS:
12
+ # 1. Provide an easy way to view and print syntax highlighted source code
13
+ # 2. Enable file selection or rejection in an intuitive way
14
+ # ============================================================================
15
+ # TODO:
16
+ # 1. Ensure there is a section for a file, even when it has a bad encoding
17
+ # ============================================================================
18
+
19
+ require "optparse"
20
+
21
+ OptionParser.new.instance_eval do
22
+ @banner = "usage: #{program_name} [options] <paths ...> [--] <-paths_to_skip ...>"
23
+
24
+ on '-b' , '--bypass' , 'Bypass (skip over) top comments (begin with "#") in files'
25
+ on '-f' , '--foreground', 'Stay in the foreground, allows more requests'
26
+ on '-h' , '--help' , 'Show help and command usage' do Kernel.abort to_s; end
27
+ on '-i <exts>' , '--include' , 'Comma list of extensions to include (incompatible with -x)', Array
28
+ on '-x <exts>' , '--exclude' , 'Comma list of extensions to exclude (incompatible with -i)', Array
29
+
30
+ self
31
+ end.parse!(into: opts={}) rescue abort($!.message)
32
+
33
+ skip, want = ARGV.map(&:dup).partition {|item| item.delete_prefix!("-") }; ARGV.clear
34
+ want << "." if want.empty?
35
+ skip = skip.empty? ? nil : Set.new(skip)
36
+
37
+ nuke = opts[:bypass ]
38
+ fore = opts[:foreground]
39
+ keep = opts[:include ] # will become a Set or nil
40
+ deny = opts[:exclude ] # will become a Set or nil
41
+
42
+ if keep && deny
43
+ abort "#{File.basename $0}: include and exclude cannot be used together"
44
+ elsif keep
45
+ keep = Set.new(keep.map(&:downcase))
46
+ deny = nil
47
+ else
48
+ keep = nil
49
+ deny = Set.new(((deny || []) + %w[
50
+ ico jpg jpeg otf png pdf gif css svg eot ttf woff woff2 o a dylib
51
+ gem gz zip sqlite3 DS_Store
52
+ ]).map(&:downcase))
53
+ end
54
+
55
+ $skip, $want, $fore, $keep, $deny, $nuke = [skip, want, fore, keep, deny, nuke]
56
+
57
+ # ============================================================================
58
+
59
+ require "rouge"
60
+
61
+ $show = Rouge::Formatters::HTMLLegacy.new(line_numbers: true)
62
+
63
+ Rouge::Lexers::Ruby.filenames "*.rbx", "*.ru", "*.imba"
64
+ Rouge::Lexers::Sass.filenames "*.styl"
65
+ Rouge::Lexers::ERB.filenames "*.eco"
66
+
67
+ # ============================================================================
68
+
69
+ require "find"
70
+ require "set"
71
+ require "sinatra"
72
+
73
+ $stderr = $stderr.dup.reopen File.new("/dev/null", "w") # turn off logger
74
+
75
+ set :server, "webrick"
76
+
77
+ get "*" do
78
+ skip, want, fore, keep, deny = [$skip, $want, $fore, $keep, $deny]
79
+
80
+ init = Time.now.to_f
81
+
82
+ @list = want.inject([]) do |list, path|
83
+ if !File.readable?(path)
84
+ warn "unreadable '#{path}'"
85
+ elsif File.directory?(path)
86
+ Find.find(path) do |path|
87
+ path.delete_prefix!("./") #!# TODO: is this working as expected?
88
+ if !File.readable?(path) || path == "."
89
+ next
90
+ elsif File.directory?(path)
91
+ Find.prune if File.basename(path).start_with?(".") or skip&.include?(path)
92
+ elsif File.file?(path)
93
+ next if skip&.include?(path)
94
+ type = path[/(?<=\.)[^.\/]+\z/].to_s.downcase
95
+ list << path if keep ? keep.include?(type) : !deny.include?(type)
96
+ else
97
+ warn "unknown '#{path}'"
98
+ end
99
+ end
100
+ elsif File.file?(path)
101
+ list << path # requested explicitly
102
+ end
103
+ list
104
+ end.sort.uniq
105
+
106
+ # show filenames
107
+ time = Time.now.strftime("%Y-%m-%d %H:%M:%S")
108
+ STDERR.puts "\n[#{time}]\n\n", @list.map {|item| " • #{item}"}
109
+
110
+ # generate content and generation time
111
+ $err = false
112
+ body = erb :page
113
+ wait = Time.now.to_f - init
114
+ STDERR.puts "\nTime elapsed: %.2f" % wait
115
+
116
+ # send response
117
+ $fore or Thread.new { sleep wait; exit! } # how can we know when the request is "done?"
118
+ headers "Connection" => "close"
119
+ body
120
+ end
121
+
122
+ Thread.new do
123
+ sleep 0.1 until settings.running?
124
+ fork or exec "open 'http://localhost:#{settings.port}/'"
125
+ end
126
+
127
+ __END__
128
+
129
+ @@ layout
130
+ <!DOCTYPE html>
131
+ <html lang="en">
132
+ <head>
133
+ <meta charset="utf-8">
134
+ <title>Synful</title>
135
+ <link rel="icon" href="data:,">
136
+ <style type="text/css">
137
+ html body { font-size: 14px; }
138
+ body { font-family: Verdana; padding: 5px; }
139
+ a { text-decoration: none; }
140
+ h4 a { color: #fff; font-weight: bold; margin: 0; border: none; }
141
+ h4 a:hover { background: #333; }
142
+ h4 { background: black; color: white; padding: 5px; margin: 0; border: none; }
143
+ li a { color: #000; }
144
+ li a:hover { text-decoration: underline; color: #666; }
145
+ pre { margin: 0; font-family: Consolas, Menlo, monospace; }
146
+
147
+ .rouge-table { padding: 0; border-collapse: collapse; }
148
+ .rouge-table td { padding: 0; vertical-align: top; }
149
+ .rouge-table td:first-child { text-align: right; }
150
+ .rouge-table pre { padding: 0.7em; }
151
+
152
+ .codehilite { margin: 0; background: #fff; white-space: pre; overflow-x: hidden; }
153
+ .codehilite .lineno { margin: 0; color: #ddd; background: #f4f4f4; }
154
+ .codehilite .hll { background-color: #ffffcc }
155
+ .codehilite .c { color: #888888 } /* Comment */
156
+ .codehilite .k { color: #008800; font-weight: bold } /* Keyword */
157
+ .codehilite .o { color: #333333 } /* Operator */
158
+ .codehilite .cm { color: #888888 } /* Comment.Multiline */
159
+ .codehilite .cp { color: #557799 } /* Comment.Preproc */
160
+ .codehilite .c1 { color: #888888 } /* Comment.Single */
161
+ .codehilite .cs { color: #cc0000; font-weight: bold } /* Comment.Special */
162
+ .codehilite .gd { color: #a00000 } /* Generic.Deleted */
163
+ .codehilite .ge { font-style: italic } /* Generic.Emph */
164
+ .codehilite .gr { color: #ff0000 } /* Generic.Error */
165
+ .codehilite .gh { color: #000080; font-weight: bold } /* Generic.Heading */
166
+ .codehilite .gi { color: #00a000 } /* Generic.Inserted */
167
+ .codehilite .go { color: #888888 } /* Generic.Output */
168
+ .codehilite .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */
169
+ .codehilite .gs { font-weight: bold } /* Generic.Strong */
170
+ .codehilite .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
171
+ .codehilite .gt { color: #0044dd } /* Generic.Traceback */
172
+ .codehilite .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
173
+ .codehilite .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
174
+ .codehilite .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
175
+ .codehilite .kp { color: #003388; font-weight: bold } /* Keyword.Pseudo */
176
+ .codehilite .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
177
+ .codehilite .kt { color: #333399; font-weight: bold } /* Keyword.Type */
178
+ .codehilite .m { color: #6600ee; font-weight: bold } /* Literal.Number */
179
+ .codehilite .n { color: #933 } /* name? */
180
+ .codehilite .s { } /* Literal.String */
181
+ .codehilite .na { color: #0000cc } /* Name.Attribute */
182
+ .codehilite .nb { color: #007020 } /* Name.Builtin */
183
+ .codehilite .nc { color: #bb0066; font-weight: bold } /* Name.Class */
184
+ .codehilite .no { color: #003366; font-weight: bold } /* Name.Constant */
185
+ .codehilite .nd { color: #555555; font-weight: bold } /* Name.Decorator */
186
+ .codehilite .ni { color: #880000; font-weight: bold } /* Name.Entity */
187
+ .codehilite .ne { color: #ff0000; font-weight: bold } /* Name.Exception */
188
+ .codehilite .nf { color: #0066bb; font-weight: bold } /* Name.Function */
189
+ .codehilite .nl { color: #997700; font-weight: bold } /* Name.Label */
190
+ .codehilite .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */
191
+ .codehilite .nt { color: #007700 } /* Name.Tag */
192
+ .codehilite .nv { color: #996633 } /* Name.Variable */
193
+ .codehilite .ow { color: #000000; font-weight: bold } /* Operator.Word */
194
+ .codehilite .w { color: #bbbbbb } /* Text.Whitespace */
195
+ .codehilite .mf { color: #6600ee; font-weight: bold } /* Literal.Number.Float */
196
+ .codehilite .mh { color: #005588; font-weight: bold } /* Literal.Number.Hex */
197
+ .codehilite .mi { color: #0000dd; font-weight: bold } /* Literal.Number.Integer */
198
+ .codehilite .mo { color: #4400ee; font-weight: bold } /* Literal.Number.Oct */
199
+ .codehilite .sb { } /* Literal.String.Backtick */
200
+ .codehilite .sc { color: #0044dd } /* Literal.String.Char */
201
+ .codehilite .sd { color: #dd4422 } /* Literal.String.Doc */
202
+ .codehilite .s2 { } /* Literal.String.Double */
203
+ .codehilite .se { color: #666666; font-weight: bold; } /* Literal.String.Escape */
204
+ .codehilite .sh { } /* Literal.String.Heredoc */
205
+ .codehilite .si { } /* Literal.String.Interpol */
206
+ .codehilite .sx { color: #dd2200; } /* Literal.String.Other */
207
+ .codehilite .sr { color: #000000; } /* Literal.String.Regex */
208
+ .codehilite .s1 { } /* Literal.String.Single */
209
+ .codehilite .ss { color: #aa6600 } /* Literal.String.Symbol */
210
+ .codehilite .bp { color: #007020 } /* Name.Builtin.Pseudo */
211
+ .codehilite .vc { color: #336699 } /* Name.Variable.Class */
212
+ .codehilite .vg { color: #dd7700; font-weight: bold } /* Name.Variable.Global */
213
+ .codehilite .vi { color: #3333bb } /* Name.Variable.Instance */
214
+ .codehilite .il { color: #0000dd; font-weight: bold } /* Literal.Number.Integer.Long */
215
+ </style>
216
+ </head>
217
+ <body>
218
+ <div class="top">
219
+
220
+ <%= yield %></div>
221
+ </body>
222
+ </html>
223
+
224
+ @@ page
225
+ <%= erb :toc if (count = @list.size) > 1 %><%
226
+ @list.each_with_index do |file, i|
227
+ @file = file
228
+ @prev = @list[(i - 1) % count]
229
+ @next = @list[(i + 1) % count]
230
+ @curr = count > 1 ? "#{i + 1}) " : "" %>
231
+ <%= erb :file %><% end %>
232
+
233
+ @@ toc
234
+ <h4><a name="top">Index</a></h4>
235
+
236
+ <ul><% @list.each do |file| %>
237
+ <li><a href="#<%= file %>"><%= file %></a></li><% end %>
238
+ </ul>
239
+
240
+ @@ file
241
+ <%
242
+ begin
243
+ data = File.read(@file)
244
+ data.gsub!(/\t/, " ") # replace tab with 2 spaces (make configurable?)
245
+ data.sub!(/\A.*?(?=^[^#\s])/m, "") if $nuke # nuke top comments (license, etc.)
246
+ type = Rouge::Lexer.guess(filename:@file, source:data)
247
+ rescue ArgumentError => e
248
+ STDERR.puts "\n" unless $err; $err = true
249
+ STDERR.puts "Invalid byte encoding in '#{@file}'"
250
+ return "Invalid... something here?"
251
+ rescue => e
252
+ if (data =~ /\A[^\n]+ruby/)
253
+ type = Rouge::Lexer.guess(filename:"foo.rb")
254
+ else
255
+ return "<p>Unable to process <b>#{@file.inspect}</b> (#{$!})\n\n"
256
+ end
257
+ end
258
+ if type.tag == "plaintext" || (type.tag == "html" && @file !~ /\.html?$/i)
259
+ mime = `file -b --mime-type "#{@file}"`.chomp
260
+ type = (mime =~ %r!\b(?:octet|image|font)\b!) ? nil : Rouge::Lexer.guess(mimetype:mime)
261
+ end
262
+ %>
263
+
264
+ <h4>
265
+ <a name="<%= @file %>"><%= @curr %><%= @file %>
266
+ (<%= data.count("\n") + 1 %> lines)
267
+ <%= "[" + type.tag + "]" if type %>
268
+ <%= File.mtime(@file).strftime("on %Y-%m-%d at %-I:%M %P") %>
269
+ </a>
270
+ <span style="float: right">
271
+ <a class="jump" href="#<%= @prev %>">&nbsp;&nbsp;&nbsp;&nbsp;&darr;&nbsp;&nbsp;&nbsp;&nbsp;</a>
272
+ <a class="jump" href="#<%= @next %>">&nbsp;&nbsp;&nbsp;&nbsp;&UpArrow;&nbsp;&nbsp;&nbsp;&nbsp;</a>
273
+ <a class="jump" href="#top">&nbsp;&nbsp;&nbsp;&nbsp;⥣&nbsp;&nbsp;&nbsp;&nbsp;</a>
274
+ </span>
275
+ </h4>
276
+
277
+ <%=
278
+ if type
279
+ begin
280
+ html = $show.format(type.lex(data))
281
+ html[html.rindex("\n"), 1] = ""
282
+ html
283
+ rescue
284
+ type = Rouge::Lexer.guess(filename:"foo.txt")
285
+ "<p>Unable to parse, showing plaintext instead...</p>" +
286
+ $show.format(type.lex(data))
287
+ end
288
+ else
289
+ "<p>(binary file)</p>"
290
+ end
291
+ %>
data/synful.gemspec ADDED
@@ -0,0 +1,16 @@
1
+ # encoding: utf-8
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "synful"
5
+ s.version = "1.0.0"
6
+ s.author = "Steve Shreeve"
7
+ s.email = "steve.shreeve@gmail.com"
8
+ s.summary =
9
+ s.description = "Ruby utility that shows syntax highlighted code in your browser"
10
+ s.homepage = "https://github.com/shreeve/synful"
11
+ s.license = "MIT"
12
+ s.files = `git ls-files`.split("\n") - %w[.gitignore]
13
+ s.executables = `cd bin && git ls-files .`.split("\n")
14
+ s.add_runtime_dependency "rouge", "~> 4.0"
15
+ s.add_runtime_dependency "sinatra", "~> 3.0"
16
+ end
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: synful
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Steve Shreeve
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-02-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rouge
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: sinatra
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
41
+ description: Ruby utility that shows syntax highlighted code in your browser
42
+ email: steve.shreeve@gmail.com
43
+ executables:
44
+ - synful
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - Gemfile
49
+ - LICENSE
50
+ - README.md
51
+ - bin/synful
52
+ - synful.gemspec
53
+ homepage: https://github.com/shreeve/synful
54
+ licenses:
55
+ - MIT
56
+ metadata: {}
57
+ post_install_message:
58
+ rdoc_options: []
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ requirements: []
72
+ rubygems_version: 3.4.6
73
+ signing_key:
74
+ specification_version: 4
75
+ summary: Ruby utility that shows syntax highlighted code in your browser
76
+ test_files: []