opml2html 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 86f8b4072a12bf95e2bc5432d08f89810703425938d2ad537a3e0460eb34972a
4
+ data.tar.gz: cd58c06735451231cd56708c0cc144e27737573b24d363d57ec45f10fdfec8e5
5
+ SHA512:
6
+ metadata.gz: 50da65a32f37b59868dec3c76514a5e826c2533045fd4b1cfd8c517bc33ec17046160339296dca5e55cd20af7ec3756f89f43bb5b121196cdb0999138578e182
7
+ data.tar.gz: 6008a560211d1253f7c34947cba17658b44561fe98cacd7b5f404f0791d818d12c2d0b30469f58b7af7f95c127524f38f0af4b1bdacc112aed6b17b715ab81f4
data/.rubocop.yml ADDED
@@ -0,0 +1,5 @@
1
+ inherit_from: .rubocop_todo.yml
2
+
3
+ AllCops:
4
+ TargetRubyVersion: 3.1
5
+ NewCops: enable
data/.rubocop_todo.yml ADDED
@@ -0,0 +1,85 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # on 2025-01-05 06:02:27 UTC using RuboCop version 1.48.1.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
9
+ # Offense count: 3
10
+ # Configuration parameters: EnforcedStyle, AllowedGems, Include.
11
+ # SupportedStyles: Gemfile, gems.rb, gemspec
12
+ # Include: **/*.gemspec, **/Gemfile, **/gems.rb
13
+ Gemspec/DevelopmentDependencies:
14
+ Exclude:
15
+ - 'opml2html.gemspec'
16
+
17
+ # Offense count: 2
18
+ # This cop supports safe autocorrection (--autocorrect).
19
+ # Configuration parameters: EnforcedStyle.
20
+ # SupportedStyles: space, no_space
21
+ Layout/SpaceInsideStringInterpolation:
22
+ Exclude:
23
+ - 'lib/opml2html/renderer.rb'
24
+
25
+ # Offense count: 2
26
+ # Configuration parameters: AllowedMethods, AllowedPatterns.
27
+ Metrics/CyclomaticComplexity:
28
+ Max: 8
29
+
30
+ # Offense count: 6
31
+ # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
32
+ Metrics/MethodLength:
33
+ Max: 22
34
+
35
+ # Offense count: 8
36
+ # Configuration parameters: AllowedConstants.
37
+ Style/Documentation:
38
+ Exclude:
39
+ - 'spec/**/*'
40
+ - 'test/**/*'
41
+ - 'exe/opml2html~'
42
+ - 'lib/opml2html/dumper.rb'
43
+ - 'lib/opml2html/html_document.rb'
44
+ - 'lib/opml2html/renderer.rb'
45
+ - 'lib/opml2html/traverser.rb'
46
+
47
+ # Offense count: 6
48
+ # This cop supports unsafe autocorrection (--autocorrect-all).
49
+ # Configuration parameters: EnforcedStyle.
50
+ # SupportedStyles: always, always_true, never
51
+ Style/FrozenStringLiteralComment:
52
+ Exclude:
53
+ - 'exe/opml2html'
54
+ - 'exe/opml2html~'
55
+ - 'lib/opml2html/dumper.rb'
56
+ - 'lib/opml2html/html_document.rb'
57
+ - 'lib/opml2html/renderer.rb'
58
+ - 'lib/opml2html/traverser.rb'
59
+
60
+ # Offense count: 118
61
+ # This cop supports safe autocorrection (--autocorrect).
62
+ # Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline.
63
+ # SupportedStyles: single_quotes, double_quotes
64
+ Style/StringLiterals:
65
+ Exclude:
66
+ - 'Gemfile'
67
+ - 'Rakefile'
68
+ - 'bin/console'
69
+ - 'exe/opml2html'
70
+ - 'exe/opml2html~'
71
+ - 'lib/opml2html.rb'
72
+ - 'lib/opml2html/html_document.rb'
73
+ - 'lib/opml2html/renderer.rb'
74
+ - 'lib/opml2html/traverser.rb'
75
+ - 'lib/opml2html/version.rb'
76
+ - 'opml2html.gemspec'
77
+ - 'test/opml2html_test.rb'
78
+ - 'test/test_helper.rb'
79
+
80
+ # Offense count: 5
81
+ # This cop supports safe autocorrection (--autocorrect).
82
+ # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns.
83
+ # URISchemes: http, https
84
+ Layout/LineLength:
85
+ Max: 259
data/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # OPML2HTML change log
2
+
3
+ ## Unreleased
4
+
5
+ ## 0.0.1 - 2025-01-05
6
+
7
+ * Initial release
data/README.md ADDED
@@ -0,0 +1,38 @@
1
+ # OPML2HTML
2
+
3
+ OPML to HTML renderer (converter) command line tool.
4
+
5
+ Currently only the [OPML 2.0](http://opml.org/spec2.opml) is supported, and
6
+ there are a lot of limitations on outline structures.
7
+
8
+ ## Installation
9
+
10
+ Install the gem by executing:
11
+
12
+ ``` shell
13
+ gem install opml2html
14
+ ```
15
+
16
+ ## Usage
17
+
18
+ ```shell
19
+ opml2html < /path/to/some.opml > /path/to/output.html
20
+ ```
21
+
22
+ See also the help message by `opml2html --help`.
23
+
24
+ ## Development
25
+
26
+ After checking out the repo, run `bin/setup` to install dependencies.
27
+ Then, run `rake test` to run the tests.
28
+ You can also run `bin/console` for an interactive prompt that will allow you to experiment.
29
+
30
+ To install this gem onto your local machine, run `bundle exec rake install`.
31
+ To release a new version, update the version number in `version.rb`, and then
32
+ run `bundle exec rake release`, which will create a git tag for the version,
33
+ push git commits and the created tag, and push the `.gem` file to
34
+ [rubygems.org](https://rubygems.org).
35
+
36
+ ## Contributing
37
+
38
+ Bug reports and pull requests are welcome.
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/*_test.rb"]
10
+ end
11
+
12
+ require "rubocop/rake_task"
13
+
14
+ RuboCop::RakeTask.new
15
+
16
+ task default: %i[test rubocop]
data/exe/opml2html ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "optparse"
4
+ require_relative "../lib/opml2html"
5
+
6
+ css = "main.css"
7
+ parser = OptionParser.new
8
+ parser.on("--css=PATH") { |path| css = path }
9
+ parser.parse!
10
+
11
+ doc = OPML2HTML::HTMLDocument.new
12
+ hook = OPML2HTML::Renderer.new(doc, css:)
13
+ trav = OPML2HTML::Traverser.start($stdin, hook:)
14
+ trav.write_html_to($stdout, encoding: Encoding::UTF_8)
@@ -0,0 +1,35 @@
1
+ module OPML2HTML
2
+ class Dumper
3
+ def head_title(content)
4
+ warn "head / title: #{content}"
5
+ end
6
+
7
+ def head_date_created(time)
8
+ warn "head / date created: #{time}"
9
+ end
10
+
11
+ def head_owner_twitter_screen_name(content)
12
+ warn "head / owner Twitter screen name: #{content}"
13
+ end
14
+
15
+ def head_owner_name(content)
16
+ warn "head / owner name: #{content}"
17
+ end
18
+
19
+ def head_date_modified(time)
20
+ warn "head / date modified: #{time}"
21
+ end
22
+
23
+ def head_end; end
24
+
25
+ def body_outline_heading(*); end
26
+
27
+ def body_outline_text(value)
28
+ warn "body / outline / text: #{value}"
29
+ end
30
+
31
+ def body_outline_created(time)
32
+ warn "body / outline / created: #{time}"
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,51 @@
1
+ require "nokogiri"
2
+
3
+ module OPML2HTML
4
+ class HTMLDocument
5
+ def initialize
6
+ @document = Nokogiri::HTML::Document.new
7
+ end
8
+
9
+ def head
10
+ @head and return @head
11
+ @head = create_element("head")
12
+ root.add_child(@head)
13
+ @head
14
+ end
15
+
16
+ def body
17
+ @body and return @body
18
+ @body = create_element("body")
19
+ root.add_child(@body)
20
+ @body
21
+ end
22
+
23
+ def main
24
+ @main and return @main
25
+ @main = create_element("main")
26
+ body.add_child(@main)
27
+ @main
28
+ end
29
+
30
+ def metadata
31
+ @metadata and return @metadata
32
+ @metadata = create_element("p")
33
+ @metadata["style"] = "text-align: right"
34
+ main.add_child(@metadata)
35
+ @metadata
36
+ end
37
+
38
+ def create_element(name)
39
+ Element.new(name, @document)
40
+ end
41
+
42
+ private
43
+
44
+ def root
45
+ @root and return @root
46
+ @root = create_element("html")
47
+ @document.add_child(@root)
48
+ @root
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,55 @@
1
+ module OPML2HTML
2
+ class Renderer
3
+ def initialize(document, css:)
4
+ @document = document
5
+ @css = css
6
+ end
7
+
8
+ def head_title(content)
9
+ title = @document.create_element("title")
10
+ title.content = content
11
+ @document.head.add_child(title)
12
+
13
+ heading = @document.create_element("h1")
14
+ heading.content = content
15
+ @document.main.add_child(heading)
16
+ end
17
+
18
+ def head_date_created(time)
19
+ @document.metadata.add_child("created: #{time}<br/>")
20
+ end
21
+
22
+ def head_owner_twitter_screen_name(content)
23
+ @document.metadata.add_child("Twitter: #{content}<br/>")
24
+ end
25
+
26
+ def head_owner_name(content)
27
+ @document.metadata.add_child("owner: #{content}<br/>")
28
+ end
29
+
30
+ def head_date_modified(time)
31
+ @document.metadata.add_child("modified: #{time}<br/>")
32
+ end
33
+
34
+ def head_end
35
+ @document.head.add_child("<link rel=\"stylesheet\" href=\"#{ @css }\"/>")
36
+ @document.main.add_child(@document.metadata)
37
+ end
38
+
39
+ def body_outline_heading(value)
40
+ heading = @document.create_element("h2")
41
+ heading.add_child(value)
42
+ @document.main.add_child(heading)
43
+ end
44
+
45
+ def body_outline_text(value)
46
+ par = @document.create_element("p")
47
+ par.add_child(value)
48
+ @document.main.add_child(par)
49
+ end
50
+
51
+ def body_outline_created(time)
52
+ # @document.body.add_child("<p style='text-align: right'><small>#{time}</small></p>")
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,98 @@
1
+ require "nokogiri"
2
+ require "time"
3
+
4
+ module OPML2HTML
5
+ class Traverser
6
+ def self.start(input, hook:)
7
+ doc = Nokogiri::XML(input)
8
+ new(hook:).start(doc)
9
+ end
10
+
11
+ def initialize(hook:) # :nodoc:
12
+ @hook = hook
13
+ end
14
+
15
+ def start(document)
16
+ parse_document(document)
17
+ @hook.instance_variable_get(:@document).instance_variable_get(:@document)
18
+ end
19
+
20
+ private
21
+
22
+ def parse_document(node)
23
+ node => Nokogiri::XML::Document[root: Element[name: "opml",
24
+ attributes: [Attr[name: "version",
25
+ value: "2.0"]],
26
+ children:]]
27
+ children.each { |child| parse_under_document(child) }
28
+ end
29
+
30
+ def parse_under_document(node)
31
+ case node
32
+ in Text # nop
33
+ in Element[name: "head", children:]
34
+ children.each { |child| parse_under_head(child) }
35
+ @hook.head_end
36
+ in Element[name: "body", children:]
37
+ children.each { |child| parse_outline(child, level: 1) }
38
+ else
39
+ raise Error, node.inspect
40
+ end
41
+ end
42
+
43
+ def parse_under_head(node)
44
+ case node
45
+ in Element[name: "title", children: [Text[content:]]]
46
+ @hook.head_title(content)
47
+ in Element[name: "dateCreated", children: [Text[content:]]]
48
+ time = Time.parse(content)
49
+ @hook.head_date_created(time)
50
+ in Element[name: "ownerTwitterScreenName",
51
+ children: [Text[content:]]]
52
+ @hook.head_owner_twitter_screen_name(content)
53
+ in Element[name: "ownerName", children: [Text[content:]]]
54
+ @hook.head_owner_name(content)
55
+ in Element[name: "dateModified", children: [Text[content:]]]
56
+ time = Time.parse(content)
57
+ @hook.head_date_modified(time)
58
+ in Text |
59
+ Element[name: "ownerId" |
60
+ "urlUpdateSocket" |
61
+ "expansionState" |
62
+ "lastCursor"] # nop
63
+ else
64
+ raise Error, node.inspect
65
+ end
66
+ end
67
+
68
+ def parse_body_outline_attribute(node)
69
+ case node
70
+ in Attr[name: "text", value:]
71
+ value = DocumentFragment.parse(value)
72
+ @hook.body_outline_text(value)
73
+ in Attr[name: "created", value:]
74
+ time = Time.parse(value)
75
+ @hook.body_outline_created(time)
76
+ in Attr[name: "flNumberedSubs"] # nop
77
+ else
78
+ raise Error, node.inspect
79
+ end
80
+ end
81
+
82
+ def parse_outline(node, level:)
83
+ case [node, level]
84
+ in [Text, _] # nop
85
+ in [Element[name: "outline", attributes: [Attr[name: "text", value:], Attr[name: "created", value: _]], children: []], _]
86
+ value = DocumentFragment.parse(value)
87
+ @hook.body_outline_text(value)
88
+ in [Element[name: "outline", attributes: [Attr[name: "text", value:], Attr[name: "created", value: _]], children:], 1]
89
+ value = DocumentFragment.parse(value)
90
+ @hook.body_outline_heading(value)
91
+ children.each { |child| parse_outline(child, level: level + 1) }
92
+ in [Element[name: "outline", attributes:, children:], _]
93
+ attributes.each { |attribute| parse_body_outline_attribute(attribute) }
94
+ children.each { |child| parse_outline(child, level: level + 1) }
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OPML2HTML
4
+ VERSION = "0.0.1"
5
+ end
data/lib/opml2html.rb ADDED
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "nokogiri"
4
+ require_relative "opml2html/version"
5
+ require_relative "opml2html/traverser"
6
+ require_relative "opml2html/renderer"
7
+ require_relative "opml2html/html_document"
8
+
9
+ module OPML2HTML
10
+ Error = Class.new(StandardError)
11
+ Element = Nokogiri::XML::Element
12
+ Attr = Nokogiri::XML::Attr
13
+ Text = Nokogiri::XML::Text
14
+ DocumentFragment = Nokogiri::XML::DocumentFragment
15
+ end
data/manifest.scm ADDED
@@ -0,0 +1 @@
1
+ (specifications->manifest (list "ruby@3.1" "ruby-nokogiri" "ruby-rubocop"))
data/sig/opml2html.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module OPML2HTML
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: opml2html
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - gemmaro
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2025-01-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: nokogiri
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rubocop
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: test-unit
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: OPML2HTML is a OPML to HTML renderer (converter) it currently supports
70
+ only OPML 2.0 Spec and limited outline structure. This gem provides executable
71
+ `opml2html`, which takes standard input as input and emits output to standard output.
72
+ email:
73
+ - gemmaro.dev@gmail.com
74
+ executables:
75
+ - opml2html
76
+ extensions: []
77
+ extra_rdoc_files: []
78
+ files:
79
+ - ".rubocop.yml"
80
+ - ".rubocop_todo.yml"
81
+ - CHANGELOG.md
82
+ - README.md
83
+ - Rakefile
84
+ - exe/opml2html
85
+ - lib/opml2html.rb
86
+ - lib/opml2html/dumper.rb
87
+ - lib/opml2html/html_document.rb
88
+ - lib/opml2html/renderer.rb
89
+ - lib/opml2html/traverser.rb
90
+ - lib/opml2html/version.rb
91
+ - manifest.scm
92
+ - sig/opml2html.rbs
93
+ homepage: https://git.disroot.org/gemmaro/opml2html
94
+ licenses: []
95
+ metadata:
96
+ rubygems_mfa_required: 'true'
97
+ bug_tracker_uri: https://git.disroot.org/gemmaro/opml2html/issues
98
+ changelog_uri: https://git.disroot.org/gemmaro/opml2html/src/branch/main/CHANGELOG.md
99
+ documentation_uri: https://rubydoc.info/gems/opml2html
100
+ homepage_uri: https://git.disroot.org/gemmaro/opml2html
101
+ source_code_uri: https://git.disroot.org/gemmaro/opml2html
102
+ wiki_uri: https://git.disroot.org/gemmaro/opml2html/wiki
103
+ post_install_message:
104
+ rdoc_options: []
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: 3.1.0
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ requirements: []
118
+ rubygems_version: 3.3.26
119
+ signing_key:
120
+ specification_version: 4
121
+ summary: OPML to HTML renderer
122
+ test_files: []