impression 0.1 → 0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +1 -0
  3. data/.github/workflows/test.yml +32 -0
  4. data/CHANGELOG.md +23 -0
  5. data/Gemfile +5 -0
  6. data/Gemfile.lock +63 -32
  7. data/README.md +44 -2
  8. data/Rakefile +10 -0
  9. data/examples/markdown/_assets/style.css +98 -0
  10. data/examples/markdown/app.rb +11 -5
  11. data/examples/markdown/docs/tutorial.md +15 -0
  12. data/impression.gemspec +11 -7
  13. data/lib/impression/app.rb +9 -0
  14. data/lib/impression/file_tree.rb +124 -0
  15. data/lib/impression/file_watcher.rb +50 -0
  16. data/lib/impression/jamstack.rb +179 -0
  17. data/lib/impression/request_extensions/responses.rb +48 -0
  18. data/lib/impression/request_extensions/routing.rb +52 -0
  19. data/lib/impression/request_extensions.rb +8 -2
  20. data/lib/impression/resource.rb +168 -0
  21. data/lib/impression/version.rb +1 -1
  22. data/lib/impression.rb +8 -0
  23. data/test/helper.rb +94 -0
  24. data/test/jamstack/_layouts/article.rb +9 -0
  25. data/test/jamstack/_layouts/default.rb +12 -0
  26. data/test/jamstack/articles/2008-06-14-manu.md +6 -0
  27. data/test/jamstack/articles/2009-06-12-noatche.md +6 -0
  28. data/test/jamstack/articles/a.md +6 -0
  29. data/test/jamstack/assets/js/a.js +1 -0
  30. data/test/jamstack/bar.html +1 -0
  31. data/test/jamstack/baz/index.md +7 -0
  32. data/test/jamstack/foo.rb +7 -0
  33. data/test/jamstack/foobar.rb +10 -0
  34. data/test/jamstack/index.md +4 -0
  35. data/test/run.rb +5 -0
  36. data/test/static/bar/index.html +1 -0
  37. data/test/static/foo.html +1 -0
  38. data/test/static/index.html +1 -0
  39. data/test/static/js/a.js +1 -0
  40. data/test/test_app.rb +52 -0
  41. data/test/test_file_tree.rb +116 -0
  42. data/test/test_file_watcher.rb +57 -0
  43. data/test/test_jamstack.rb +263 -0
  44. data/test/test_resource.rb +201 -0
  45. metadata +57 -35
  46. data/lib/impression/pages.rb +0 -120
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f5a7e55ef9ff6c795a485a4b1e50456949932d923425ad447ada836ef2e69b0c
4
- data.tar.gz: 669f0781d30986542fb09aad0af058107a658281136ace716edfdefc18fefabb
3
+ metadata.gz: 8db17f3627f0b8de2ed13a81effd9689f8a292b0861104c276d343bf5ec23e8f
4
+ data.tar.gz: 79cc272aed0503e1514723815f7019002c780a890995ef7c6f2d4718d9704000
5
5
  SHA512:
6
- metadata.gz: 1cdbfd6167b1e1717e5a3fda6089c84bb0ed7c6d810b1dac9376a1cd81a38a473c097ba6dd6ecbd07c61145b86b5e10f13c8be02ac69c14c556ba6991f87e4f4
7
- data.tar.gz: cf2e071b60b5ed0aa0ab826d2721c55d15cabb7fa0f63e84d068f76eef9a39db68159d6a651ddf5371976c4703a72a910883e50becdd64ff365d4b955cb93192
6
+ metadata.gz: 46112700200e6018c10b9a84b3a90184ce590e0da7369096a787467af4c84aba6578c043c1c9ad520cfa6da83638492def2c53b8cc9d51407d74695e3c12af4f
7
+ data.tar.gz: 94b1e171d6161bdd452ce27a6ee5effde4c9a1fdb2fd0247b72449fcb7048a8c1800dc616c6e291d56ea639dcec8a4d2d2dad5e9aa6865ccc0cb96cac4525688
@@ -0,0 +1 @@
1
+ github: ciconia
@@ -0,0 +1,32 @@
1
+ name: Tests
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ build:
7
+ strategy:
8
+ fail-fast: false
9
+ matrix:
10
+ os: [ubuntu-latest, macos-latest]
11
+ ruby: ['2.7', '3.0', '3.1', 'head']
12
+
13
+ name: >-
14
+ ${{matrix.os}}, ${{matrix.ruby}}
15
+
16
+ runs-on: ${{matrix.os}}
17
+
18
+ env:
19
+ POLYPHONY_USE_LIBEV: "1"
20
+
21
+ steps:
22
+ - name: Setup machine
23
+ uses: actions/checkout@v1
24
+ - name: Setup Ruby and install deps
25
+ uses: ruby/setup-ruby@v1
26
+ with:
27
+ ruby-version: ${{matrix.ruby}}
28
+ bundler-cache: true # 'bundle install' and cache
29
+ cache-version: 1
30
+
31
+ - name: Run tests
32
+ run: bundle exec rake test
data/CHANGELOG.md CHANGED
@@ -0,0 +1,23 @@
1
+ # 0.5 2022-01-20
2
+
3
+ - Pass resource and request to rendered templates (#8)
4
+ - Implement Jamstack#page_list method (#7)
5
+
6
+ # 0.4 2022-01-19
7
+
8
+ - Remove deprecated Pages code (#6)
9
+
10
+ # 0.3 2022-01-19
11
+
12
+ - Implement basic Jamstack resource (#5)
13
+ - Add support for stock callable responses (#4)
14
+ - Make resource callable, rename `#respond` to `#callable` (#3)
15
+
16
+ ## 0.2 2022-01-13
17
+
18
+ - Implement `FileTree` (#2)
19
+ - Rename `Resource#render` to `Resource#respond`
20
+ - Rewrite resource routing (#1)
21
+ - Implement `Resource`
22
+
23
+ ## 0.1 2021-02-24
data/Gemfile CHANGED
@@ -1,3 +1,8 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
+
5
+ # %w{polyphony tipi qeweney papercraft}.each do |dep|
6
+ # dir = "../#{dep}"
7
+ # gem(dep, path: dir) if File.directory?(dir)
8
+ # end
data/Gemfile.lock CHANGED
@@ -1,66 +1,97 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- impression (0.1)
5
- kramdown (~> 2.3.0)
6
- kramdown-parser-gfm (~> 1.1.0)
7
- polyphony (~> 0.51.0)
8
- rouge (~> 3.26.0)
9
- tipi (~> 0.37)
4
+ impression (0.5)
5
+ modulation (~> 1.1)
6
+ papercraft (~> 0.14)
7
+ polyphony (~> 0.73.1)
8
+ qeweney (~> 0.15)
9
+ tipi (~> 0.45)
10
10
 
11
11
  GEM
12
12
  remote: https://rubygems.org/
13
13
  specs:
14
- ansi (1.5.0)
15
- builder (3.2.4)
16
- docile (1.3.5)
14
+ acme-client (2.0.9)
15
+ faraday (>= 0.17, < 2.0.0)
16
+ docile (1.4.0)
17
17
  escape_utils (1.2.1)
18
- http-2 (0.10.2)
19
- http_parser.rb (0.6.0)
20
- json (2.5.1)
21
- kramdown (2.3.0)
18
+ ever (0.1)
19
+ extralite (1.11)
20
+ faraday (1.9.3)
21
+ faraday-em_http (~> 1.0)
22
+ faraday-em_synchrony (~> 1.0)
23
+ faraday-excon (~> 1.1)
24
+ faraday-httpclient (~> 1.0)
25
+ faraday-multipart (~> 1.0)
26
+ faraday-net_http (~> 1.0)
27
+ faraday-net_http_persistent (~> 1.0)
28
+ faraday-patron (~> 1.0)
29
+ faraday-rack (~> 1.0)
30
+ faraday-retry (~> 1.0)
31
+ ruby2_keywords (>= 0.0.4)
32
+ faraday-em_http (1.0.0)
33
+ faraday-em_synchrony (1.0.0)
34
+ faraday-excon (1.1.0)
35
+ faraday-httpclient (1.0.1)
36
+ faraday-multipart (1.0.3)
37
+ multipart-post (>= 1.2, < 3)
38
+ faraday-net_http (1.0.1)
39
+ faraday-net_http_persistent (1.2.0)
40
+ faraday-patron (1.0.0)
41
+ faraday-rack (1.0.0)
42
+ faraday-retry (1.0.3)
43
+ h1p (0.2)
44
+ http-2 (0.11.0)
45
+ json (2.6.1)
46
+ kramdown (2.3.1)
22
47
  rexml
23
48
  kramdown-parser-gfm (1.1.0)
24
49
  kramdown (~> 2.0)
50
+ localhost (1.1.9)
25
51
  minitest (5.11.3)
26
- minitest-reporters (1.4.3)
27
- ansi
28
- builder
29
- minitest (>= 5.0)
30
- ruby-progressbar
31
- msgpack (1.4.2)
32
- polyphony (0.51.0)
33
- qeweney (0.5)
52
+ modulation (1.1)
53
+ msgpack (1.4.3)
54
+ multipart-post (2.1.1)
55
+ papercraft (0.14)
56
+ escape_utils (= 1.2.1)
57
+ kramdown (~> 2.3.0)
58
+ kramdown-parser-gfm (~> 1.1.0)
59
+ rouge (~> 3.26.0)
60
+ polyphony (0.73.1)
61
+ qeweney (0.15)
34
62
  escape_utils (~> 1.2.1)
35
63
  rack (2.2.3)
36
64
  rake (12.3.3)
37
- rexml (3.2.4)
38
- rouge (3.26.0)
39
- ruby-progressbar (1.11.0)
65
+ rexml (3.2.5)
66
+ rouge (3.26.1)
67
+ ruby2_keywords (0.0.5)
40
68
  simplecov (0.17.1)
41
69
  docile (~> 1.1)
42
70
  json (>= 1.8, < 3)
43
71
  simplecov-html (~> 0.10.0)
44
72
  simplecov-html (0.10.2)
45
- tipi (0.37)
46
- http-2 (~> 0.10.0)
47
- http_parser.rb (~> 0.6.0)
73
+ tipi (0.45)
74
+ acme-client (~> 2.0.8)
75
+ ever (~> 0.1)
76
+ extralite (~> 1.2)
77
+ h1p (~> 0.2)
78
+ http-2 (~> 0.11)
79
+ localhost (~> 1.1.4)
48
80
  msgpack (~> 1.4.2)
49
- polyphony (~> 0.51.0)
50
- qeweney (~> 0.5)
81
+ polyphony (~> 0.71)
82
+ qeweney (~> 0.14)
51
83
  rack (>= 2.0.8, < 2.3.0)
52
84
  websocket (~> 1.2.8)
53
85
  websocket (1.2.9)
54
86
 
55
87
  PLATFORMS
56
- ruby
88
+ x86_64-linux
57
89
 
58
90
  DEPENDENCIES
59
91
  impression!
60
92
  minitest (~> 5.11.3)
61
- minitest-reporters (~> 1.4.2)
62
93
  rake (~> 12.3.3)
63
94
  simplecov (~> 0.17.1)
64
95
 
65
96
  BUNDLED WITH
66
- 2.1.4
97
+ 2.3.3
data/README.md CHANGED
@@ -1,2 +1,44 @@
1
- # impression
2
- A modern web framework for Ruby
1
+ <h1 align="center">
2
+ Impression
3
+ </h1>
4
+
5
+ <h4 align="center">A modern web framework for Ruby</h4>
6
+
7
+ <p align="center">
8
+ <a href="http://rubygems.org/gems/impression">
9
+ <img src="https://badge.fury.io/rb/impression.svg" alt="Ruby gem">
10
+ </a>
11
+ <a href="https://github.com/digital-fabric/impression/actions?query=workflow%3ATests">
12
+ <img src="https://github.com/digital-fabric/impression/workflows/Tests/badge.svg" alt="Tests">
13
+ </a>
14
+ <a href="https://github.com/digital-fabric/impression/blob/master/LICENSE">
15
+ <img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT License">
16
+ </a>
17
+ </p>
18
+
19
+ ## What is Impression
20
+
21
+ > Impression is still in a very early stage of development. Things might not
22
+ > correctly, or not at all.
23
+
24
+ Impression is a modern web framework for Ruby. Unlike other web framework,
25
+ Impression does not impose any rigid structure or paradigm, but instead provides
26
+ a set of tools letting you build any kind of web app, by freely mixing different
27
+ kinds of web resources, be they static files, structured templates, Jamstack
28
+ sites, or dynamic APIs.
29
+
30
+ In Impression, resources can be composed into a hierarchical tree, allowing
31
+ combining of multiple web apps, or web subsystems, into a single URL hierarchy.
32
+
33
+ Impression is made for running on top of
34
+ [Tipi](https://github.com/digital-fabric/tipi), a new all-in-one web server for
35
+ Ruby.
36
+
37
+ ## I want to know more
38
+
39
+ Further documentation is coming real soon (TM)...
40
+
41
+ ## Contributing
42
+
43
+ Contributions in the form of issues, PRs or comments will be greatly
44
+ appreciated!
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/clean"
5
+
6
+ task :default => [:test]
7
+
8
+ task :test do
9
+ exec 'ruby test/run.rb'
10
+ end
@@ -0,0 +1,98 @@
1
+ * {
2
+ border: 0;
3
+ font: inherit;
4
+ font-size: 100%;
5
+ vertical-align: baseline;
6
+ margin: 0;
7
+ padding: 0;
8
+ color: black;
9
+ }
10
+
11
+ body {
12
+ font: normal 14px monospace;
13
+ }
14
+
15
+ h1, h2 {
16
+ font-weight: bold;
17
+ }
18
+
19
+ header {
20
+ margin-bottom: 20px;
21
+ }
22
+
23
+ a {
24
+ color: -webkit-link;
25
+ }
26
+
27
+ li {
28
+ margin-left: 40px;
29
+ }
30
+
31
+ em {
32
+ font-style: italic;
33
+ }
34
+
35
+ strong {
36
+ font-weight: bold;
37
+ }
38
+
39
+ p, ul, ol {
40
+ margin: 20px;
41
+ }
42
+
43
+ pre {
44
+ margin-bottom: 20px;
45
+ padding: 20px 5px;
46
+ overflow-x: visible;
47
+ }
48
+
49
+ pre code {
50
+ word-wrap: no;
51
+ }
52
+
53
+ hr {
54
+ display: block;
55
+ margin-top: 0.5em;
56
+ margin-bottom: 0.5em;
57
+ margin-left: auto;
58
+ margin-right: auto;
59
+ border-style: inset;
60
+ border-width: 1px;
61
+ }
62
+
63
+ #nav {
64
+ padding: 20px 5px;
65
+ }
66
+
67
+ #nav, #content, footer {
68
+ margin: 0 20px;
69
+ max-width: 580px;
70
+ }
71
+
72
+ #nav h1 {
73
+ margin-bottom: 20px;
74
+ }
75
+
76
+ iframe {
77
+ margin: 20px;
78
+ max-width: 530px;
79
+ }
80
+
81
+ #content img {
82
+ max-width: 100%;
83
+ }
84
+
85
+ #title {
86
+ margin-top: 40px;
87
+ font-weight: bold;
88
+ text-align: center;
89
+ }
90
+
91
+ #date {
92
+ display: block;
93
+ text-align: right;
94
+ }
95
+
96
+ #webring {
97
+ padding-bottom: 10px;
98
+ }
@@ -11,13 +11,19 @@ opts = {
11
11
  }
12
12
 
13
13
  puts "pid: #{Process.pid}"
14
- puts 'Listening on port 4411...'
15
14
 
16
- Tipi.serve('0.0.0.0', 4411, opts) do |req|
17
- app.call(req)
15
+ Tipi.full_service do |req|
16
+ req.route do
17
+ if req.host != 'noteflakes.com'
18
+ req.respond(nil, ':status' => Qeweney::Status::SERVICE_UNAVAILABLE)
19
+ stop_routing
20
+ end
21
+ req.on('assets') { req.serve_file(req.route_relative_path, base_path: File.join(__dir__, '_assets')) }
22
+ req.default { app.call(req) }
23
+ end
18
24
  rescue Exception => e
19
- p e
20
- puts e.backtrace.join("\n")
25
+ p [req.path, e]
26
+ # puts e.backtrace.join("\n")
21
27
  status = e.respond_to?(:http_status) ? e.http_status : 500
22
28
  req.respond(e.inspect, ':status' => status)
23
29
  end
@@ -0,0 +1,15 @@
1
+ # This is a tutorial
2
+
3
+ ## I'm an h2
4
+
5
+ ```ruby
6
+ "here's some code"
7
+
8
+ class A
9
+ def b
10
+ [:c, 42, /foo/]
11
+ end
12
+ end
13
+
14
+ p A.new.b
15
+ ```
data/impression.gemspec CHANGED
@@ -10,7 +10,10 @@ Gem::Specification.new do |s|
10
10
  s.files = `git ls-files`.split
11
11
  s.homepage = 'http://github.com/digital-fabric/impression'
12
12
  s.metadata = {
13
- "source_code_uri" => "https://github.com/digital-fabric/impression"
13
+ "source_code_uri" => "https://github.com/digital-fabric/impression",
14
+ "documentation_uri" => "https://www.rubydoc.info/gems/impression",
15
+ "homepage_uri" => "https://github.com/digital-fabric/impression",
16
+ "changelog_uri" => "https://github.com/digital-fabric/impression/blob/master/CHANGELOG.md"
14
17
  }
15
18
  s.rdoc_options = ["--title", "impression", "--main", "README.md"]
16
19
  s.extra_rdoc_files = ["README.md"]
@@ -19,15 +22,16 @@ Gem::Specification.new do |s|
19
22
 
20
23
  # s.executables = ['impression']
21
24
 
22
- s.add_runtime_dependency 'polyphony', '~>0.51.0'
23
- s.add_runtime_dependency 'tipi', '~>0.37'
24
- s.add_runtime_dependency 'kramdown', '~>2.3.0'
25
- s.add_runtime_dependency 'rouge', '~>3.26.0'
26
- s.add_runtime_dependency 'kramdown-parser-gfm', '~>1.1.0'
25
+ s.add_runtime_dependency 'polyphony', '~>0.73.1'
26
+ s.add_runtime_dependency 'tipi', '~>0.45'
27
+ s.add_runtime_dependency 'qeweney', '~>0.15'
28
+
29
+ # s.add_runtime_dependency 'rb-inotify', '~>0.10.1'
30
+ s.add_runtime_dependency 'papercraft', '~>0.14'
31
+ s.add_runtime_dependency 'modulation', '~>1.1'
27
32
 
28
33
 
29
34
  s.add_development_dependency 'rake', '~>12.3.3'
30
35
  s.add_development_dependency 'minitest', '~>5.11.3'
31
- s.add_development_dependency 'minitest-reporters', '~>1.4.2'
32
36
  s.add_development_dependency 'simplecov', '~>0.17.1'
33
37
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Impression
4
+ class App < Resource
5
+ def initialize(path: '/', **opts)
6
+ super(path: path, **opts)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require_relative './resource'
5
+
6
+ module Impression
7
+
8
+ # `FileTree` implements a resource that maps to a static file hierarchy.
9
+ class FileTree < Resource
10
+
11
+ # Initializes a `FileTree` resource.
12
+ #
13
+ # @param directory [String] static directory path
14
+ # @return [void]
15
+ def initialize(directory:, **props)
16
+ super(**props)
17
+ @directory = directory
18
+ @path_info_cache = {}
19
+ end
20
+
21
+ # Responds to a request.
22
+ #
23
+ # @param req [Qeweney::Request] request
24
+ # @return [void]
25
+ def call(req)
26
+ path_info = get_path_info(req.resource_relative_path)
27
+ render_from_path_info(req, path_info)
28
+ end
29
+
30
+ private
31
+
32
+ # Renders a response from the given response kind and path.
33
+ #
34
+ # @param req [Qeweney::Request] request
35
+ # @param kind [Symbol] path kind (`:not_found` or `:file`)
36
+ # @param path [String, nil] file path
37
+ def render_from_path_info(req, path_info)
38
+ case path_info[:kind]
39
+ when :not_found
40
+ req.respond(nil, ':status' => Qeweney::Status::NOT_FOUND)
41
+ when :file
42
+ render_file(req, path_info)
43
+ else
44
+ raise "Invalid path info kind #{kind.inspect}"
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ # Renders a file response for the given request and the given path info.
51
+ #
52
+ # @param req [Qeweney::Request] request
53
+ # @param path_info [Hash] path info
54
+ # @return [void]
55
+ def render_file(req, path_info)
56
+ req.serve_file(path_info[:path])
57
+ end
58
+
59
+ # Returns the path info for the given relative path.
60
+ #
61
+ # @param path [String] relative path
62
+ # @return [Hash] path info
63
+ def get_path_info(path)
64
+ @path_info_cache[path] ||= calculate_path_info(path)
65
+ end
66
+
67
+ # Calculates the path info for the given relative path.
68
+ #
69
+ # @param path [String] relative path
70
+ # @param add_ext [bool] whether to add .html extension if not found
71
+ # @return [Hash] path info
72
+ def calculate_path_info(path)
73
+ full_path = File.join(@directory, path)
74
+
75
+ path_info(full_path) || search_path_info_with_extension(full_path) || { kind: :not_found }
76
+ end
77
+
78
+ # Returns the path info for the given path. If the path refers to a file,
79
+ # returns a hash containing the file information. If the path refers to a
80
+ # directory, performs a search for an index file using #directory_path_info.
81
+ # Otherwise, returns nil.
82
+ #
83
+ # @param path [String] path
84
+ # @return [Hash, nil] path info
85
+ def path_info(path)
86
+ stat = File.stat(path) rescue nil
87
+ if !stat
88
+ nil
89
+ elsif stat.directory?
90
+ return directory_path_info(path)
91
+ else
92
+ return { kind: :file, path: path, ext: File.extname(path) }
93
+ end
94
+ end
95
+
96
+ # Calculates the path info for a directory. If an index file exists, its
97
+ # path info is returned, otherwise, returns nil.
98
+ #
99
+ # @param path [String] directory path
100
+ # @return [Hash, nil] path info
101
+ def directory_path_info(path)
102
+ search_path_info_with_extension(File.join(path, 'index'))
103
+ end
104
+
105
+ # Returns the supported path extensions for paths without extension.
106
+ #
107
+ # @return [Array] supported extensions
108
+ def supported_path_extensions
109
+ [:html]
110
+ end
111
+
112
+ # Searches for files with extensions for the given path.
113
+ #
114
+ # @param path [String] path
115
+ # @return [Hash, nil] path info
116
+ def search_path_info_with_extension(path)
117
+ supported_path_extensions.each do |ext|
118
+ info = path_info("#{path}.#{ext}")
119
+ return info if info
120
+ end
121
+ nil
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rb-inotify'
4
+
5
+ module Impression
6
+ class FileWatcher
7
+ def initialize(spec)
8
+ @notifier = INotify::Notifier.new
9
+ @buffer = []
10
+ setup(spec)
11
+ end
12
+
13
+ def setup(spec)
14
+ if File.file?(spec)
15
+ setup_single_file(spec)
16
+ elsif File.directory?(spec)
17
+ setup_directory(spec)
18
+ else
19
+ dir = File.dir_name(spec)
20
+ filename = File.basename(spec)
21
+ if dir =~ /\/\*\*$/
22
+ dir = File.dir_name(dir)
23
+ filename = "**/#{filename}"
24
+ end
25
+ setup_directory(dir, filename)
26
+ end
27
+ end
28
+
29
+ def setup_directory(dir)
30
+ @notifier.watch(dir, :moved_to, :create, :move, :attrib, :modify, :delete) do |event|
31
+ kind = event.flags.first
32
+
33
+ @buffer << [kind, event.absolute_name]
34
+ end
35
+ end
36
+
37
+ def each(&block)
38
+ @receiver = Fiber.current
39
+ io = @notifier.to_io
40
+ loop do
41
+ io.wait_readable
42
+ @notifier.process
43
+ next if @buffer.empty?
44
+
45
+ @buffer.each(&block)
46
+ @buffer.clear
47
+ end
48
+ end
49
+ end
50
+ end