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,79 @@
1
+ class Sitefs::CommandConfig
2
+ attr_accessor :index_format, :port, :ignore_patterns
3
+ attr_reader :root_path, :manifest
4
+ attr_writer :quiet, :force
5
+
6
+ def initialize seed
7
+ @root_path = '.'
8
+ @aws_region = 'aws_region'
9
+ @index_format = 'all-index'
10
+ @ignore_patterns = []
11
+
12
+ seed.each do |k, v|
13
+ instance_variable_set "@#{k}", v
14
+ end
15
+
16
+ self.root_path = @root_path
17
+
18
+ @manifest = Manifest.new root_path
19
+ end
20
+
21
+ def quiet?; @quiet; end
22
+ def force?; @force; end
23
+
24
+ def root_path= path
25
+ @root_path = File.join(File.expand_path(@root_path), '')
26
+ end
27
+
28
+ def aws_creds
29
+ @aws_creds ||= Aws::Credentials.new(@aws_access_key_id, @aws_secret_access_key)
30
+ end
31
+
32
+ def s3
33
+ @s3 ||= Aws::S3::Client.new credentials: aws_creds, region: @aws_region
34
+ end
35
+
36
+ def s3_bucket
37
+ @bucket ||= s3.bucket(@aws_s3_bucket)
38
+ end
39
+
40
+ def s3_pages
41
+ s3.list_objects(bucket: @aws_s3_bucket)
42
+ end
43
+
44
+ def put_s3_object path, content_type: nil, **args
45
+ if content_type
46
+ args[:content_type] = content_type
47
+ end
48
+
49
+ s3.put_object(bucket: @aws_s3_bucket, key: path, **args)
50
+ end
51
+
52
+ def s3_object_contents path
53
+ resp = s3.get_object(bucket: @aws_s3_bucket, key: path)
54
+
55
+ resp && resp.body.read
56
+ end
57
+
58
+ def s3_object path
59
+ return @s3_object[path.to_s] if @s3_object
60
+
61
+ @s3_object = {}
62
+
63
+ s3_pages.each do |request|
64
+ request.contents.each do |obj|
65
+ @s3_object[obj.key.to_s] = obj
66
+ end
67
+ end
68
+
69
+ @s3_object[path.to_s]
70
+ end
71
+
72
+ class << self
73
+ attr_reader :current
74
+
75
+ def from_file filename
76
+ @current = new YAML.load_file(filename)
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,49 @@
1
+ # To make sure that inside of the DSLs you can import yaml files
2
+ require 'yaml'
3
+
4
+ class Sitefs::DslContext
5
+ attr_reader :_pages_to_render, :path_helper
6
+ def initialize path_helper
7
+ @path_helper = path_helper
8
+ @_pages_to_render = []
9
+ end
10
+
11
+ def source_file
12
+ @path_helper.source_file
13
+ end
14
+
15
+ def path_for_href href
16
+ @path_helper.pathname_for href
17
+ end
18
+
19
+ def root_path
20
+ @path_helper.root_path
21
+ end
22
+
23
+ def _eval
24
+ instance_eval(File.read(source_file), source_file)
25
+ @_pages_to_render
26
+ end
27
+
28
+ def current_dir
29
+ File.dirname(source_file)
30
+ end
31
+
32
+ def files_like matcher
33
+ if matcher.is_a?(Regexp)
34
+ Dir[File.join(current_dir,'**/*')].grep(matcher)
35
+ else
36
+ Dir.glob(File.join(current_dir, matcher), File::FNM_CASEFOLD)
37
+ end
38
+ end
39
+
40
+ def new_page
41
+ Page.new path_helper
42
+ end
43
+
44
+ def render page, with:, **args
45
+ page._rendering_template = @path_helper.full_path_for(with)
46
+ @_pages_to_render << page
47
+ page
48
+ end
49
+ end
@@ -0,0 +1,155 @@
1
+ require 'digest/md5'
2
+ require 'fileutils'
3
+ require 'diffy'
4
+ require 'mime/types'
5
+
6
+ Diffy::Diff.default_format = :color
7
+
8
+ MAP_MIME_TYPE = MIME::Type.new('application/json+sourcemap') {|type| type.add_extensions('map')}
9
+ MIME::Types.add(MAP_MIME_TYPE)
10
+
11
+ class Sitefs::FileAction
12
+ attr_accessor :render_error, :content_type, :path
13
+
14
+ POSSIBLE_ACTIONS = %i{upload write}
15
+
16
+ def initialize possible_actions: POSSIBLE_ACTIONS, path:, content: nil, content_type: nil, &blk
17
+ @content_type = content_type || content_type_for(path)
18
+ @possible_actions = possible_actions
19
+ @path = path
20
+
21
+ @content = blk || content
22
+ end
23
+
24
+ def content_type_for path
25
+ unless mtype = MIME::Types.type_for(path).first
26
+ return
27
+ end
28
+
29
+ type = mtype.to_s
30
+ case mtype
31
+ when 'text/html', 'application/javascript'
32
+ type += '; charset=utf-8'
33
+ when MAP_MIME_TYPE
34
+ type = 'application/json'
35
+ end
36
+
37
+ type
38
+ end
39
+
40
+ def content
41
+ if @content.respond_to?(:call)
42
+ @content = @content.call
43
+ end
44
+
45
+ @content
46
+ end
47
+
48
+ def content_hash
49
+ @content_hash ||= Digest::MD5.hexdigest(content)
50
+ end
51
+
52
+ def destination_hash config, action, path
53
+ case action
54
+ when :write
55
+ if File.exist? path
56
+ Digest::MD5.hexdigest(File.read(path))
57
+ end
58
+ when :upload
59
+ if manifest = config.manifest.path_uploaded_hash(path)
60
+ manifest
61
+ elsif obj = config.s3_object(path)
62
+ obj.etag[1...-1]
63
+ end
64
+ end
65
+ end
66
+
67
+ # TODO: make this return a reason instead of bool
68
+ def should_write? config, action, path
69
+ return true if config.force?
70
+
71
+ d_hash = destination_hash(config, action, path)
72
+
73
+ result = !d_hash || (content_hash != d_hash)
74
+ if result && false
75
+ puts "should_write? #{path}"
76
+ puts " dest hash: #{d_hash}"
77
+ puts " cont hash: #{content_hash}"
78
+
79
+ remote_content = config.s3_object_contents(path)
80
+
81
+ diff = Diffy::Diff.new(remote_content, content)
82
+ puts " #{diff.to_s}"
83
+ end
84
+
85
+ result
86
+ end
87
+
88
+ def write_path_for path, config
89
+ return path unless path =~ /\.html$/
90
+
91
+ case config.index_format
92
+ when 'github', 'standard'
93
+ path
94
+ when 'all-index'
95
+ if path =~ /index\.html$/i
96
+ path
97
+ else
98
+ path.sub(/\.html$/, '/index.html')
99
+ end
100
+ when 'aws'
101
+ if path =~ /index\.html$/i
102
+ path
103
+ else
104
+ path.sub(/\.html$/, '')
105
+ end
106
+ end
107
+ end
108
+
109
+ def perform_action config, action
110
+ unless POSSIBLE_ACTIONS.include? action
111
+ raise ArgumentError.new("invalid action: #{action}")
112
+ end
113
+
114
+ unless @possible_actions.include? action
115
+ return
116
+ end
117
+
118
+ path = write_path_for @path, config
119
+
120
+ dirname = File.dirname path
121
+
122
+ config.manifest.set_path_content_type path, content_type if content_type
123
+
124
+ case action
125
+ when :write
126
+ if should_write? config, action, path
127
+ log :generate, path
128
+ FileUtils.mkdir_p dirname
129
+ File.open(path, 'w') { |file| file.write content }
130
+
131
+ config.manifest.generated_path path
132
+ else
133
+ log :identical, path
134
+ end
135
+ when :upload
136
+ if should_write? config, action, path
137
+ log :upload, path
138
+
139
+ config.put_s3_object path, content_type: content_type, body: content
140
+ config.manifest.uploaded_path path
141
+ config.manifest.set_path_uploaded_hash path, content_hash
142
+ else
143
+ log :identical, path
144
+ end
145
+ end
146
+ end
147
+
148
+ def log type, path
149
+ puts " #{type} - #{path}"
150
+ end
151
+
152
+ def to_s
153
+ inspect
154
+ end
155
+ end
@@ -0,0 +1,51 @@
1
+ class Sitefs::FileActionSet
2
+ include Enumerable
3
+
4
+ def initialize(file_actions)
5
+ @file_actions = file_actions
6
+ @errored_on = []
7
+ end
8
+
9
+ def each
10
+ @file_actions.each do |file_action|
11
+ yield file_action
12
+ end
13
+ end
14
+
15
+ def [] i
16
+ @file_actions[i]
17
+ end
18
+
19
+ def has_errors?
20
+ !@errored_on.empty?
21
+ end
22
+
23
+ def output_paths
24
+ map(&:path)
25
+ end
26
+
27
+ def includes_path? path
28
+ path = File.expand_path(path)
29
+
30
+ output_paths.include? path
31
+ end
32
+
33
+ def call config, action
34
+
35
+ each do |file_action|
36
+ begin
37
+ file_action.perform_action config, action
38
+ rescue Exception => e
39
+ STDERR.puts "Error: file(#{file_action.inspect})"
40
+ STDERR.puts e
41
+
42
+ raise e
43
+
44
+ file_action.render_error = e
45
+ @errored_on << file_action
46
+ end
47
+ end
48
+
49
+ self
50
+ end
51
+ end
@@ -0,0 +1,61 @@
1
+ class Sitefs::FileRegistry
2
+
3
+ def initialize
4
+ @handlers = []
5
+ end
6
+
7
+ def << handler
8
+ @pages = nil
9
+
10
+ if handler.respond_to? :registry=
11
+ handler.registry = self
12
+ end
13
+
14
+ @handlers << handler
15
+ end
16
+
17
+ def handlers
18
+ @handlers.select {|h| !h.delay_generation }
19
+ end
20
+
21
+ def delayed_handlers
22
+ @handlers.select {|h| h.delay_generation }
23
+ end
24
+
25
+ def pages
26
+ @pages ||= handlers.flat_map do |h|
27
+ h.pages
28
+ end.compact
29
+ end
30
+
31
+ def pages_tagged tag_str, all: false
32
+ # If it hasnt been published always sort by the same
33
+ default_time = Time.now
34
+
35
+ pages.select do |page|
36
+ is_tagged = page.tags.include? tag_str
37
+
38
+ if all
39
+ is_tagged
40
+ else
41
+ is_tagged && page.published_at
42
+ end
43
+ end.sort_by do |page|
44
+ [page.published_at || default_time, page.title]
45
+ end
46
+ end
47
+
48
+ def public_tags
49
+ pages.flat_map(&:public_tags).uniq
50
+ end
51
+
52
+ def gather_actions
53
+ all_handlers = handlers + delayed_handlers
54
+
55
+ actions = all_handlers.flat_map do |handler|
56
+ handler.file_actions self
57
+ end
58
+
59
+ FileActionSet.new(actions)
60
+ end
61
+ end
@@ -0,0 +1,46 @@
1
+ class Sitefs::Handler
2
+ attr_reader :root_path, :source_file
3
+
4
+ def initialize root_path, source_file
5
+ @root_path = root_path
6
+ @source_file = source_file
7
+ end
8
+
9
+ def should_generate? config
10
+ true
11
+ end
12
+
13
+ def delay_generation
14
+ false
15
+ end
16
+
17
+ def pages
18
+ []
19
+ end
20
+
21
+ def path_helper
22
+ @path_helper ||= PathHelper.new(root_path, source_file)
23
+ end
24
+
25
+ def file_actions registry
26
+ []
27
+ end
28
+
29
+ def tags
30
+ []
31
+ end
32
+
33
+ def html_pipeline_result
34
+ @html_pipeline_result ||= {}
35
+ end
36
+
37
+ def attributes
38
+ @attributes ||= AttributeSet.new.tap do |attrs|
39
+ attrs['tags'] ||= tags
40
+ attrs['title'] ||= html_pipeline_result[:title]
41
+ attrs['subtitle'] ||= html_pipeline_result[:subtitle]
42
+ attrs['description'] ||= html_pipeline_result[:subtitle]
43
+ end
44
+ end
45
+
46
+ end
@@ -0,0 +1,102 @@
1
+ class Sitefs::Handlers::Markdown < Sitefs::Handler
2
+ def output_path
3
+ @source_file.sub(/\.page.*$/, '.html').sub(root_path, '')
4
+ end
5
+
6
+ def path
7
+ File.join('', @source_file.gsub(/\.page.*$/, '').sub(@root_path, ''))
8
+ end
9
+
10
+ def config_strings
11
+ _read unless @config_strings
12
+ @config_strings
13
+ end
14
+
15
+ def markdown
16
+ _read unless @markdown
17
+ @markdown.join
18
+ end
19
+
20
+ MARKER_REG = /^\-{3,}$/
21
+
22
+ def _read
23
+ @config_strings = []
24
+ @markdown = []
25
+
26
+ in_config = false
27
+
28
+ File.open(@source_file).each_with_index do |line, i|
29
+ if i === 0 && line =~ MARKER_REG
30
+ in_config = true
31
+ next
32
+ end
33
+
34
+ if in_config
35
+ if line =~ MARKER_REG
36
+ in_config = false
37
+ next
38
+ else
39
+ @config_strings << line
40
+ end
41
+ else
42
+ @markdown << line
43
+ end
44
+ end
45
+ end
46
+
47
+ def attributes
48
+ @attributes ||= AttributeSet.from_yaml(config_strings.join).tap do |attrs|
49
+ attrs['tags'] ||= []
50
+ attrs['title'] ||= html_pipeline_result[:title]
51
+ attrs['subtitle'] ||= html_pipeline_result[:subtitle]
52
+ attrs['description'] ||= html_pipeline_result[:subtitle]
53
+ end
54
+ end
55
+
56
+ def pages
57
+ @pages ||= begin
58
+ page = Page.new(path_helper, attributes)
59
+ page.path = path
60
+ [page]
61
+ end
62
+ end
63
+
64
+ def html_pipeline_result
65
+ @html_pipeline_result ||= HtmlPipelines.markdown.call(markdown)
66
+ end
67
+
68
+ def xml
69
+ html_pipeline_result[:output]
70
+ end
71
+
72
+ def output
73
+ xml.to_s
74
+ end
75
+
76
+ def markdown_pipeline
77
+ @markdown_pipeline ||= RendererPipeline.for(
78
+ root_path: root_path,
79
+ source_file: source_file,
80
+ content_text: output,
81
+ layout_name: '_layout.md.html.erb',
82
+ )
83
+ end
84
+
85
+ def render_pipleine
86
+ @render_pipleine ||= RendererPipeline.for(root_path: root_path, source_file: source_file, content_text: markdown_pipeline)
87
+ end
88
+
89
+ def _render context=nil
90
+ render_pipleine.render(context)
91
+ end
92
+
93
+ def file_actions registry
94
+ return [] unless page = pages.first
95
+
96
+ FileAction.new path: output_path do
97
+ context = RenderContext.new(registry, current_page: page)
98
+ _render(context)
99
+ end
100
+ end
101
+
102
+ end
@@ -0,0 +1,15 @@
1
+ class Sitefs::Handlers::Noop < Sitefs::Handler
2
+ def output_path
3
+ @source_file.sub(root_path, '')
4
+ end
5
+
6
+ def should_generate? config
7
+ (File.basename(source_file)[0] != '_') && !config.manifest.generated?(output_path)
8
+ end
9
+
10
+ def file_actions registry
11
+ FileAction.new(path: output_path, possible_actions: %i{upload}) do
12
+ File.read(@source_file)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,33 @@
1
+ class Sitefs::Handlers::RubyGen < Sitefs::Handler
2
+ def dsl
3
+ @dsl ||= DslContext.new path_helper
4
+ end
5
+
6
+ def pages
7
+ @pages ||= begin
8
+ dsl._eval
9
+ end
10
+ end
11
+
12
+ def renderer_for page
13
+ content_path = page._rendering_template
14
+ content_text = File.read(content_path)
15
+
16
+ RendererPipeline.for(root_path: root_path, source_file: source_file, content_text: content_text)
17
+ end
18
+
19
+ def file_actions registry
20
+ pages.map do |page|
21
+ output_path = page.expanded_path + '.html'
22
+ output_path.sub!(root_path, '')
23
+
24
+ FileAction.new path: output_path do
25
+ renderer = renderer_for page
26
+ context = RenderContext.new(registry, current_page: page)
27
+
28
+ renderer.render context
29
+ end
30
+ end
31
+ end
32
+
33
+ end
@@ -0,0 +1,63 @@
1
+ require 'sass'
2
+
3
+ class Sitefs::Handlers::Scss < Sitefs::Handler
4
+
5
+ def content
6
+ engine.render
7
+ end
8
+
9
+ def engine_options
10
+ {
11
+ cache: true,
12
+ syntax: :scss,
13
+ style: :compressed,
14
+ filename: source_file,
15
+ sourcemap: :inline,
16
+ load_paths: [root_path],
17
+ }
18
+ end
19
+
20
+ def engine
21
+ @engine ||= Sass::Engine.new(File.read(source_file), engine_options)
22
+ end
23
+
24
+ def render_with_sourcemap
25
+ @render_with_sourcemap ||= engine.render_with_sourcemap(sourcemap_output_pathname)
26
+ end
27
+
28
+ # def delay_generation
29
+ # true
30
+ # end
31
+
32
+ def should_generate? config
33
+ File.basename(source_file)[0] != '_'
34
+ end
35
+
36
+ def output_path
37
+ source_file.sub(/\.scss$/, '.css').sub(root_path, '')
38
+ end
39
+
40
+ def output_pathname
41
+ File.join('', output_path)
42
+ end
43
+
44
+ def sourcemap_output_pathname
45
+ output_pathname + '.map'
46
+ end
47
+
48
+ def sourcemap_output_path
49
+ output_path + '.map'
50
+ end
51
+
52
+ def file_actions registry
53
+ sourcemap_options = {
54
+ css_path: output_path,
55
+ sourcemap_path: sourcemap_output_path,
56
+ }
57
+
58
+ [
59
+ FileAction.new(path: output_path) { render_with_sourcemap[0] },
60
+ FileAction.new(path: sourcemap_output_path) { render_with_sourcemap[1].to_json(sourcemap_options) },
61
+ ]
62
+ end
63
+ end
@@ -0,0 +1,49 @@
1
+ class Sitefs::Handlers::SingleErb < Sitefs::Handler
2
+ def basename
3
+ File.basename(source_file)
4
+ end
5
+
6
+ def extension
7
+ if match = basename.match(/\.page(\..*)?\.erb$/)
8
+ match[1]
9
+ else
10
+ '.html'
11
+ end
12
+ end
13
+
14
+ def output_path
15
+ source_file.sub(/\.page(\..*)?\.erb$/,'\1').sub(root_path, '')
16
+ end
17
+
18
+ def content_text
19
+ @content_text ||= File.read(source_file)
20
+ end
21
+
22
+ def renderer
23
+ RendererPipeline.for(root_path: root_path, source_file: source_file, content_text: content_text)
24
+ end
25
+
26
+ def pages
27
+ @pages ||= begin
28
+ page = Page.new path_helper, attributes
29
+ page.published_at = File.birthtime(source_file)
30
+ [page]
31
+ end
32
+ end
33
+
34
+ def page
35
+ pages.first
36
+ end
37
+
38
+ def html_pipeline_result
39
+ @html_pipeline_result ||= HtmlPipelines.content.call(content_text)
40
+ end
41
+
42
+ def file_actions registry
43
+ FileAction.new path: output_path do
44
+ context = RenderContext.new(registry, current_page: page)
45
+
46
+ renderer.render context
47
+ end
48
+ end
49
+ end