sitefs 1.0.0.beta1

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 (68) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +7 -0
  3. data/.gitignore +13 -0
  4. data/.rspec +2 -0
  5. data/.travis.yml +4 -0
  6. data/Gemfile +4 -0
  7. data/Gemfile.lock +101 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +41 -0
  10. data/Rakefile +6 -0
  11. data/TODO.md +8 -0
  12. data/bin/sitefs +96 -0
  13. data/inline-site/_layout.html.erb +30 -0
  14. data/inline-site/_layout.md.html.erb +3 -0
  15. data/inline-site/advanced-md.page.md +18 -0
  16. data/inline-site/dir/_layout.md.html.erb +3 -0
  17. data/inline-site/dir/sub1/_layout.html.erb +7 -0
  18. data/inline-site/dir/sub1/sub2/deep-page.page.md +8 -0
  19. data/inline-site/ears.png +0 -0
  20. data/inline-site/gallery/IMG_4371.jpg +0 -0
  21. data/inline-site/gallery/Screen Shot 2016-04-21 at 5.57.53 PM.png +0 -0
  22. data/inline-site/gallery/Screen Shot 2016-04-25 at 2.06.46 PM.png +0 -0
  23. data/inline-site/gallery/Screen Shot 2016-04-27 at 9.31.28 PM.png +0 -0
  24. data/inline-site/gallery/_gallery.html.erb +1 -0
  25. data/inline-site/gallery/_permalink.html.erb +1 -0
  26. data/inline-site/gallery/photos.page.rb +23 -0
  27. data/inline-site/index.html +41 -0
  28. data/inline-site/index.page.html.erb +1 -0
  29. data/inline-site/single-markdown.page.md +12 -0
  30. data/inline-site/styles/_helper.scss +1 -0
  31. data/inline-site/styles/app.css +1 -0
  32. data/inline-site/styles/app.scss +5 -0
  33. data/inline-site/tagged/_tag_layout.html.erb +7 -0
  34. data/inline-site/tagged/public.tag-page.rb +8 -0
  35. data/lib/sitefs/attribute_set.rb +93 -0
  36. data/lib/sitefs/command_config.rb +79 -0
  37. data/lib/sitefs/dsl_context.rb +49 -0
  38. data/lib/sitefs/file_action.rb +155 -0
  39. data/lib/sitefs/file_action_set.rb +51 -0
  40. data/lib/sitefs/file_registry.rb +61 -0
  41. data/lib/sitefs/handler.rb +46 -0
  42. data/lib/sitefs/handlers/markdown.rb +102 -0
  43. data/lib/sitefs/handlers/noop.rb +15 -0
  44. data/lib/sitefs/handlers/ruby_gen.rb +33 -0
  45. data/lib/sitefs/handlers/scss.rb +63 -0
  46. data/lib/sitefs/handlers/single_erb.rb +49 -0
  47. data/lib/sitefs/handlers/tag_page.rb +11 -0
  48. data/lib/sitefs/handlers.rb +6 -0
  49. data/lib/sitefs/html_pipelines.rb +95 -0
  50. data/lib/sitefs/layout_type.rb +16 -0
  51. data/lib/sitefs/manifest.rb +98 -0
  52. data/lib/sitefs/manifest_file.rb +64 -0
  53. data/lib/sitefs/page.rb +44 -0
  54. data/lib/sitefs/page_render.rb +6 -0
  55. data/lib/sitefs/path_helper.rb +31 -0
  56. data/lib/sitefs/render_context.rb +31 -0
  57. data/lib/sitefs/render_result.rb +16 -0
  58. data/lib/sitefs/renderer.rb +65 -0
  59. data/lib/sitefs/renderer_pipeline.rb +94 -0
  60. data/lib/sitefs/servlet.rb +31 -0
  61. data/lib/sitefs/tag_page_dsl_context.rb +13 -0
  62. data/lib/sitefs/version.rb +3 -0
  63. data/lib/sitefs/walker.rb +94 -0
  64. data/lib/sitefs/watcher.rb +85 -0
  65. data/lib/sitefs.rb +19 -0
  66. data/sitefs.gemspec +47 -0
  67. data/test-walker.rb +20 -0
  68. metadata +335 -0
@@ -0,0 +1,11 @@
1
+ class Sitefs::Handlers::TagPage < Sitefs::Handlers::RubyGen
2
+ attr_accessor :registry
3
+
4
+ def dsl
5
+ @dsl ||= TagPageDslContext.new registry, path_helper
6
+ end
7
+
8
+ def delay_generation
9
+ true
10
+ end
11
+ end
@@ -0,0 +1,6 @@
1
+ module Sitefs
2
+ module Handlers
3
+ Autoloaded.module do |autoloading|
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,95 @@
1
+ require 'uri'
2
+
3
+ require 'html/pipeline'
4
+ require 'html/pipeline/rouge_filter'
5
+
6
+ module Sitefs
7
+ class TitleDeterminer < HTML::Pipeline::Filter
8
+ def call
9
+ if node = doc.css('h1:first-child').first
10
+ text = node.text.strip
11
+
12
+ result[:title] = node.text unless text.empty?
13
+ end
14
+
15
+ if node = doc.css('h1:first-child + h2').first
16
+ text = node.text.strip
17
+
18
+ result[:subtitle] = node.text unless text.empty?
19
+ end
20
+
21
+ if node = doc.css('h2 + p:nth-of-type(1)').first
22
+ text = node.text.strip
23
+
24
+ result[:description] = node.text[0..199] unless text.empty?
25
+ end
26
+
27
+ doc
28
+ end
29
+ end
30
+
31
+ class RelativePathFilter < HTML::Pipeline::Filter
32
+ def call
33
+ base_path = context[:image_base_path] || context[:base_path]
34
+ context_path = context[:context_path]
35
+
36
+ unless base_path || context_path
37
+ return doc
38
+ end
39
+
40
+ doc.search('img').each do |img|
41
+ next if img['src'].nil?
42
+
43
+ src = img['src'].strip
44
+
45
+ if src.start_with?('/') || src.start_with?('http')
46
+ elsif context_path
47
+ src = File.join(context_path, src)
48
+ end
49
+
50
+ if base_path && src.start_with?('/')
51
+ src = URI.join(base_path, src).to_s
52
+ end
53
+
54
+ img['src'] = src
55
+ end
56
+ doc
57
+
58
+ end
59
+
60
+ end
61
+
62
+ module HtmlPipelines
63
+ extend self
64
+
65
+ def content
66
+ HTML::Pipeline.new([
67
+ TitleDeterminer,
68
+ HTML::Pipeline::RougeFilter,
69
+ ])
70
+ end
71
+
72
+ def markdown
73
+ HTML::Pipeline.new(
74
+ [
75
+ HTML::Pipeline::MarkdownFilter,
76
+ TitleDeterminer,
77
+ HTML::Pipeline::AutolinkFilter,
78
+ HTML::Pipeline::RougeFilter,
79
+ ], {
80
+ gfm: true,
81
+ })
82
+ end
83
+
84
+ def finishing str, **opts
85
+
86
+ filters = [
87
+ RelativePathFilter,
88
+ HTML::Pipeline::AutolinkFilter,
89
+ HTML::Pipeline::RougeFilter,
90
+ ]
91
+
92
+ HTML::Pipeline.new(filters, **opts).call(str)
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,16 @@
1
+ module Sitefs
2
+ module LayoutType
3
+ extend self
4
+ def default
5
+ standard
6
+ end
7
+
8
+ def standard
9
+ :standard
10
+ end
11
+
12
+ def root
13
+ :root
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,98 @@
1
+ require 'json'
2
+
3
+ module Sitefs
4
+ MANIFEST_FILENAME = '.sitefs-manifest.json'
5
+
6
+ class Manifest
7
+ def initialize root_path
8
+ @root_path = root_path
9
+
10
+ if File.exist? manifest_path
11
+ @manifest = ManifestFile.from_file manifest_path
12
+ else
13
+ @manifest = {}
14
+ end
15
+ end
16
+
17
+ def clean!
18
+ @manifest = {}
19
+ save!
20
+ end
21
+
22
+ def manifest_path
23
+ File.join @root_path, MANIFEST_FILENAME
24
+ end
25
+
26
+ def resolve_path path
27
+ path.sub(@root_path, '').sub(/^\//, '').to_sym
28
+ end
29
+
30
+ def [] path
31
+ @manifest[resolve_path(path)]
32
+ end
33
+
34
+ def []= path, value
35
+ @manifest[resolve_path(path)] = ManifestFile.ensure(value)
36
+ end
37
+
38
+ def to_h
39
+ @manifest
40
+ end
41
+
42
+ def as_json *opts
43
+ to_h.inject({}) do |h, (k,v)|
44
+ h[k] = v unless v.nil?
45
+ h
46
+ end
47
+ end
48
+
49
+ def to_json *opts
50
+ as_json(*opts).to_json(*opts)
51
+ end
52
+
53
+ def save!
54
+ File.open(manifest_path, 'w') { |f| f.puts JSON.pretty_generate(as_json) }
55
+ end
56
+
57
+ def delete_generated
58
+ @manifest.each do |path, file|
59
+ if file[:'generated_at']
60
+ full_path = File.join(@root_path, path.to_s)
61
+ if File.exist? full_path
62
+ File.delete full_path
63
+ end
64
+ file[:'generated_at'] = nil
65
+ puts "Deleting: #{path}"
66
+ end
67
+ end
68
+
69
+ save!
70
+ end
71
+
72
+ ManifestFile::ALLOWED_KEYS.each do |key|
73
+ if key[-3..-1] == '_at'
74
+ verb = key[0...-3]
75
+ define_method "#{verb}_path" do |path|
76
+ now = Time.now
77
+ send "set_path_#{key}", path, now
78
+ end
79
+
80
+ define_method "#{verb}?" do |path|
81
+ !!send("path_#{key}", path)
82
+ end
83
+ end
84
+
85
+ define_method "path_#{key}" do |path|
86
+ @manifest[resolve_path(path)] && @manifest[resolve_path(path)][key]
87
+ end
88
+
89
+ define_method "set_path_#{key}" do |path, value|
90
+ @manifest[resolve_path(path)] ||= ManifestFile.new
91
+ @manifest[resolve_path(path)][key] = value
92
+ save!
93
+ @manifest[resolve_path(path)]
94
+ end
95
+ end
96
+
97
+ end
98
+ end
@@ -0,0 +1,64 @@
1
+ class Sitefs::ManifestFile
2
+ ALLOWED_KEYS = %i{generated_at uploaded_at uploaded_hash content_type}
3
+
4
+ def initialize **attrs
5
+ @attributes = attrs
6
+ end
7
+
8
+ def to_h
9
+ ALLOWED_KEYS.inject({}) do |h, key|
10
+ h[key] = @attributes[key] if @attributes[key]
11
+ h
12
+ end
13
+ end
14
+
15
+ def as_json *opts
16
+ hsh = to_h
17
+ if hsh.keys == [:content_type]
18
+ nil
19
+ else
20
+ hsh
21
+ end
22
+ end
23
+
24
+ def to_json *opts
25
+ as_json(*opts).to_json(*opts)
26
+ end
27
+
28
+ def [] key
29
+ raise ArgumentError.new('invalid key') unless ALLOWED_KEYS.include?(key)
30
+ @attributes[key]
31
+ end
32
+
33
+ def []= key, value
34
+ raise ArgumentError.new('invalid key') unless ALLOWED_KEYS.include?(key)
35
+
36
+ if value.nil?
37
+ @attributes.delete key
38
+ else
39
+ @attributes[key] = value
40
+ end
41
+ end
42
+
43
+ class << self
44
+ def ensure val
45
+ if val.is_a?(self)
46
+ return val
47
+ elsif val.respond_to? :to_h
48
+ return new(**val.to_h)
49
+ else
50
+ raise ArgumentError.new('can only convert hashes to ' + self.to_s)
51
+ end
52
+ end
53
+
54
+ def from_file file
55
+ content = File.read file
56
+ raw = JSON.parse content, symbolize_names: true
57
+
58
+ raw.inject({}) do |h, (k,v)|
59
+ h[k] = new **v if k && v
60
+ h
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,44 @@
1
+ class Sitefs::Page
2
+ class << self
3
+ def attribute_key *keys
4
+ keys.each do |key|
5
+ define_method(key) { attributes[key.to_s] }
6
+ define_method("#{key}=") { |v| attributes[key.to_s] = v }
7
+ end
8
+ end
9
+ end
10
+
11
+ attr_accessor :path, :attributes
12
+ attribute_key :title, :subtitle, :description, :tags, :published_at
13
+ attr_accessor :_rendering_template
14
+
15
+ def initialize path_helper, attributes = nil
16
+ @attributes = attributes
17
+ @path_helper = path_helper
18
+ @tags = []
19
+ end
20
+
21
+ def public_tags
22
+ tags.select {|tag| !tag.start_with?('_')}
23
+ end
24
+
25
+ def href
26
+ @path_helper.min_href_for @path
27
+ end
28
+
29
+ def href= href
30
+ @path = @path_helper.pathname_for href
31
+ end
32
+
33
+ def expanded_path
34
+ File.join(@path_helper.root_path, path)
35
+ end
36
+
37
+ def attributes
38
+ @attributes ||= AttributeSet.new
39
+ end
40
+
41
+ def [] key
42
+ attributes[key]
43
+ end
44
+ end
@@ -0,0 +1,6 @@
1
+ class Sitefs::PageRender
2
+ attr_reader :page, :template
3
+ def initialize page:, template:
4
+ @page, @template = page, template
5
+ end
6
+ end
@@ -0,0 +1,31 @@
1
+ class Sitefs::PathHelper
2
+ attr_reader :root_path, :source_file
3
+
4
+ def initialize root_path, source_file
5
+ @root_path = File.expand_path root_path
6
+ @source_file = File.expand_path source_file, @root_path
7
+ end
8
+
9
+ def pathname
10
+ source_file.sub(File.join(root_path, ''), '')
11
+ end
12
+
13
+ def pathname_for href
14
+ return nil unless href
15
+
16
+ dir = File.dirname source_file
17
+
18
+ File.expand_path(href, dir).sub(root_path, '')
19
+ end
20
+
21
+ def min_href_for path
22
+ return nil unless path
23
+
24
+ dir = File.join(File.dirname(source_file), '')
25
+ File.join(root_path, path, ).sub(dir, '').sub(root_path, '')
26
+ end
27
+
28
+ def full_path_for href
29
+ File.join(root_path, pathname_for(href))
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+ class Sitefs::RenderContext
2
+ attr_reader :current_page
3
+
4
+ def initialize(registry, current_page: nil)
5
+ @registry, @current_page = registry, current_page
6
+
7
+ @content_for = {}
8
+ end
9
+
10
+ def _set_content_for key, val
11
+ @content_for[key] = val
12
+ end
13
+
14
+ def _get_content_for key
15
+ @content_for[key]
16
+ end
17
+
18
+ def public_tags
19
+ @registry ? @registry.public_tags : []
20
+ end
21
+
22
+ def pages_tagged tag_str, all: false
23
+ @registry ? @registry.pages_tagged(tag_str, all: all) : []
24
+ end
25
+
26
+ class << self
27
+ def nil
28
+ @nil ||= new(nil)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,16 @@
1
+ module Sitefs
2
+ class RenderResult
3
+ attr_reader :text
4
+ def initialize text:, layout_type: LayoutType.default
5
+ @text, @layout_type = text, layout_type
6
+ end
7
+
8
+ def hit_root?
9
+ @layout_type == LayoutType.root
10
+ end
11
+
12
+ def to_s
13
+ text
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,65 @@
1
+ require 'erb'
2
+
3
+ module Sitefs
4
+ class Renderer
5
+ attr_reader :context
6
+ attr_accessor :layout_type
7
+
8
+ def initialize content
9
+ @erb = ERB.new(content, nil, nil, '@_out_buf')
10
+ @layout_type = LayoutType.default
11
+ @content_for = {}
12
+ end
13
+
14
+ def is_root!
15
+ @layout_type = LayoutType.root
16
+
17
+ if @context.respond_to? :hit_root!
18
+ @context.hit_root!
19
+ end
20
+ end
21
+
22
+ def is_root?
23
+ @layout_type == LayoutType.root
24
+ end
25
+
26
+ def render context = nil
27
+ @context = context
28
+ __result = @erb.result(binding)
29
+ @context = nil
30
+
31
+ RenderResult.new(text: __result, layout_type: @layout_type)
32
+ end
33
+
34
+ def content_for key, txt=nil, &block
35
+ if block
36
+ @_out_buf, _buf_was = '', @_out_buf
37
+ block.call
38
+ result = eval('@_out_buf', block.binding)
39
+ @_out_buf = _buf_w
40
+ else
41
+ result = txt
42
+ end
43
+
44
+ @context._set_content_for key, (result.strip)
45
+ end
46
+
47
+ def to_s
48
+ render
49
+ end
50
+
51
+ def title
52
+ current_page && current_page.title
53
+ end
54
+
55
+ def method_missing name, *args, &blk
56
+ if context.respond_to? name
57
+ context.send(name, *args, &blk)
58
+ elsif RenderContext.nil.respond_to? name
59
+ RenderContext.nil.send(name, *args, &blk)
60
+ else
61
+ raise "Rendering context respond to #{name}"
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,94 @@
1
+ class Sitefs::RendererPipeline
2
+ attr_reader :renderers
3
+
4
+ def initialize *args
5
+ @renderers = args
6
+ end
7
+
8
+ def << renderer
9
+ if renderer.respond_to?(:renderers)
10
+ renderer.renderers.each do |renderer|
11
+ self << renderer
12
+ end
13
+ else
14
+ @renderers << renderer
15
+ end
16
+ end
17
+
18
+ def render context=nil
19
+ renderers.reverse.reduce('') do |prev, renderer|
20
+ renderer = ensure_renderer renderer
21
+
22
+ result = renderer.render(context) do |arg=nil|
23
+ if arg
24
+ if context.respond_to?(:_get_content_for)
25
+ value = context._get_content_for arg
26
+ else
27
+ value = "<!-- No context, so couldn't render content for #{arg} -->"
28
+ end
29
+ else
30
+ value = prev
31
+ end
32
+
33
+ value
34
+ end
35
+
36
+ if result.hit_root?
37
+ break result.to_s
38
+ else
39
+ next result.to_s
40
+ end
41
+ end
42
+ end
43
+
44
+ def ensure_renderer renderer
45
+ if !renderer.respond_to?(:render)
46
+ if renderer.is_a?(String)
47
+ renderer = Renderer.new(renderer)
48
+ else
49
+ throw "#{renderer.inspect} is not a supported renderer"
50
+ end
51
+ end
52
+
53
+ renderer
54
+ end
55
+
56
+ class << self
57
+ def possible_files_for root_path:, source_file:, layout_name: '_layout.html.erb'
58
+ root_path = File.join(root_path,'')
59
+
60
+ files = []
61
+
62
+ begin
63
+ dir = File.join(File.dirname(source_file), '')
64
+
65
+ look_at = File.join(dir, layout_name)
66
+
67
+ if File.exist? look_at
68
+ files << look_at
69
+ end
70
+
71
+ source_file = dir
72
+ end until dir === root_path || root_path.length >= dir.length # safety to ensure the equality doesn't miss
73
+
74
+ files
75
+ end
76
+
77
+ def for content_text: nil, **args
78
+ layouts = possible_files_for **args
79
+
80
+ layout_text = layouts.reverse.map do |layout_file|
81
+ File.read(layout_file)
82
+ end
83
+
84
+ pipeline = new *layout_text
85
+
86
+ if content_text
87
+ pipeline << content_text
88
+ end
89
+
90
+ pipeline
91
+ end
92
+ end
93
+
94
+ end
@@ -0,0 +1,31 @@
1
+ require "webrick"
2
+
3
+ class Sitefs::Servlet < WEBrick::HTTPServlet::FileHandler
4
+ def initialize server, root, callbacks
5
+ @sitefs_config = server.config[:SitefsConfig]
6
+ super
7
+ end
8
+
9
+ def search_file(req, res, basename)
10
+ case @sitefs_config.index_format
11
+ when 'github', 'standard'
12
+ # /file.* > /file/index.html > /file.html
13
+ super || super(req, res, "#{basename}.html")
14
+ else
15
+ super
16
+ end
17
+ end
18
+
19
+ def do_GET(req, res)
20
+ rtn = super
21
+
22
+ content_type = @sitefs_config.manifest.path_content_type res.filename
23
+ res['content-type'] = content_type if content_type
24
+
25
+ # Disable caching to make dev easier
26
+ res['Cache-Control'] = "private, max-age=0, proxy-revalidate, " \
27
+ "no-store, no-cache, must-revalidate"
28
+
29
+ rtn
30
+ end
31
+ end
@@ -0,0 +1,13 @@
1
+ require 'forwardable'
2
+
3
+ class Sitefs::TagPageDslContext < Sitefs::DslContext
4
+ extend Forwardable
5
+ def_delegators :@registry, :pages, :public_tags, :pages_tagged
6
+
7
+ def initialize registry, path_helper
8
+ raise "registry required for #{self.class}" unless registry.is_a? FileRegistry
9
+
10
+ super path_helper
11
+ @registry = registry
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ module Sitefs
2
+ VERSION = "1.0.0.beta1"
3
+ end