isola 0.1.0 → 0.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d66e9eb2d33f8ab15d77fc1d71ed2c962d977b004c3eb340cd09c932a513a65e
4
- data.tar.gz: b6d134d39802939a0728b9ab59300968286da39f36b9eb13e79ebac380192efd
3
+ metadata.gz: d1d603cb56640c3cef57383a675c18280a05f7e31cb0b7e12e93a590ab5a3756
4
+ data.tar.gz: a143ef3efdb79ea497a74c0843189d8b40e21f1ab4bed895f8533648e50fb714
5
5
  SHA512:
6
- metadata.gz: ebeba2d27bb183e2a1ebe19b1dc899d3686bfb918d5d9cbebb6fa5cd1354c81ba85dfa950051fe192cfec00c7c132712b765b566385312dad15be79cf7f333cb
7
- data.tar.gz: 568589f57d81020434011aa145c01c33452bb04b4f8fee604b0b9e5a89447ba4dc3172168746afb5827f77e526961dbbf559e70db883d45a945e0c5a2f888b8a
6
+ metadata.gz: 21a9977148002a45b0bed065e2f22c12da1378b6f0354ee0758bcded391ee9c9b574511d4ce37fa8e0615077f02e5ce281d0950f11a42ae83612fcefab165216
7
+ data.tar.gz: f93813208135b400d2de4bb6e8d49c4de710fe722f704e1d7f13a5275e008323bfe64222f52e2fb2885648527cb920b459874cbe8978ff06540f32be674caa76
data/README.md CHANGED
@@ -19,6 +19,8 @@ gem install isola
19
19
  1. place markdown files in your directory
20
20
  2. `isola build`
21
21
 
22
+ See [Usage Guide](USAGE.md) / [使い方ガイド](USAGE.ja.md) for details.
23
+
22
24
  ## Development Policy
23
25
 
24
26
  This project does not use Agentic Coding at all. AI assistance in code reviews is not avoided.
data/USAGE.ja.md ADDED
@@ -0,0 +1,102 @@
1
+ # Isola 使い方ガイド
2
+
3
+ ERBを使ったシンプルなStatic Site Generator(SSG)です。
4
+
5
+ ## インストール
6
+
7
+ ```bash
8
+ gem install isola
9
+ ```
10
+
11
+ ## 基本的な使い方
12
+
13
+ ### 1. サイトディレクトリを作る
14
+
15
+ ```
16
+ my-site/
17
+ ├── _config.yaml
18
+ ├── _layouts/
19
+ │ └── default.html.erb
20
+ ├── _includes/
21
+ │ └── head.html.erb
22
+ ├── index.md
23
+ └── css/
24
+ └── main.css
25
+ ```
26
+
27
+ ### 2. 設定ファイル(`_config.yaml`)
28
+
29
+ ```yaml
30
+ url: https://example.com
31
+ title: My Site
32
+ destination: _site
33
+ default_language: ja
34
+ excludes:
35
+ - README.md
36
+ ```
37
+
38
+ すべて省略可能です。`destination`のデフォルトは`_site`です。
39
+
40
+ ### 3. ページを書く
41
+
42
+ Markdownファイルの先頭にYAML front-matterを記述します。
43
+
44
+ ```markdown
45
+ ---
46
+ layout: default
47
+ title: トップページ
48
+ ---
49
+
50
+ 本文をMarkdownで書きます。
51
+ ```
52
+
53
+ 拡張子`.md.erb`にすると、Markdown内でERBが使えます。
54
+
55
+ ### 4. レイアウトを作る
56
+
57
+ `_layouts/default.html.erb`:
58
+
59
+ ```erb
60
+ <html>
61
+ <head>
62
+ <title><%= page.title %></title>
63
+ </head>
64
+ <body>
65
+ <%= content %>
66
+ </body>
67
+ </html>
68
+ ```
69
+
70
+ テンプレート内では以下が使えます:
71
+
72
+ - `content` — ページ本文
73
+ - `page.title` など — front-matterの値
74
+ - `site.title`, `site.url`, `site.lang` — サイト設定
75
+ - `include 'head', key: value` — インクルードの挿入
76
+
77
+ ### 5. ビルド
78
+
79
+ ```bash
80
+ cd my-site
81
+ isola build
82
+ ```
83
+
84
+ `_site/`に生成されます。
85
+
86
+ ## ファイルの処理
87
+
88
+ Isolaが処理するテンプレートエンジンは現在 **ERB**(`.erb`)と **Markdown**(`.md`, `.markdown`, `.mkd`)のみです。
89
+
90
+ 拡張子の末尾から順に処理します。例えば `page.md.erb` の場合、まずERBを処理し、次にMarkdownを処理します。
91
+
92
+ 処理後にまだ拡張子が残っている場合はそのまま使います。残っていない場合は `.html` が付与されます。
93
+
94
+ | ソース | 処理 | 出力 |
95
+ |---|---|---|
96
+ | `page.md` | Markdown → HTML | `page.html` |
97
+ | `page.md.erb` | ERB → Markdown → HTML | `page.html` |
98
+ | `style.css.erb` | ERB | `style.css` |
99
+ | `index.html.erb` | ERB | `index.html` |
100
+ | `*.css`, `*.js` など | なし | そのままコピー |
101
+
102
+ `_`や`.`で始まるファイル・ディレクトリは自動的に除外されます(`_layouts/`と`_includes/`を除く)。
data/USAGE.md ADDED
@@ -0,0 +1,102 @@
1
+ # Isola Usage Guide
2
+
3
+ A simple Static Site Generator (SSG) using ERB.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ gem install isola
9
+ ```
10
+
11
+ ## Basic Usage
12
+
13
+ ### 1. Create a site directory
14
+
15
+ ```
16
+ my-site/
17
+ ├── _config.yaml
18
+ ├── _layouts/
19
+ │ └── default.html.erb
20
+ ├── _includes/
21
+ │ └── head.html.erb
22
+ ├── index.md
23
+ └── css/
24
+ └── main.css
25
+ ```
26
+
27
+ ### 2. Configuration (`_config.yaml`)
28
+
29
+ ```yaml
30
+ url: https://example.com
31
+ title: My Site
32
+ destination: _site
33
+ default_language: en
34
+ excludes:
35
+ - README.md
36
+ ```
37
+
38
+ All fields are optional. The default `destination` is `_site`.
39
+
40
+ ### 3. Write pages
41
+
42
+ Add YAML front-matter at the top of your Markdown files.
43
+
44
+ ```markdown
45
+ ---
46
+ layout: default
47
+ title: Top Page
48
+ ---
49
+
50
+ Write your content in Markdown.
51
+ ```
52
+
53
+ Use the `.md.erb` extension to enable ERB inside Markdown.
54
+
55
+ ### 4. Create layouts
56
+
57
+ `_layouts/default.html.erb`:
58
+
59
+ ```erb
60
+ <html>
61
+ <head>
62
+ <title><%= page.title %></title>
63
+ </head>
64
+ <body>
65
+ <%= content %>
66
+ </body>
67
+ </html>
68
+ ```
69
+
70
+ The following are available in templates:
71
+
72
+ - `content` — page body
73
+ - `page.title` etc. — front-matter values
74
+ - `site.title`, `site.url`, `site.lang` — site configuration
75
+ - `include 'head', key: value` — insert an include
76
+
77
+ ### 5. Build
78
+
79
+ ```bash
80
+ cd my-site
81
+ isola build
82
+ ```
83
+
84
+ Output is generated in `_site/`.
85
+
86
+ ## File Processing
87
+
88
+ Isola currently supports only **ERB** (`.erb`) and **Markdown** (`.md`, `.markdown`, `.mkd`) as template engines.
89
+
90
+ Extensions are processed from right to left. For example, `page.md.erb` is first processed as ERB, then as Markdown.
91
+
92
+ If an extension remains after processing, it is kept as-is. If no extension remains, `.html` is used.
93
+
94
+ | Source | Processing | Output |
95
+ |---|---|---|
96
+ | `page.md` | Markdown → HTML | `page.html` |
97
+ | `page.md.erb` | ERB → Markdown → HTML | `page.html` |
98
+ | `style.css.erb` | ERB | `style.css` |
99
+ | `index.html.erb` | ERB | `index.html` |
100
+ | `*.css`, `*.js` etc. | None | Copied as-is |
101
+
102
+ Files and directories starting with `_` or `.` are excluded automatically (except `_layouts/` and `_includes/`).
data/exe/isola CHANGED
@@ -3,20 +3,45 @@
3
3
 
4
4
  require "isola"
5
5
  require "thor"
6
+ require "listen"
6
7
 
7
8
  class IsolaCLI < Thor
8
9
  package_name "Isola"
10
+
9
11
  desc "build site", "build site on current directory"
10
12
  def build
13
+ site = construct_site
14
+ site.build
15
+ end
16
+
17
+ desc "serve", "build site and serve"
18
+ def serve
19
+ site = construct_site
20
+ site.build
21
+
22
+ site_dir = File.join(site.root_dir, site.config[:destination])
23
+ host = site.config[:host]
24
+ port = site.config[:port]
25
+ server = Isola::DevServer.new(site_dir, host, port)
26
+
27
+ watcher = Isola::Watcher.new(site) { server.notify_reload }
28
+ listener = Listen.to(File.expand_path(site.root_dir), &watcher.method(:handle_changes))
29
+ listener.start
30
+
31
+ puts "serving at http://#{host}:#{port}"
32
+ server.start
33
+ end
34
+
35
+ private
36
+
37
+ def construct_site
11
38
  config =
12
39
  if File.exist? "_config.yaml"
13
40
  File.read("_config.yaml")
14
41
  else
15
42
  ""
16
43
  end
17
- site = Isola::Site.new(config)
18
- site.collect_files
19
- site.process
44
+ Isola::Site.new(config)
20
45
  end
21
46
  end
22
47
 
@@ -0,0 +1,104 @@
1
+ require "webrick"
2
+
3
+ module Isola
4
+ class ReloadStream
5
+ def initialize
6
+ @queue = Queue.new
7
+ end
8
+
9
+ def readpartial(maxlen, buf = +"")
10
+ if !@data
11
+ @data = @queue.pop
12
+ @data.force_encoding(Encoding::ASCII_8BIT)
13
+ end
14
+
15
+ if @data.bytesize <= maxlen
16
+ buf.replace(@data)
17
+ @data = nil
18
+ else
19
+ buf.replace(@data.byteslice(0, maxlen))
20
+ @data = @data.byteslice(maxlen..-1) || ""
21
+ end
22
+
23
+ buf
24
+ rescue
25
+ raise EOFError
26
+ end
27
+
28
+ def notify
29
+ @queue.push("data: reload\n\n")
30
+ end
31
+
32
+ def close
33
+ @queue.close
34
+ end
35
+
36
+ def closed?
37
+ @queue.closed?
38
+ end
39
+ end
40
+
41
+ class DevServer
42
+ def initialize(root_dir, host, port)
43
+ @root_dir = File.expand_path(root_dir)
44
+ @host = host
45
+ @port = port
46
+ @streams = []
47
+ @mutex = Mutex.new
48
+ end
49
+
50
+ def start
51
+ @server = WEBrick::HTTPServer.new(BindAddress: @host, Port: @port)
52
+ @server.mount("/", LiveFileHandler, @root_dir)
53
+ @server.mount_proc("/_reload") do |req, res|
54
+ stream = ReloadStream.new
55
+ @mutex.synchronize { @streams << stream }
56
+ res["Content-Type"] = "text/event-stream"
57
+ res["Cache-Control"] = "no-cache"
58
+ res["Connection"] = "keep-alive"
59
+ res.chunked = true
60
+ res.body = stream
61
+ end
62
+
63
+ trap("INT") do
64
+ @streams.each(&:close)
65
+ @server.shutdown
66
+ end
67
+
68
+ @server.start
69
+ end
70
+
71
+ def remove_stream stream
72
+ @mutex.synchronize { @streams.delete(stream) }
73
+ end
74
+
75
+ def notify_reload
76
+ @mutex.synchronize {
77
+ @streams.reject!(&:closed?)
78
+ @streams.each(&:notify)
79
+ }
80
+ end
81
+
82
+ def shutdown
83
+ @mutex.synchronize {
84
+ @streams.each(&:close)
85
+ }
86
+ @server&.shutdown
87
+ end
88
+ end
89
+
90
+ # for live reload
91
+ class LiveFileHandler < WEBrick::HTTPServlet::FileHandler
92
+ RELOAD_SCRIPT = <<~HTML
93
+ <script>new EventSource("/_reload").onmessage=()=>location.reload()</script>
94
+ HTML
95
+ def do_GET(req, res)
96
+ super
97
+ if res["Content-Type"]&.include?("text/html")
98
+ html = res.body.is_a?(IO) ? res.body.read : res.body
99
+ res.body = html.sub(%r{</body>}i, "#{RELOAD_SCRIPT}</body>")
100
+ res["Content-Length"] = res.body.bytesize
101
+ end
102
+ end
103
+ end
104
+ end
@@ -17,6 +17,10 @@ module Isola
17
17
  collect(@root_dir)
18
18
  end
19
19
 
20
+ def ignore?(absolute_path)
21
+ !process_path?(absolute_path.delete_prefix("#{@root_dir}/"))
22
+ end
23
+
20
24
  private
21
25
 
22
26
  def collect dir
data/lib/isola/site.rb CHANGED
@@ -3,15 +3,19 @@ require "fileutils"
3
3
  module Isola
4
4
  class Site
5
5
  attr_accessor :config
6
- DEFAULT_CONFIG = {url: "http://example.com", title: "my awesome site", destination: "_site", default_language: "en"}.freeze
6
+ DEFAULT_CONFIG = {url: "http://example.com",
7
+ title: "my awesome site",
8
+ destination: "_site",
9
+ default_language: "en",
10
+ host: "127.0.0.1",
11
+ port: 4444}.freeze
7
12
  SUPPORTED_TILT_EXT = [".erb", ".md", ".markdown", ".mkd"]
8
13
  EXT_MAP = {".md" => ".html", ".mkd" => ".html", ".markdown" => ".html", "" => ".html"}
9
14
  def initialize(config)
10
15
  @config = DEFAULT_CONFIG.merge(YAML.safe_load(config, symbolize_names: true) || {})
11
16
  @config[:root_dir] ||= Dir.pwd
12
17
  @config[:excludes] ||= []
13
- @parsed_layouts = {}
14
- @parsed_includes = {}
18
+ collect_files
15
19
  end
16
20
 
17
21
  def title
@@ -38,19 +42,27 @@ module Isola
38
42
  EXT_MAP[ext]
39
43
  end
40
44
 
41
- def collect_files
42
- @file_handler = FileHandler.new(root_dir, excludes: @config[:excludes])
43
- end
44
-
45
- def process
45
+ def build
46
+ dest_dir = File.join(@file_handler.root_dir, @config[:destination])
47
+ FileUtils.rm_rf(dest_dir)
46
48
  @file_handler.pages.each do |name, path|
47
49
  page = Source.new(path, read_in_site(path))
48
- puts "processing #{path}..."
50
+ puts "building #{path}..."
49
51
  rendered, path = Context.new(page, self).render
50
- dest_path = File.join(@file_handler.root_dir, @config[:destination], path)
52
+ dest_path = File.join(dest_dir, path)
51
53
  FileUtils.mkdir_p(File.dirname(dest_path))
52
54
  File.write(dest_path, rendered)
53
55
  end
56
+ puts "done."
57
+ end
58
+
59
+ def rebuild
60
+ collect_files
61
+ build
62
+ end
63
+
64
+ def ignore?(path)
65
+ @file_handler.ignore?(path)
54
66
  end
55
67
 
56
68
  def layout name
@@ -63,6 +75,12 @@ module Isola
63
75
 
64
76
  private
65
77
 
78
+ def collect_files
79
+ @file_handler = FileHandler.new(root_dir, excludes: @config[:excludes])
80
+ @parsed_layouts = {}
81
+ @parsed_includes = {}
82
+ end
83
+
66
84
  def find_source(name, cache, store)
67
85
  cache[name] ||=
68
86
  begin
data/lib/isola/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Isola
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
@@ -0,0 +1,15 @@
1
+ module Isola
2
+ class Watcher
3
+ def initialize(site, &on_rebuild)
4
+ @site = site
5
+ @on_rebuild = on_rebuild
6
+ end
7
+
8
+ def handle_changes(modified, added, removed)
9
+ paths = [*modified, *added, *removed]
10
+ return if paths.all? { |path| @site.ignore?(path) }
11
+ @site.rebuild
12
+ @on_rebuild&.call
13
+ end
14
+ end
15
+ end
data/lib/isola.rb CHANGED
@@ -5,6 +5,8 @@ require_relative "isola/site"
5
5
  require_relative "isola/file_handler"
6
6
  require_relative "isola/source"
7
7
  require_relative "isola/context"
8
+ require_relative "isola/watcher"
9
+ require_relative "isola/dev_server"
8
10
 
9
11
  module Isola
10
12
  class Error < StandardError; end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: isola
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Satoshi Kojima
@@ -51,6 +51,34 @@ dependencies:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
53
  version: '1.5'
54
+ - !ruby/object:Gem::Dependency
55
+ name: webrick
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '1.9'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '1.9'
68
+ - !ruby/object:Gem::Dependency
69
+ name: listen
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '3.10'
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '3.10'
54
82
  description: ''
55
83
  email:
56
84
  - skoji@skoji.jp
@@ -62,14 +90,17 @@ files:
62
90
  - LICENSE
63
91
  - README.md
64
92
  - Rakefile
93
+ - USAGE.ja.md
94
+ - USAGE.md
65
95
  - exe/isola
66
96
  - lib/isola.rb
67
97
  - lib/isola/context.rb
98
+ - lib/isola/dev_server.rb
68
99
  - lib/isola/file_handler.rb
69
100
  - lib/isola/site.rb
70
101
  - lib/isola/source.rb
71
102
  - lib/isola/version.rb
72
- - sig/isola.rbs
103
+ - lib/isola/watcher.rb
73
104
  homepage: https://github.com/skoji/isola
74
105
  licenses: []
75
106
  metadata:
data/sig/isola.rbs DELETED
@@ -1,3 +0,0 @@
1
- module Isola
2
- VERSION: String
3
- end