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,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