cosensee 0.6.0

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.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +56 -0
  4. data/.rubocop_todo.yml +7 -0
  5. data/README.md +40 -0
  6. data/Rakefile +10 -0
  7. data/lib/cosensee/blank_bracket.rb +8 -0
  8. data/lib/cosensee/bracket_parser.rb +82 -0
  9. data/lib/cosensee/bracket_serializer.rb +26 -0
  10. data/lib/cosensee/code.rb +18 -0
  11. data/lib/cosensee/codeblock.rb +18 -0
  12. data/lib/cosensee/command_line.rb +18 -0
  13. data/lib/cosensee/decorate_bracket.rb +16 -0
  14. data/lib/cosensee/double_bracket.rb +20 -0
  15. data/lib/cosensee/empty_bracket.rb +8 -0
  16. data/lib/cosensee/error.rb +5 -0
  17. data/lib/cosensee/external_link_bracket.rb +8 -0
  18. data/lib/cosensee/formula_bracket.rb +8 -0
  19. data/lib/cosensee/hash_tag.rb +19 -0
  20. data/lib/cosensee/html_builder.rb +59 -0
  21. data/lib/cosensee/html_encodable.rb +12 -0
  22. data/lib/cosensee/icon_bracket.rb +8 -0
  23. data/lib/cosensee/image_bracket.rb +8 -0
  24. data/lib/cosensee/indent.rb +25 -0
  25. data/lib/cosensee/internal_link_bracket.rb +8 -0
  26. data/lib/cosensee/line.rb +47 -0
  27. data/lib/cosensee/line_parser.rb +253 -0
  28. data/lib/cosensee/link.rb +18 -0
  29. data/lib/cosensee/link_encodable.rb +26 -0
  30. data/lib/cosensee/page.rb +68 -0
  31. data/lib/cosensee/page_store.rb +63 -0
  32. data/lib/cosensee/parsed_bracket.rb +42 -0
  33. data/lib/cosensee/parsed_line.rb +76 -0
  34. data/lib/cosensee/project.rb +46 -0
  35. data/lib/cosensee/quote.rb +22 -0
  36. data/lib/cosensee/tailwind_renderer/blank_bracket.rb +11 -0
  37. data/lib/cosensee/tailwind_renderer/code.rb +14 -0
  38. data/lib/cosensee/tailwind_renderer/codeblock_builder.rb +37 -0
  39. data/lib/cosensee/tailwind_renderer/command_line.rb +17 -0
  40. data/lib/cosensee/tailwind_renderer/decorate_bracket.rb +25 -0
  41. data/lib/cosensee/tailwind_renderer/double_bracket.rb +13 -0
  42. data/lib/cosensee/tailwind_renderer/empty_bracket.rb +11 -0
  43. data/lib/cosensee/tailwind_renderer/external_link_bracket.rb +15 -0
  44. data/lib/cosensee/tailwind_renderer/formula_bracket.rb +13 -0
  45. data/lib/cosensee/tailwind_renderer/hash_tag.rb +17 -0
  46. data/lib/cosensee/tailwind_renderer/icon_bracket.rb +14 -0
  47. data/lib/cosensee/tailwind_renderer/image_bracket.rb +15 -0
  48. data/lib/cosensee/tailwind_renderer/internal_link_bracket.rb +15 -0
  49. data/lib/cosensee/tailwind_renderer/link.rb +14 -0
  50. data/lib/cosensee/tailwind_renderer/page.rb +31 -0
  51. data/lib/cosensee/tailwind_renderer/parsed_line.rb +30 -0
  52. data/lib/cosensee/tailwind_renderer/quote.rb +14 -0
  53. data/lib/cosensee/tailwind_renderer/text_bracket.rb +12 -0
  54. data/lib/cosensee/tailwind_renderer.rb +36 -0
  55. data/lib/cosensee/text_bracket.rb +10 -0
  56. data/lib/cosensee/user.rb +37 -0
  57. data/lib/cosensee/version.rb +5 -0
  58. data/lib/cosensee.rb +54 -0
  59. data/sig/cosensee.rbs +4 -0
  60. data/templates/index.html.erb +39 -0
  61. data/templates/page.html.erb +59 -0
  62. metadata +130 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ca076a9a5e18e0bd0f5c1825e35714536bcfad2921b8d3e997a3fe86140314b4
4
+ data.tar.gz: e28ec42e8750b9426275e78ba6b238b358217d8c97c7d95c1134a5f54f6f341c
5
+ SHA512:
6
+ metadata.gz: 72a7eb0c65b77920c3b1784db4bf58f9d984c0f660dbda56cab55ac2ac91d83ecf3e47b08ffb413d08e1601180f1258adcb141470a1dcbe5b653bd8d77232d7b
7
+ data.tar.gz: 1a8bef01b367b5d8f665236e0940fb4cf823134d4cbe8a3955eab932cf76e63d21c9548b813acadfd16c5da57ee17a40cf350ed093e1c23f3955ede40862954f
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,56 @@
1
+ inherit_from: .rubocop_todo.yml
2
+
3
+ AllCops:
4
+ TargetRubyVersion: '3.4'
5
+ NewCops: enable
6
+
7
+ require:
8
+ - rubocop-rake
9
+ - rubocop-rspec
10
+
11
+ Layout/LineLength:
12
+ Max: 300
13
+ Exclude:
14
+ - 'spec/**/*'
15
+
16
+ Layout/MultilineMethodCallIndentation:
17
+ EnforcedStyle: indented_relative_to_receiver
18
+
19
+ Metrics/AbcSize:
20
+ Max: 60
21
+
22
+ Metrics/ClassLength:
23
+ Max: 300
24
+
25
+ Metrics/CyclomaticComplexity:
26
+ Max: 20
27
+
28
+ Metrics/BlockLength:
29
+ Max: 100
30
+
31
+ Metrics/MethodLength:
32
+ Max: 80
33
+
34
+ Metrics/ParameterLists:
35
+ Max: 8
36
+
37
+ Metrics/PerceivedComplexity:
38
+ Max: 20
39
+
40
+ Style/MinMaxComparison:
41
+ Enabled: false
42
+
43
+ Style/NumericLiterals:
44
+ Enabled: false
45
+
46
+ RSpec/DescribedClass:
47
+ Enabled: false
48
+
49
+ RSpec/ExampleLength:
50
+ Max: 50
51
+
52
+ RSpec/MultipleExpectations:
53
+ Enabled: false
54
+
55
+ Style/NumericPredicate:
56
+ Enabled: false
data/.rubocop_todo.yml ADDED
@@ -0,0 +1,7 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # on 2024-12-11 09:34:23 UTC using RuboCop version 1.69.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.
data/README.md ADDED
@@ -0,0 +1,40 @@
1
+ # Cosensee
2
+
3
+ Cosensee is a tool that reads JSON data from [Cosense](https://scrapbox.io) (formerly Scrapbox) and outputs it in HTML format.
4
+
5
+ ## Installation
6
+
7
+ Install the gem:
8
+
9
+ $ gem install cosensee
10
+
11
+ If you use bundler, add to the application's Gemfile by executing:
12
+
13
+ $ bundle add cosensee
14
+
15
+ ## Usage
16
+
17
+ To output HTML, specify the JSON file from Cosense as an argument and run `bin/build` with Cosense JSON dump file.
18
+
19
+ $ bin/build <json file>
20
+
21
+ The HTML files will be output to the .out directory. You can view them locally by using the bin/server command.
22
+
23
+ $ $bin/server
24
+
25
+ By adding a Cosense JSON file as an argument, the build process will be executed before starting the server.
26
+
27
+ $ $bin/server <json file>
28
+
29
+ The default port used by bin/server is 1212.
30
+
31
+ ## Development
32
+
33
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
34
+
35
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
36
+
37
+ ## Contributing
38
+
39
+ Bug reports and pull requests are welcome on GitHub at https://github.com/takahashim/cosensee.
40
+
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+ require 'rubocop/rake_task'
6
+
7
+ RSpec::Core::RakeTask.new(:spec)
8
+ RuboCop::RakeTask.new
9
+
10
+ task default: :spec
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cosensee
4
+ # for bracket only spaces
5
+ BlankBracket = Data.define(:content, :blank, :raw) do
6
+ include BracketSerializer
7
+ end
8
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cosensee
4
+ # parser of Bracket
5
+ class BracketParser
6
+ include ::Cosensee::LinkEncodable
7
+
8
+ def self.parse(content)
9
+ new.parse(content)
10
+ end
11
+
12
+ attr_reader :content
13
+
14
+ def parse(content)
15
+ @content = content
16
+
17
+ case content
18
+ in [/\A\z/]
19
+ EmptyBracket.new(content:, raw: '[]')
20
+ in [/\A([ \t]+)\z/]
21
+ BlankBracket.new(content:, blank: Regexp.last_match(1), raw: "[#{Regexp.last_match(0)}]")
22
+ in [/\A\$ (.*)\z/]
23
+ FormulaBracket.new(content:, formula: Regexp.last_match(1), raw: "[#{Regexp.last_match(0)}]")
24
+ in [/\A(.*).icon\z/]
25
+ IconBracket.new(content:, icon_name: Regexp.last_match(1))
26
+ in [%r{\A(https?://[^\s]+)\s+(https?://[^\s\]]*\.(png|jpe?g|gif|svg|webp)(\?[^\s\]]+)?)\z}]
27
+ ImageBracket.new(content:, link: Regexp.last_match(1), src: Regexp.last_match(2), raw: "[#{Regexp.last_match(0)}]")
28
+ in [%r{\A(https?://[^\s\]]*\.(png|jpe?g|gif|svg|webp)(\?[^\s\]]+)?)\s+(https?://[^\s]+)\z}]
29
+ ImageBracket.new(content:, link: Regexp.last_match(4), src: Regexp.last_match(1), raw: "[#{Regexp.last_match(0)}]")
30
+ in [%r{\A(https?://[^\s\]]*\.(png|jpe?g|gif|svg|webp))\z}]
31
+ ImageBracket.new(content:, link: nil, src: Regexp.last_match(1), raw: "[#{Regexp.last_match(0)}]")
32
+ in [%r{\A(https?://[^ \t]*)(\s+(.+))?\z}]
33
+ # match_external_link_precede
34
+ anchor = Regexp.last_match(3) || Regexp.last_match(1)
35
+ link = Regexp.last_match(1)
36
+ raw = "[#{Regexp.last_match(0)}]"
37
+ ExternalLinkBracket.new(content:, link:, anchor:, raw:)
38
+ in [%r{\A((.*\S)\s+)?(https?://[^\s]+)\z}]
39
+ # match_external_link_succeed
40
+ anchor = Regexp.last_match(2) || Regexp.last_match(3)
41
+ link = Regexp.last_match(3)
42
+ raw = "[#{Regexp.last_match(0)}]"
43
+ ExternalLinkBracket.new(content:, link:, anchor:, raw:)
44
+ in [%r{\A([_\*/\-"#%&'\(\)~\|\+<>{},\.]+) (.+)\z}]
45
+ # match_decorate
46
+ deco = Regexp.last_match(1)
47
+ text = Regexp.last_match(2)
48
+ font_size = deco.count('*') > 10 ? 10 : deco.count('*')
49
+ underlined = deco.include?('_')
50
+ deleted = deco.include?('-')
51
+ slanted = deco.include?('/')
52
+ DecorateBracket.new(
53
+ content:,
54
+ font_size:,
55
+ underlined:,
56
+ slanted:,
57
+ deleted:,
58
+ text:,
59
+ raw:
60
+ )
61
+ else
62
+ line_parser = LineParser.new
63
+ data = ParsedBracket.new(content:)
64
+ parsed = data
65
+ .then { |d| line_parser.parse_url(d) }
66
+ .then { |d| line_parser.parse_hashtag(d) }
67
+ if parsed.single_text? && parsed.content == content
68
+ anchor = parsed.first_content
69
+ link = make_link(anchor)
70
+ raw = raw_string
71
+ InternalLinkBracket.new(content:, link:, anchor:, raw:)
72
+ else
73
+ TextBracket.new(content: parsed.content, raw: raw_string)
74
+ end
75
+ end
76
+ end
77
+
78
+ def raw_string
79
+ "[#{content.map(&:to_s).join}]"
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Cosensee
6
+ # serializer for brackets
7
+ module BracketSerializer
8
+ def to_s = raw
9
+
10
+ def to_obj
11
+ unparsed = content.map do |elem|
12
+ if elem.is_a?(String)
13
+ elem
14
+ else
15
+ elem.to_obj
16
+ end
17
+ end
18
+
19
+ "[#{unparsed}]"
20
+ end
21
+
22
+ def to_json(*)
23
+ to_obj.to_json(*)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Cosensee
6
+ # parse a line
7
+ Code = Data.define(:content, :raw) do
8
+ alias_method :to_s, :raw
9
+
10
+ def to_obj
11
+ "`#{content}`"
12
+ end
13
+
14
+ def to_json(*)
15
+ to_obj.to_json(*)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Cosensee
6
+ # for codeblock
7
+ Codeblock = Data.define(:content, :raw) do
8
+ alias_method :to_s, :raw
9
+
10
+ def to_obj
11
+ "code:#{content}"
12
+ end
13
+
14
+ def to_json(*)
15
+ to_obj.to_json(*)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Cosensee
6
+ # for command line
7
+ CommandLine = Data.define(:content, :prompt, :raw) do
8
+ alias_method :to_s, :raw
9
+
10
+ def to_obj
11
+ "#{prompt} #{content}"
12
+ end
13
+
14
+ def to_json(*)
15
+ to_obj.to_json(*)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cosensee
4
+ # for Decorate
5
+ DecorateBracket = Data.define(
6
+ :content,
7
+ :font_size,
8
+ :underlined,
9
+ :slanted,
10
+ :deleted,
11
+ :text,
12
+ :raw
13
+ ) do
14
+ include BracketSerializer
15
+ end
16
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cosensee
4
+ # for double Bracket
5
+ DoubleBracket = Data.define(:content, :raw) do
6
+ def image?
7
+ content.size == 1 && content[0].match?(/\.(png|jpg)$/)
8
+ end
9
+
10
+ alias_method :to_s, :raw
11
+
12
+ def to_obj
13
+ "[[#{content}]]"
14
+ end
15
+
16
+ def to_json(*)
17
+ to_obj.to_json(*)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cosensee
4
+ # for empty bracket
5
+ EmptyBracket = Data.define(:content, :raw) do
6
+ include BracketSerializer
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cosensee
4
+ class Error < StandardError; end
5
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cosensee
4
+ # for Formula
5
+ ExternalLinkBracket = Data.define(:content, :link, :anchor, :raw) do
6
+ include BracketSerializer
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cosensee
4
+ # for Formula
5
+ FormulaBracket = Data.define(:content, :formula, :raw) do
6
+ include BracketSerializer
7
+ end
8
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Cosensee
6
+ # parse a hash tag
7
+ HashTag = Data.define(:content, :raw) do
8
+ alias_method :to_s, :raw
9
+ alias_method :anchor, :content
10
+
11
+ def to_obj
12
+ "##{content}"
13
+ end
14
+
15
+ def to_json(*)
16
+ to_obj.to_json(*)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'erubi'
5
+ require 'tilt'
6
+ require 'fileutils'
7
+
8
+ module Cosensee
9
+ # generate HTML files
10
+ class HtmlBuilder
11
+ def initialize(project, root_dir: nil)
12
+ @project = project
13
+ @templates_dir = File.join(__dir__, '../../templates')
14
+ @root_dir = root_dir || File.join(Dir.pwd, '.out')
15
+ FileUtils.mkdir_p(@root_dir)
16
+ end
17
+
18
+ attr_reader :project, :root_dir, :templates_dir
19
+
20
+ def build_all
21
+ build_index(project)
22
+
23
+ # build all pages
24
+ project.pages.each do |page|
25
+ build_page(page)
26
+ end
27
+
28
+ # build all orphan (title only) pages
29
+ project.page_store.orphan_page_titles.each do |title|
30
+ build_page_only_title(title)
31
+ end
32
+ end
33
+
34
+ def build_index(project)
35
+ template = Tilt::ErubiTemplate.new(File.join(templates_dir, 'index.html.erb'))
36
+ output = template.render(nil, project:)
37
+ File.write(File.join(root_dir, 'index.html'), output)
38
+ end
39
+
40
+ def build_page(page)
41
+ template = Tilt::ErubiTemplate.new(File.join(templates_dir, 'page.html.erb'))
42
+ output = template.render(nil, project:, page:, title: page.title)
43
+ File.write(File.join(root_dir, page.link_path), output)
44
+ end
45
+
46
+ def build_page_only_title(title)
47
+ path = File.join(root_dir, "#{title.gsub(/ /, '_').gsub('/', '%2F')}.html")
48
+ return if File.exist?(path)
49
+
50
+ template = Tilt::ErubiTemplate.new(File.join(templates_dir, 'page.html.erb'))
51
+ output = template.render(nil, project:, page: nil, title:)
52
+ File.write(path, output)
53
+ end
54
+
55
+ def page_title(page)
56
+ "#{page.title} | #{project.title}"
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cgi/util'
4
+
5
+ module Cosensee
6
+ # parser of Bracket
7
+ module HtmlEncodable
8
+ def escape_html(str)
9
+ CGI.escape_html(str)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cosensee
4
+ # for Icon
5
+ IconBracket = Data.define(:content, :icon_name) do
6
+ include BracketSerializer
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cosensee
4
+ # for Image
5
+ ImageBracket = Data.define(:content, :link, :src, :raw) do
6
+ include BracketSerializer
7
+ end
8
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Cosensee
6
+ # for normal Text
7
+ Indent = Data.define(:content, :raw) do
8
+ def initialize(content: '', raw: '')
9
+ @level = content.size
10
+ super
11
+ end
12
+
13
+ attr_reader :level
14
+
15
+ alias_method :to_s, :raw
16
+
17
+ def to_obj
18
+ content
19
+ end
20
+
21
+ def to_json(*)
22
+ to_obj.to_json(*)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cosensee
4
+ # for Icon
5
+ InternalLinkBracket = Data.define(:content, :link, :anchor, :raw) do
6
+ include BracketSerializer
7
+ end
8
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Cosensee
6
+ # for Line
7
+ Line = Data.define(:content, :parsed) do
8
+ def self.from_array(lines_args)
9
+ lines_args.map { |arg| new(arg) }
10
+ end
11
+
12
+ def initialize(content:)
13
+ super(
14
+ content:,
15
+ parsed: LineParser.parse(content)
16
+ )
17
+ end
18
+
19
+ def brackets
20
+ content.scan(/\[.+?\]/)
21
+ end
22
+
23
+ def some_images?
24
+ brackets.any? { |item| item.match?(/\.(png|jpg)$/) }
25
+ end
26
+
27
+ def first_image
28
+ brackets.find { |item| item.match?(/\.(png|jpg)$/) }
29
+ end
30
+
31
+ def indent_level
32
+ content.match(/\A([\t ]*)/)[1].size
33
+ end
34
+
35
+ def indented?
36
+ content.match?(/\A[\t ]/)
37
+ end
38
+
39
+ def to_obj
40
+ content
41
+ end
42
+
43
+ def to_json(*)
44
+ to_obj.to_json(*)
45
+ end
46
+ end
47
+ end