opml2html 0.0.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.
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: []