sitefs 1.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
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