cosensee 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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