cosensee 0.6.0 → 0.8.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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +13 -2
  3. data/README.md +25 -7
  4. data/Rakefile +1 -1
  5. data/assets/js/search.js +42 -0
  6. data/assets/styles/input.css +3 -0
  7. data/exe/cosensee +36 -0
  8. data/lib/cosensee/api/page_data.rb +43 -0
  9. data/lib/cosensee/bracket_parser.rb +21 -16
  10. data/lib/cosensee/bracket_serializer.rb +3 -1
  11. data/lib/cosensee/cli/initializer.rb +64 -0
  12. data/lib/cosensee/cli/option.rb +65 -0
  13. data/lib/cosensee/cli/parser.rb +83 -0
  14. data/lib/cosensee/delegatable.rb +33 -0
  15. data/lib/cosensee/html_builder.rb +26 -17
  16. data/lib/cosensee/line_parser.rb +29 -16
  17. data/lib/cosensee/node/blank_bracket.rb +10 -0
  18. data/lib/cosensee/node/code.rb +20 -0
  19. data/lib/cosensee/node/codeblock.rb +32 -0
  20. data/lib/cosensee/node/command_line.rb +20 -0
  21. data/lib/cosensee/node/decorate_bracket.rb +23 -0
  22. data/lib/cosensee/node/double_bracket.rb +22 -0
  23. data/lib/cosensee/node/empty_bracket.rb +10 -0
  24. data/lib/cosensee/node/external_link_bracket.rb +10 -0
  25. data/lib/cosensee/node/formula_bracket.rb +10 -0
  26. data/lib/cosensee/node/gyazo_image_bracket.rb +10 -0
  27. data/lib/cosensee/node/hash_tag.rb +21 -0
  28. data/lib/cosensee/node/icon_bracket.rb +10 -0
  29. data/lib/cosensee/node/image_bracket.rb +10 -0
  30. data/lib/cosensee/node/indent.rb +27 -0
  31. data/lib/cosensee/node/internal_link_bracket.rb +10 -0
  32. data/lib/cosensee/node/link.rb +20 -0
  33. data/lib/cosensee/node/quote.rb +26 -0
  34. data/lib/cosensee/node/spotify_playlist_bracket.rb +10 -0
  35. data/lib/cosensee/node/text_bracket.rb +12 -0
  36. data/lib/cosensee/node/youtube_bracket.rb +10 -0
  37. data/lib/cosensee/page.rb +46 -16
  38. data/lib/cosensee/page_store.rb +25 -2
  39. data/lib/cosensee/parsed_line.rb +24 -6
  40. data/lib/cosensee/project.rb +21 -6
  41. data/lib/cosensee/render_class_findable.rb +13 -0
  42. data/lib/cosensee/tailwind_command.rb +24 -0
  43. data/lib/cosensee/tailwind_renderer/blank_bracket.rb +1 -1
  44. data/lib/cosensee/tailwind_renderer/code.rb +1 -1
  45. data/lib/cosensee/tailwind_renderer/codeblock.rb +22 -0
  46. data/lib/cosensee/tailwind_renderer/command_line.rb +1 -1
  47. data/lib/cosensee/tailwind_renderer/decorate_bracket.rb +1 -1
  48. data/lib/cosensee/tailwind_renderer/double_bracket.rb +2 -2
  49. data/lib/cosensee/tailwind_renderer/empty_bracket.rb +1 -1
  50. data/lib/cosensee/tailwind_renderer/external_link_bracket.rb +1 -1
  51. data/lib/cosensee/tailwind_renderer/formula_bracket.rb +1 -1
  52. data/lib/cosensee/tailwind_renderer/gyazo_image_bracket.rb +15 -0
  53. data/lib/cosensee/tailwind_renderer/hash_tag.rb +1 -1
  54. data/lib/cosensee/tailwind_renderer/icon_bracket.rb +4 -3
  55. data/lib/cosensee/tailwind_renderer/image_bracket.rb +3 -3
  56. data/lib/cosensee/tailwind_renderer/inline_svg_text.rb +37 -0
  57. data/lib/cosensee/tailwind_renderer/internal_link_bracket.rb +1 -1
  58. data/lib/cosensee/tailwind_renderer/link.rb +1 -1
  59. data/lib/cosensee/tailwind_renderer/page.rb +3 -18
  60. data/lib/cosensee/tailwind_renderer/parsed_line.rb +4 -3
  61. data/lib/cosensee/tailwind_renderer/quote.rb +2 -2
  62. data/lib/cosensee/tailwind_renderer/spotify_playlist_bracket.rb +11 -0
  63. data/lib/cosensee/tailwind_renderer/text_bracket.rb +2 -2
  64. data/lib/cosensee/tailwind_renderer/youtube_bracket.rb +11 -0
  65. data/lib/cosensee/tailwind_renderer.rb +6 -11
  66. data/lib/cosensee/user.rb +7 -1
  67. data/lib/cosensee/version.rb +1 -1
  68. data/lib/cosensee/web_content_generator.rb +77 -0
  69. data/lib/cosensee/web_server/static_file_handler.rb +43 -0
  70. data/lib/cosensee/web_server.rb +38 -0
  71. data/lib/cosensee.rb +45 -20
  72. data/templates/index.html.erb +49 -27
  73. data/templates/page.html.erb +85 -29
  74. metadata +114 -23
  75. data/lib/cosensee/blank_bracket.rb +0 -8
  76. data/lib/cosensee/code.rb +0 -18
  77. data/lib/cosensee/codeblock.rb +0 -18
  78. data/lib/cosensee/command_line.rb +0 -18
  79. data/lib/cosensee/decorate_bracket.rb +0 -16
  80. data/lib/cosensee/double_bracket.rb +0 -20
  81. data/lib/cosensee/empty_bracket.rb +0 -8
  82. data/lib/cosensee/external_link_bracket.rb +0 -8
  83. data/lib/cosensee/formula_bracket.rb +0 -8
  84. data/lib/cosensee/hash_tag.rb +0 -19
  85. data/lib/cosensee/icon_bracket.rb +0 -8
  86. data/lib/cosensee/image_bracket.rb +0 -8
  87. data/lib/cosensee/indent.rb +0 -25
  88. data/lib/cosensee/internal_link_bracket.rb +0 -8
  89. data/lib/cosensee/line.rb +0 -47
  90. data/lib/cosensee/link.rb +0 -18
  91. data/lib/cosensee/quote.rb +0 -22
  92. data/lib/cosensee/tailwind_renderer/codeblock_builder.rb +0 -37
  93. data/lib/cosensee/text_bracket.rb +0 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ca076a9a5e18e0bd0f5c1825e35714536bcfad2921b8d3e997a3fe86140314b4
4
- data.tar.gz: e28ec42e8750b9426275e78ba6b238b358217d8c97c7d95c1134a5f54f6f341c
3
+ metadata.gz: 8d86134550a97a17819c4280b1805d789f96d53eecbfb27ca62ef84a893d0064
4
+ data.tar.gz: 1e3fb5ffcc426841dd1a68a9b9b23fd9d42dcc0f8232545c97dfcf01ffbc60e5
5
5
  SHA512:
6
- metadata.gz: 72a7eb0c65b77920c3b1784db4bf58f9d984c0f660dbda56cab55ac2ac91d83ecf3e47b08ffb413d08e1601180f1258adcb141470a1dcbe5b653bd8d77232d7b
7
- data.tar.gz: 1a8bef01b367b5d8f665236e0940fb4cf823134d4cbe8a3955eab932cf76e63d21c9548b813acadfd16c5da57ee17a40cf350ed093e1c23f3955ede40862954f
6
+ metadata.gz: 73a4994a814da445c620e34ab927c5f0da3e22219a1189d588ae3361f89802f90b2e07d90e2b24239a4946d36d13b677302e01dd8cf60563aa04d558d555fc28
7
+ data.tar.gz: 4a519da076c4fafe6e82c4224c7358c1c2653d5ae95a1e64cef666c3b46cdcdcaa8020a6ba8adad4f9f77c10d9990143ccc96a7cbe94205d1f4010901b1320e9
data/.rubocop.yml CHANGED
@@ -5,6 +5,7 @@ AllCops:
5
5
  NewCops: enable
6
6
 
7
7
  require:
8
+ - rubocop-factory_bot
8
9
  - rubocop-rake
9
10
  - rubocop-rspec
10
11
 
@@ -17,7 +18,7 @@ Layout/MultilineMethodCallIndentation:
17
18
  EnforcedStyle: indented_relative_to_receiver
18
19
 
19
20
  Metrics/AbcSize:
20
- Max: 60
21
+ Max: 80
21
22
 
22
23
  Metrics/ClassLength:
23
24
  Max: 300
@@ -32,11 +33,14 @@ Metrics/MethodLength:
32
33
  Max: 80
33
34
 
34
35
  Metrics/ParameterLists:
35
- Max: 8
36
+ Max: 10
36
37
 
37
38
  Metrics/PerceivedComplexity:
38
39
  Max: 20
39
40
 
41
+ Style/FetchEnvVar:
42
+ Enabled: false
43
+
40
44
  Style/MinMaxComparison:
41
45
  Enabled: false
42
46
 
@@ -52,5 +56,12 @@ RSpec/ExampleLength:
52
56
  RSpec/MultipleExpectations:
53
57
  Enabled: false
54
58
 
59
+ RSpec/MultipleMemoizedHelpers:
60
+ Max: 7
61
+
55
62
  Style/NumericPredicate:
56
63
  Enabled: false
64
+
65
+ Style/HashSyntax:
66
+ EnforcedStyle: ruby19_no_mixed_keys
67
+ EnforcedShorthandSyntax: always
data/README.md CHANGED
@@ -14,19 +14,37 @@ If you use bundler, add to the application's Gemfile by executing:
14
14
 
15
15
  ## Usage
16
16
 
17
- To output HTML, specify the JSON file from Cosense as an argument and run `bin/build` with Cosense JSON dump file.
17
+ First, create project directory and move to it.
18
18
 
19
- $ bin/build <json file>
19
+ $ cosensee --init <project dir>
20
+ $ cd <project dir>
20
21
 
21
- The HTML files will be output to the .out directory. You can view them locally by using the bin/server command.
22
+ To output HTML, specify the JSON file from Cosense as an argument and run `cosensee` with Cosense JSON page-data file.
22
23
 
23
- $ $bin/server
24
+ $ cosensee -f <json file>
24
25
 
25
- By adding a Cosense JSON file as an argument, the build process will be executed before starting the server.
26
+ If you want to output the HTML file to a directory other than `public`, use the `-d` option.
26
27
 
27
- $ $bin/server <json file>
28
+ The HTML files will be output to the `public` directory. You can view them locally by using the `-s` option.
28
29
 
29
- The default port used by bin/server is 1212.
30
+ $ cosensee -f <json file> -s
31
+
32
+ The default port used by the local server is `1212`. If you want to change the port, use the `-p` option.
33
+
34
+ $ cosensee -f <json file> -s -p 3000
35
+
36
+ You can also use the `-h` option to display the help message.
37
+
38
+ $ cosensee --help
39
+
40
+ ## Deploy
41
+
42
+ Cosensee outputs static HTML and CSS files, so they can be deployed as-is.
43
+ Use the tools and resources specific to the environment you want to deploy to.
44
+
45
+ For example, in the case of Cloudflare Pages, you can deploy using [Wrangler](https://developers.cloudflare.com/workers/wrangler/) as follows:
46
+
47
+ $ npx wrangler pages deploy dist --project-name <cloudflare project name>
30
48
 
31
49
  ## Development
32
50
 
data/Rakefile CHANGED
@@ -7,4 +7,4 @@ require 'rubocop/rake_task'
7
7
  RSpec::Core::RakeTask.new(:spec)
8
8
  RuboCop::RakeTask.new
9
9
 
10
- task default: :spec
10
+ task default: %i[spec rubocop]
@@ -0,0 +1,42 @@
1
+ import Fuse from 'https://cdn.jsdelivr.net/npm/fuse.js@6.6.2/dist/fuse.esm.js';
2
+
3
+ export function createSearch() {
4
+ return {
5
+ query: '',
6
+ results: [],
7
+ fuse: null,
8
+ lastResults: [],
9
+
10
+ async init() {
11
+ try {
12
+ const res = await fetch("/search.json");
13
+ if (!res.ok) {
14
+ throw new Error(`Error HTTP status: ${res.status}`);
15
+ }
16
+ const data = await res.json();
17
+ this.fuse = new Fuse(data, { keys: ["title", "summary"], threshold: 0.3 });
18
+ } catch (error) {
19
+ console.error("Error fetching data:", error);
20
+ }
21
+ },
22
+
23
+ search() {
24
+ if (!this.fuse || this.query.trim() === '') {
25
+ this.clearResults();
26
+ return;
27
+ }
28
+ this.results = this.fuse.search(this.query);
29
+ this.lastResults = this.results;
30
+ },
31
+
32
+ clearResults() {
33
+ this.results = [];
34
+ },
35
+
36
+ restoreResults() {
37
+ if (this.query.trim() !== '' && this.lastResults.length > 0) {
38
+ this.results = this.lastResults;
39
+ }
40
+ },
41
+ };
42
+ }
@@ -0,0 +1,3 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
data/exe/cosensee ADDED
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'cosensee'
6
+
7
+ require 'console'
8
+ require 'dotenv'
9
+
10
+ Dotenv.load
11
+ logger = Console.logger
12
+
13
+ option = Cosensee::CLI::Parser.parse(ARGV)
14
+ exit 1 if option.failed?
15
+
16
+ if option.init?
17
+ Cosensee::CLI::Initializer.new(option:, logger:).run
18
+ exit 0
19
+ end
20
+
21
+ begin
22
+ content_generator = Cosensee::WebContentGenerator.new(option:, renderer_class: Cosensee::TailwindRenderer, logger:, sid: ENV['CONNECT_SID'])
23
+ content_generator.generate
24
+ rescue Cosensee::WebContentGenerator::Error => e
25
+ logger.error "Error: #{e.message}"
26
+ exit 1
27
+ end
28
+
29
+ exit 0 unless option.server?
30
+
31
+ web_server = Cosensee::WebServer.new(
32
+ dir: option.output_dir,
33
+ server_url: "http://localhost:#{option.port}",
34
+ logger:
35
+ )
36
+ web_server.start
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open-uri'
4
+
5
+ module Cosensee
6
+ module Api
7
+ # for page-data API
8
+ class PageData
9
+ def export(project_name:, sid:)
10
+ uri = "https://scrapbox.io/api/page-data/export/#{project_name}.json"
11
+ send_request(uri, sid)
12
+ end
13
+
14
+ def download(project_name:, sid:, filename:)
15
+ res = export(project_name:, sid:)
16
+
17
+ begin
18
+ File.binwrite(filename, res)
19
+ rescue SystemCallError => e
20
+ raise Cosensee::Error, "Failed to write to file '#{filename}': #{e.message}"
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def send_request(uri, sid)
27
+ cookies = "connect.sid=#{sid}"
28
+ parsed_uri = URI.parse(uri)
29
+
30
+ begin
31
+ response = parsed_uri.open('Cookie' => cookies)
32
+ response.read
33
+ rescue OpenURI::HTTPError => e
34
+ raise Cosensee::Error, "HTTP error while accessing #{uri}: #{e.message}"
35
+ rescue SocketError => e
36
+ raise Cosensee::Error, "Network error: #{e.message}"
37
+ rescue URI::InvalidURIError => e
38
+ raise Cosensee::Error, "Invalid URI: #{e.message}"
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -16,31 +16,37 @@ module Cosensee
16
16
 
17
17
  case content
18
18
  in [/\A\z/]
19
- EmptyBracket.new(content:, raw: '[]')
19
+ Node::EmptyBracket.new(content:, raw: '[]')
20
20
  in [/\A([ \t]+)\z/]
21
- BlankBracket.new(content:, blank: Regexp.last_match(1), raw: "[#{Regexp.last_match(0)}]")
21
+ Node::BlankBracket.new(content:, blank: Regexp.last_match(1), raw: "[#{Regexp.last_match(0)}]")
22
22
  in [/\A\$ (.*)\z/]
23
- FormulaBracket.new(content:, formula: Regexp.last_match(1), raw: "[#{Regexp.last_match(0)}]")
23
+ Node::FormulaBracket.new(content:, formula: Regexp.last_match(1), raw: "[#{Regexp.last_match(0)}]")
24
24
  in [/\A(.*).icon\z/]
25
- IconBracket.new(content:, icon_name: Regexp.last_match(1))
25
+ Node::IconBracket.new(content:, icon_name: Regexp.last_match(1), raw: Regexp.last_match(0))
26
+ in [%r{\A(https://www\.youtube\.com/watch\?v=([^&]+)(&.*)?)\z}]
27
+ Node::YoutubeBracket.new(content:, video_id: Regexp.last_match(2), raw: "[#{Regexp.last_match(0)}]")
26
28
  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)}]")
29
+ Node::ImageBracket.new(content:, link: Regexp.last_match(1), src: Regexp.last_match(2), raw: "[#{Regexp.last_match(0)}]")
28
30
  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)}]")
31
+ Node::ImageBracket.new(content:, link: Regexp.last_match(4), src: Regexp.last_match(1), raw: "[#{Regexp.last_match(0)}]")
30
32
  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)}]")
33
+ Node::ImageBracket.new(content:, link: nil, src: Regexp.last_match(1), raw: "[#{Regexp.last_match(0)}]")
34
+ in [%r{\A(https://gyazo.com/([0-9a-f]{32})(?:/raw)?)\z}]
35
+ Node::GyazoImageBracket.new(content:, link: nil, src: Regexp.last_match(1), image_id: Regexp.last_match(2), raw: "[#{Regexp.last_match(0)}]")
36
+ in [%r{\A(https://open.spotify.com/playlist/(\w+))\z}]
37
+ Node::SpotifyPlaylistBracket.new(content:, playlist_id: Regexp.last_match(2), src: Regexp.last_match(1), raw: "[#{Regexp.last_match(0)}]")
32
38
  in [%r{\A(https?://[^ \t]*)(\s+(.+))?\z}]
33
39
  # match_external_link_precede
34
40
  anchor = Regexp.last_match(3) || Regexp.last_match(1)
35
41
  link = Regexp.last_match(1)
36
42
  raw = "[#{Regexp.last_match(0)}]"
37
- ExternalLinkBracket.new(content:, link:, anchor:, raw:)
43
+ Node::ExternalLinkBracket.new(content:, link:, anchor:, raw:)
38
44
  in [%r{\A((.*\S)\s+)?(https?://[^\s]+)\z}]
39
45
  # match_external_link_succeed
40
46
  anchor = Regexp.last_match(2) || Regexp.last_match(3)
41
47
  link = Regexp.last_match(3)
42
48
  raw = "[#{Regexp.last_match(0)}]"
43
- ExternalLinkBracket.new(content:, link:, anchor:, raw:)
49
+ Node::ExternalLinkBracket.new(content:, link:, anchor:, raw:)
44
50
  in [%r{\A([_\*/\-"#%&'\(\)~\|\+<>{},\.]+) (.+)\z}]
45
51
  # match_decorate
46
52
  deco = Regexp.last_match(1)
@@ -49,7 +55,7 @@ module Cosensee
49
55
  underlined = deco.include?('_')
50
56
  deleted = deco.include?('-')
51
57
  slanted = deco.include?('/')
52
- DecorateBracket.new(
58
+ Node::DecorateBracket.new(
53
59
  content:,
54
60
  font_size:,
55
61
  underlined:,
@@ -60,17 +66,16 @@ module Cosensee
60
66
  )
61
67
  else
62
68
  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) }
69
+ parsed = ParsedBracket.new(content:)
70
+ .then { line_parser.parse_url(it) }
67
71
  if parsed.single_text? && parsed.content == content
68
72
  anchor = parsed.first_content
69
73
  link = make_link(anchor)
70
74
  raw = raw_string
71
- InternalLinkBracket.new(content:, link:, anchor:, raw:)
75
+ Node::InternalLinkBracket.new(content:, link:, anchor:, raw:)
72
76
  else
73
- TextBracket.new(content: parsed.content, raw: raw_string)
77
+ parsed = parsed.then { line_parser.parse_hashtag(it) }
78
+ Node::TextBracket.new(content: parsed.content, raw: raw_string)
74
79
  end
75
80
  end
76
81
  end
@@ -5,7 +5,9 @@ require 'json'
5
5
  module Cosensee
6
6
  # serializer for brackets
7
7
  module BracketSerializer
8
- def to_s = raw
8
+ def to_s
9
+ content.map(&:to_s).join
10
+ end
9
11
 
10
12
  def to_obj
11
13
  unparsed = content.map do |elem|
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cosensee
4
+ # for CLI
5
+ class CLI
6
+ # for initializer
7
+ class Initializer
8
+ def initialize(logger:, option:)
9
+ @logger = logger
10
+ @option = option
11
+ end
12
+
13
+ attr_reader :logger, :option
14
+
15
+ def run
16
+ logger.info 'Initializing...'
17
+ create_project_dir if option.init?
18
+ create_directories
19
+ create_files
20
+ logger.info 'Done!'
21
+ end
22
+
23
+ private
24
+
25
+ def create_project_dir
26
+ return if File.exist?(project_path)
27
+
28
+ FileUtils.mkdir_p(project_path)
29
+ logger.info "Created project directory: #{project_path}"
30
+ end
31
+
32
+ def create_directories
33
+ FileUtils.mkdir_p(project_path(option.output_dir))
34
+ FileUtils.mkdir_p(project_path(option.output_dir, option.css_dir))
35
+ end
36
+
37
+ def create_files
38
+ create_tailwind_config
39
+ end
40
+
41
+ def create_tailwind_config
42
+ return if File.exist?(project_path(Cosensee::TAILWIND_CONFIG_FILE))
43
+
44
+ logger.info 'Creating TailwindCSS config file...'
45
+ File.write(project_path(Cosensee::TAILWIND_CONFIG_FILE), <<~TAILWIND_CONFIG)
46
+ /** @type {import('tailwindcss').Config} */
47
+ module.exports = {
48
+ content: [
49
+ "dist/**/*.html"
50
+ ],
51
+ theme: {
52
+ extend: {},
53
+ },
54
+ plugins: [],
55
+ }
56
+ TAILWIND_CONFIG
57
+ end
58
+
59
+ def project_path(*relative_paths)
60
+ File.join(option.project_dir, *relative_paths)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cosensee
4
+ class CLI
5
+ # Option class for Cosensee::CLI
6
+ class Option
7
+ attr_accessor :port, :output_dir, :css_dir
8
+ attr_writer :failed, :server, :remote, :filename, :skip_tailwind_execution, :init, :clean
9
+
10
+ def initialize(filename: nil, remote: nil, port: DEFAULT_PORT, output_dir: DEFAULT_OUTPUT_DIR, css_dir: DEFAULT_CSS_DIR, server: nil, skip_tailwind_execution: false, init: nil, clean: false, base_url: nil)
11
+ @remote = remote
12
+ @filename = filename
13
+ @port = port
14
+ @output_dir = output_dir
15
+ @css_dir = css_dir
16
+ @server = server
17
+ @skip_tailwind_execution = skip_tailwind_execution
18
+ @failed = false
19
+ @clean = clean
20
+ @init = init
21
+ @base_url = base_url
22
+ end
23
+
24
+ def filename
25
+ @filename || ENV['COSENSEE_FILENAME']
26
+ end
27
+
28
+ def project_name
29
+ @remote || ENV['COSENSEE_PROJECT_NAME']
30
+ end
31
+
32
+ def base_url
33
+ @base_url || ENV['COSENSEE_BASE_URL']
34
+ end
35
+
36
+ def project_dir
37
+ @init || '.'
38
+ end
39
+
40
+ def remote?
41
+ !!@remote
42
+ end
43
+
44
+ def failed?
45
+ @failed
46
+ end
47
+
48
+ def server?
49
+ @server
50
+ end
51
+
52
+ def skip_tailwind_execution?
53
+ @skip_tailwind_execution
54
+ end
55
+
56
+ def clean?
57
+ @clean
58
+ end
59
+
60
+ def init?
61
+ !!@init
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'optparse'
4
+
5
+ module Cosensee
6
+ class CLI
7
+ # Parser class for Cosensee::CLI
8
+ class Parser
9
+ attr_reader :args, :option, :op
10
+
11
+ def self.parse(args)
12
+ new(args).parse
13
+ end
14
+
15
+ def initialize(args)
16
+ @args = args
17
+ @option = Option.new
18
+ @op = OptionParser.new
19
+ end
20
+
21
+ def parse
22
+ begin
23
+ op.banner = 'Usage: bin/build [-f <filename>] [-r <project_name>]'
24
+ op.version = Cosensee::VERSION
25
+
26
+ op.on('-f FILENAME', '--file FILENAME', 'Specify the file name') do |filename|
27
+ option.filename = filename
28
+ end
29
+ op.on('-r PROJECT_NAME', '--remote PROJECT_NAME', 'Retrieve the project pages file from the remote page-data API (with env var `CONNECT_SID`)') do |project_name|
30
+ option.remote = project_name
31
+ end
32
+ op.on('-p PORT', '--port PORT', "Specify port number of web server (default: #{DEFAULT_PORT})") do |port|
33
+ option.port = port
34
+ end
35
+ op.on('-d OUTPUT_DIR', '--dir OUTPUT_DIR', "Specify directory name of generated html files(default: #{DEFAULT_OUTPUT_DIR})") do |output_dir|
36
+ option.output_dir = output_dir
37
+ end
38
+ op.on('--css-dir CSS_DIR', "Specify directory name of generated html files(default: #{DEFAULT_CSS_DIR})") do |css_dir|
39
+ option.css_dir = css_dir
40
+ end
41
+ op.on('-s', '--server', 'Serves files by running a web server locally.') do
42
+ option.server = true
43
+ end
44
+
45
+ op.on('--skip-tailwind', 'Skip TailwindCSS execution') do
46
+ option.skip_tailwind_execution = true
47
+ end
48
+
49
+ op.on('--clean', 'Make dist directory clean') do
50
+ option.clean = true
51
+ end
52
+
53
+ op.on('--init PROJECT_DIR', 'Initialize the project with default files') do |project_dir|
54
+ option.init = project_dir
55
+ end
56
+
57
+ op.parse!(args)
58
+ rescue OptionParser::MissingArgument => e
59
+ return option_error("Error: option requires an argument: #{e.args.join(' ')}")
60
+ rescue OptionParser::InvalidOption => e
61
+ return option_error("Error: invalid option: #{e.args.join(' ')}")
62
+ end
63
+
64
+ if !option.filename && !option.server? && !option.init?
65
+ return option_error('Error: filename not found. You must specify -f, or use server with -s.')
66
+ elsif !option.filename && option.remote?
67
+ return option_error('Error: project_name not found. You must not specify project name -p without -f.')
68
+ end
69
+
70
+ option
71
+ end
72
+
73
+ private
74
+
75
+ def option_error(message)
76
+ puts message
77
+ puts op.help
78
+ option.failed = true
79
+ option
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cosensee
4
+ # Add `delegate` method that delegate method calls to another object
5
+ #
6
+ # Usage:
7
+ #
8
+ # ```ruby
9
+ # class Foo
10
+ # extend Delegatable
11
+ #
12
+ # def initialize(bar)
13
+ # @bar = bar
14
+ # end
15
+ #
16
+ # attr_reader :bar
17
+ #
18
+ # delegate :buz, :baz, to: :bar
19
+ # end
20
+ # ```
21
+ #
22
+ # This will define `buz` and `baz` method that call `bar.buz` and `bar.baz` respectively.
23
+ module Delegatable
24
+ def delegate(*methods, to:)
25
+ methods.each do |method|
26
+ define_method(method) do |*args, **kargs, &block|
27
+ target = send(to)
28
+ target.public_send(method, *args, **kargs, &block)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -8,16 +8,20 @@ require 'fileutils'
8
8
  module Cosensee
9
9
  # generate HTML files
10
10
  class HtmlBuilder
11
- def initialize(project, root_dir: nil)
11
+ def initialize(project, output_dir: nil, css_dir: nil, base_url: nil)
12
12
  @project = project
13
13
  @templates_dir = File.join(__dir__, '../../templates')
14
- @root_dir = root_dir || File.join(Dir.pwd, '.out')
15
- FileUtils.mkdir_p(@root_dir)
14
+ @output_dir = output_dir || File.join(Dir.pwd, Cosensee::DEFAULT_OUTPUT_DIR)
15
+ @css_dir = css_dir || Cosensee::DEFAULT_CSS_DIR
16
+ @base_url = base_url
17
+ FileUtils.mkdir_p(@output_dir)
16
18
  end
17
19
 
18
- attr_reader :project, :root_dir, :templates_dir
20
+ attr_reader :project, :output_dir, :css_dir, :templates_dir, :base_url
21
+
22
+ def build_all(clean: true)
23
+ purge_files if clean
19
24
 
20
- def build_all
21
25
  build_index(project)
22
26
 
23
27
  # build all pages
@@ -26,34 +30,39 @@ module Cosensee
26
30
  end
27
31
 
28
32
  # build all orphan (title only) pages
29
- project.page_store.orphan_page_titles.each do |title|
33
+ project.orphan_page_titles.each do |title|
30
34
  build_page_only_title(title)
31
35
  end
32
36
  end
33
37
 
34
38
  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)
39
+ render_html(src: 'index.html.erb', to: 'index.html', project:, css_dir:, base_url:)
38
40
  end
39
41
 
40
42
  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)
43
+ render_html(src: 'page.html.erb', to: page.link_path, project:, css_dir:, page:, title: page.title, base_url:)
44
44
  end
45
45
 
46
46
  def build_page_only_title(title)
47
- path = File.join(root_dir, "#{title.gsub(/ /, '_').gsub('/', '%2F')}.html")
47
+ path = File.join(output_dir, "#{title.gsub(/ /, '_').gsub('/', '%2F')}.html")
48
48
  return if File.exist?(path)
49
49
 
50
- template = Tilt::ErubiTemplate.new(File.join(templates_dir, 'page.html.erb'))
51
- output = template.render(nil, project:, page: nil, title:)
50
+ # make a dummy page
51
+ page = Page.new(title:, id: 0, created: Time.now, updated: Time.now, views: 0, lines: [])
52
+
53
+ template = Tilt::ErubiTemplate.new(File.join(templates_dir, 'page.html.erb'), escape_html: true)
54
+ output = template.render(nil, project:, page:, title:, css_dir:, base_url:)
52
55
  File.write(path, output)
53
56
  end
54
57
 
55
- def page_title(page)
56
- "#{page.title} | #{project.title}"
58
+ def purge_files
59
+ FileUtils.rm_rf(Dir.glob("#{output_dir}/*.html"))
60
+ end
61
+
62
+ def render_html(src:, to:, **args)
63
+ template = Tilt::ErubiTemplate.new(File.join(templates_dir, src), escape_html: true)
64
+ output = template.render(nil, **args)
65
+ File.write(File.join(output_dir, to), output)
57
66
  end
58
67
  end
59
68
  end