cosensee 0.6.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +13 -2
- data/README.md +25 -7
- data/Rakefile +1 -1
- data/assets/js/search.js +42 -0
- data/assets/styles/input.css +3 -0
- data/exe/cosensee +36 -0
- data/lib/cosensee/api/page_data.rb +43 -0
- data/lib/cosensee/bracket_parser.rb +21 -16
- data/lib/cosensee/bracket_serializer.rb +3 -1
- data/lib/cosensee/cli/initializer.rb +64 -0
- data/lib/cosensee/cli/option.rb +65 -0
- data/lib/cosensee/cli/parser.rb +83 -0
- data/lib/cosensee/delegatable.rb +33 -0
- data/lib/cosensee/html_builder.rb +26 -17
- data/lib/cosensee/line_parser.rb +29 -16
- data/lib/cosensee/node/blank_bracket.rb +10 -0
- data/lib/cosensee/node/code.rb +20 -0
- data/lib/cosensee/node/codeblock.rb +32 -0
- data/lib/cosensee/node/command_line.rb +20 -0
- data/lib/cosensee/node/decorate_bracket.rb +23 -0
- data/lib/cosensee/node/double_bracket.rb +22 -0
- data/lib/cosensee/node/empty_bracket.rb +10 -0
- data/lib/cosensee/node/external_link_bracket.rb +10 -0
- data/lib/cosensee/node/formula_bracket.rb +10 -0
- data/lib/cosensee/node/gyazo_image_bracket.rb +10 -0
- data/lib/cosensee/node/hash_tag.rb +21 -0
- data/lib/cosensee/node/icon_bracket.rb +10 -0
- data/lib/cosensee/node/image_bracket.rb +10 -0
- data/lib/cosensee/node/indent.rb +27 -0
- data/lib/cosensee/node/internal_link_bracket.rb +10 -0
- data/lib/cosensee/node/link.rb +20 -0
- data/lib/cosensee/node/quote.rb +26 -0
- data/lib/cosensee/node/spotify_playlist_bracket.rb +10 -0
- data/lib/cosensee/node/text_bracket.rb +12 -0
- data/lib/cosensee/node/youtube_bracket.rb +10 -0
- data/lib/cosensee/page.rb +46 -16
- data/lib/cosensee/page_store.rb +25 -2
- data/lib/cosensee/parsed_line.rb +24 -6
- data/lib/cosensee/project.rb +21 -6
- data/lib/cosensee/render_class_findable.rb +13 -0
- data/lib/cosensee/tailwind_command.rb +24 -0
- data/lib/cosensee/tailwind_renderer/blank_bracket.rb +1 -1
- data/lib/cosensee/tailwind_renderer/code.rb +1 -1
- data/lib/cosensee/tailwind_renderer/codeblock.rb +22 -0
- data/lib/cosensee/tailwind_renderer/command_line.rb +1 -1
- data/lib/cosensee/tailwind_renderer/decorate_bracket.rb +1 -1
- data/lib/cosensee/tailwind_renderer/double_bracket.rb +2 -2
- data/lib/cosensee/tailwind_renderer/empty_bracket.rb +1 -1
- data/lib/cosensee/tailwind_renderer/external_link_bracket.rb +1 -1
- data/lib/cosensee/tailwind_renderer/formula_bracket.rb +1 -1
- data/lib/cosensee/tailwind_renderer/gyazo_image_bracket.rb +15 -0
- data/lib/cosensee/tailwind_renderer/hash_tag.rb +1 -1
- data/lib/cosensee/tailwind_renderer/icon_bracket.rb +4 -3
- data/lib/cosensee/tailwind_renderer/image_bracket.rb +3 -3
- data/lib/cosensee/tailwind_renderer/inline_svg_text.rb +37 -0
- data/lib/cosensee/tailwind_renderer/internal_link_bracket.rb +1 -1
- data/lib/cosensee/tailwind_renderer/link.rb +1 -1
- data/lib/cosensee/tailwind_renderer/page.rb +3 -18
- data/lib/cosensee/tailwind_renderer/parsed_line.rb +4 -3
- data/lib/cosensee/tailwind_renderer/quote.rb +2 -2
- data/lib/cosensee/tailwind_renderer/spotify_playlist_bracket.rb +11 -0
- data/lib/cosensee/tailwind_renderer/text_bracket.rb +2 -2
- data/lib/cosensee/tailwind_renderer/youtube_bracket.rb +11 -0
- data/lib/cosensee/tailwind_renderer.rb +6 -11
- data/lib/cosensee/user.rb +7 -1
- data/lib/cosensee/version.rb +1 -1
- data/lib/cosensee/web_content_generator.rb +77 -0
- data/lib/cosensee/web_server/static_file_handler.rb +43 -0
- data/lib/cosensee/web_server.rb +38 -0
- data/lib/cosensee.rb +45 -20
- data/templates/index.html.erb +49 -27
- data/templates/page.html.erb +85 -29
- metadata +114 -23
- data/lib/cosensee/blank_bracket.rb +0 -8
- data/lib/cosensee/code.rb +0 -18
- data/lib/cosensee/codeblock.rb +0 -18
- data/lib/cosensee/command_line.rb +0 -18
- data/lib/cosensee/decorate_bracket.rb +0 -16
- data/lib/cosensee/double_bracket.rb +0 -20
- data/lib/cosensee/empty_bracket.rb +0 -8
- data/lib/cosensee/external_link_bracket.rb +0 -8
- data/lib/cosensee/formula_bracket.rb +0 -8
- data/lib/cosensee/hash_tag.rb +0 -19
- data/lib/cosensee/icon_bracket.rb +0 -8
- data/lib/cosensee/image_bracket.rb +0 -8
- data/lib/cosensee/indent.rb +0 -25
- data/lib/cosensee/internal_link_bracket.rb +0 -8
- data/lib/cosensee/line.rb +0 -47
- data/lib/cosensee/link.rb +0 -18
- data/lib/cosensee/quote.rb +0 -22
- data/lib/cosensee/tailwind_renderer/codeblock_builder.rb +0 -37
- data/lib/cosensee/text_bracket.rb +0 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8d86134550a97a17819c4280b1805d789f96d53eecbfb27ca62ef84a893d0064
|
4
|
+
data.tar.gz: 1e3fb5ffcc426841dd1a68a9b9b23fd9d42dcc0f8232545c97dfcf01ffbc60e5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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:
|
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:
|
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
|
-
|
17
|
+
First, create project directory and move to it.
|
18
18
|
|
19
|
-
$
|
19
|
+
$ cosensee --init <project dir>
|
20
|
+
$ cd <project dir>
|
20
21
|
|
21
|
-
|
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
|
-
$
|
24
|
+
$ cosensee -f <json file>
|
24
25
|
|
25
|
-
|
26
|
+
If you want to output the HTML file to a directory other than `public`, use the `-d` option.
|
26
27
|
|
27
|
-
|
28
|
+
The HTML files will be output to the `public` directory. You can view them locally by using the `-s` option.
|
28
29
|
|
29
|
-
|
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
data/assets/js/search.js
ADDED
@@ -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
|
+
}
|
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
|
-
|
64
|
-
|
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
|
-
|
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
|
@@ -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,
|
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
|
-
@
|
15
|
-
|
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, :
|
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.
|
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
|
-
|
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
|
-
|
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(
|
47
|
+
path = File.join(output_dir, "#{title.gsub(/ /, '_').gsub('/', '%2F')}.html")
|
48
48
|
return if File.exist?(path)
|
49
49
|
|
50
|
-
|
51
|
-
|
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
|
56
|
-
"#{
|
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
|