soundwave 0.0.1

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.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in soundwave.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 David Demaree
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # Soundwave
2
+
3
+ Soundwave is a simple static file generator. Its primary use is for building static web sites, and in that sense it's similar to Tom Preston-Werner's Jekyll. But Soundwave can also bootstrap web application projects, or basically do anything that involves processing data or copying a directory structure lots and lots of times.
4
+
5
+ ## Installation
6
+
7
+ TODO: This isn't really a Rails plugin, so you wouldn't necessarily put it in a Gemfile.
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'soundwave'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install soundwave
20
+
21
+ ## Usage
22
+
23
+ TODO: Write usage instructions here
24
+
25
+ ## Contributing
26
+
27
+ 1. Fork it
28
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
29
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
30
+ 4. Push to the branch (`git push origin my-new-feature`)
31
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task :default => :spec
8
+
9
+ require './vendor/source_annotation_extractor'
10
+
11
+ desc "Enumerate all annotations (use notes:optimize, :fixme, :todo for focus)"
12
+ task :notes do
13
+ SourceAnnotationExtractor.enumerate "OPTIMIZE|FIXME|TODO", :tag => true
14
+ end
15
+
16
+ namespace :notes do
17
+ ["OPTIMIZE", "FIXME", "TODO"].each do |annotation|
18
+ # desc "Enumerate all #{annotation} annotations"
19
+ task annotation.downcase.intern do
20
+ SourceAnnotationExtractor.enumerate annotation
21
+ end
22
+ end
23
+
24
+ desc "Enumerate a custom annotation, specify with ANNOTATION=CUSTOM"
25
+ task :custom do
26
+ SourceAnnotationExtractor.enumerate ENV['ANNOTATION']
27
+ end
28
+ end
data/bin/soundwave ADDED
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'soundwave'
5
+
6
+ help = <<HELP
7
+ Soundwave is a static site generator.
8
+
9
+ soundwave #=> Builds a Soundwave site in the current directory
10
+ soundwave init #=> Set up a new Soundwave site in the current directory
11
+
12
+ HELP
13
+
14
+ if ARGV.first == "init"
15
+ ARGV.shift
16
+ puts "soundwave init not yet implemented"
17
+ exit(0)
18
+ else
19
+ # puts "Soundwave #{Soundwave::VERSION}"
20
+ end
21
+
22
+ options = {}
23
+
24
+ opts = OptionParser.new do |opts|
25
+ opts.summary_width = 24
26
+ opts.banner = help
27
+
28
+ # opts.on("-r", "--require LIBRARY", "Require the LIBRARY before doing anything") do |lib|
29
+ # require lib
30
+ # end
31
+
32
+ # opts.on("-I DIRECTORY", "--include=DIRECTORY", "Adds the directory to the Sprockets load path") do |directory|
33
+ # environment.append_path directory
34
+ # end
35
+
36
+ opts.on("-e", "--exclude [file]", "Skip files/directories named FILE when generating the site") do |file|
37
+ Soundwave.config.exclude << file
38
+ end
39
+
40
+ opts.on("--version", "Display current version") do
41
+ puts "Soundwave " + Soundwave::VERSION
42
+ exit 0
43
+ end
44
+
45
+ opts.on("--watch", "Automatically regenerate when files are changed") do
46
+ options["watch"] = true
47
+ end
48
+ end
49
+
50
+ opts.parse!
51
+
52
+ # TODO: Move into Soundwave proper and rename
53
+ if File.exists?("./_soundwave.rb")
54
+ require "./_soundwave.rb"
55
+ end
56
+
57
+ environment = Soundwave::Environment.new
58
+
59
+ if options["watch"]
60
+ require 'fssm'
61
+ monitor = FSSM::Monitor.new #(:directories => true)
62
+
63
+ entries = Dir.chdir(environment.root_dir.expand_path) { Dir.glob("*") }
64
+ entries = environment.filter_entries(entries)
65
+ entries.map! { |e| environment.root_dir.join(e) }
66
+
67
+ directories = entries.select { |e| e.directory? }
68
+ indiv_files = entries - directories
69
+
70
+ # Also monitor the data and includes directories
71
+ directories.push environment.data_dir
72
+ directories.push environment.root_dir.join("_includes")
73
+
74
+ directories.each do |dir_path|
75
+ monitor.path(dir_path) do
76
+ update { |base_dir, relative_path|
77
+ environment.generate_site
78
+ }
79
+ end
80
+ end
81
+
82
+ indiv_files.each do |file_path|
83
+ monitor.file(file_path) do
84
+ update { |full_path, relative_dir|
85
+ environment.generate_site
86
+ }
87
+ end
88
+ end
89
+
90
+ environment.generate_site
91
+ puts "Now watching for changes in #{environment.root_dir}..."
92
+ monitor.run
93
+ else
94
+ environment.generate_site
95
+ exit(0)
96
+ end
@@ -0,0 +1,34 @@
1
+ module Soundwave
2
+ class Document
3
+
4
+ attr_reader :mtime, :pathname, :logical_path
5
+
6
+ def initialize(env, logical_path, absolute_path=nil)
7
+ @env = env
8
+ @logical_path = logical_path
9
+ @pathname = Pathname(absolute_path || env.root_dir.join(logical_path))
10
+ refresh
11
+ end
12
+
13
+ def output_path
14
+ @env.output_dir.join(@logical_path)
15
+ end
16
+
17
+ def changed?
18
+ @mtime != @pathname.stat.mtime
19
+ true
20
+ end
21
+
22
+ def refresh
23
+ @mtime = @pathname.stat.mtime
24
+ end
25
+
26
+ def file_attributes
27
+ @env.attributes_for(self.pathname)
28
+ end
29
+
30
+ def write
31
+ # Stub: override this in a subclass
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,102 @@
1
+ require 'active_support/callbacks'
2
+ require 'tilt'
3
+
4
+ module Soundwave
5
+ class Environment
6
+ include ActiveSupport::Callbacks
7
+ define_callbacks :read
8
+ define_callbacks :generate
9
+
10
+ attr_accessor :root_dir, :output_dir, :exclude, :data_dir
11
+
12
+ def initialize(root="./")
13
+ @root_dir = Pathname(root).expand_path
14
+ @exclude = Soundwave.config.exclude
15
+ @output_dir ||= @root_dir.join("_site")
16
+ @data_dir ||= @root_dir.join("_data")
17
+
18
+ register_engine ".mustache", Soundwave::MustacheTemplate
19
+ register_engine ".scss", Tilt::ScssTemplate
20
+ register_engine ".erb", Tilt::ERBTemplate
21
+ end
22
+
23
+ def site_data
24
+ # TODO: Implement something for getting global/site-level data from _data/_site.(yml|json)
25
+ {}
26
+ end
27
+
28
+ def pages
29
+ @pages ||= {}
30
+ end
31
+
32
+ def engines(ext)
33
+ @engines[ext]
34
+ end
35
+
36
+ def register_engine(ext, engine)
37
+ @engines ||= {}
38
+ @engines[ext] = engine
39
+ end
40
+
41
+ def attributes_for(pathname)
42
+ FileAttributes.new(self, pathname)
43
+ end
44
+
45
+ def build_page(logical_path, pathname)
46
+ attrs = attributes_for(pathname)
47
+
48
+ if attrs.engines.any?
49
+ RenderedPage.new(self, logical_path, pathname)
50
+ else
51
+ StaticFile.new(self, logical_path, pathname)
52
+ end
53
+ end
54
+
55
+ def read_directories(dir='')
56
+ base = File.join(self.root_dir, dir)
57
+ entries = Dir.chdir(base) { filter_entries(Dir['*']) }
58
+ entries.each do |entry|
59
+ absolute_path = File.join(base, entry)
60
+ relative_path = File.join(dir, entry)
61
+ logical_path = absolute_path.sub(self.root_dir.to_s, "")
62
+
63
+ if File.directory?(absolute_path)
64
+ read_directories(relative_path)
65
+ else
66
+ pathname = Pathname(absolute_path).expand_path
67
+ attrs = attributes_for(pathname)
68
+ logical_path = attrs.logical_path
69
+ pages[logical_path] = build_page(logical_path, pathname)
70
+ end
71
+ end
72
+ end
73
+
74
+ def generate
75
+ run_callbacks(:read) do
76
+ read_directories
77
+ end
78
+ run_callbacks(:generate) do
79
+ @pages.each do |logical_path, page|
80
+ puts "#{logical_path} => #{page.output_path}"
81
+ page.write
82
+ end
83
+ end
84
+ end
85
+
86
+ # Deprecated: Alias to #generate for the two extant projects that use this code
87
+ alias_method :generate_site, :generate
88
+
89
+ # Exclude .dotfiles, _underscores, #hashes, ~tildes, paths in @exclude
90
+ # and symlinks, EXCEPT for .htaccess
91
+ def filter_entries(entries)
92
+ entries = entries.reject do |e|
93
+ unless ['.htaccess'].include?(e)
94
+ ['.', '_', '#'].include?(e[0..0]) ||
95
+ e[-1..-1] == '~' ||
96
+ self.exclude.include?(e) ||
97
+ File.symlink?(e)
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,56 @@
1
+ require 'pathname'
2
+
3
+ module Soundwave
4
+ class FileAttributes
5
+ attr_reader :environment, :pathname
6
+
7
+ def initialize(environment, pathname)
8
+ @environment = environment
9
+ @pathname = Pathname(pathname)
10
+ end
11
+
12
+ def logical_path
13
+ if root_path = pathname.expand_path.to_s[environment.root_dir.to_s]
14
+ path = pathname.to_s.sub("#{root_path}/", '')
15
+ path = pathname.expand_path.relative_path_from(Pathname.new(root_path)).to_s
16
+ path = engine_extensions.inject(path) { |p, ext| p.sub(ext, '') }
17
+ path = "#{path}#{engine_format_extension}" unless format_extension
18
+ path
19
+ else
20
+ raise "File outside paths"
21
+ end
22
+ end
23
+
24
+ def extensions
25
+ @extensions ||= @pathname.basename.to_s.scan(/\.[^.]+/)
26
+ end
27
+
28
+ def format_extension
29
+ extensions.reverse.detect { |ext|
30
+ # TODO: Environment may need a mime types registry
31
+ !@environment.engines(ext)
32
+ }
33
+ end
34
+
35
+ def engine_extensions
36
+ exts = extensions
37
+
38
+ if offset = extensions.index(format_extension)
39
+ exts = extensions[offset+1..-1]
40
+ end
41
+
42
+ exts.select { |ext| @environment.engines(ext) }
43
+ end
44
+
45
+ def engines
46
+ engine_extensions.map { |ext| @environment.engines(ext) }
47
+ end
48
+
49
+ private
50
+
51
+ def engine_format_extension
52
+ # TODO: Engines should provide a default extension, this should be engines.first.default_extension
53
+ ".html"
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,32 @@
1
+ require 'mustache'
2
+ require 'pathname'
3
+
4
+ module Soundwave
5
+ # Custom wrapper for Mustache, to enforce Soundwave conventions w/r/t
6
+ # template and partial naming and provide a Tilt-like interface.
7
+ class MustacheTemplate < ::Mustache
8
+
9
+ # Public: Initializes a new MustacheTemplate
10
+ #
11
+ # pathname - Pathname for the template file
12
+ #
13
+ # Returns a MustacheTemplate object.
14
+ def initialize(pathname)
15
+ @pathname = Pathname(pathname)
16
+ end
17
+
18
+ # Public: Reads the template file from disk and returns its
19
+ # contents as a String.
20
+ def template
21
+ @pathname.read
22
+ end
23
+
24
+ # Public: Reads the partial template with the given name and
25
+ # returns its contents.
26
+ def partial(name)
27
+ # TODO: Make this a teeny bit more robust?
28
+ name = '_includes/' + name.to_s
29
+ super
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,69 @@
1
+ require 'mustache'
2
+
3
+ module Soundwave
4
+ class RenderedPage < Document
5
+
6
+ attr_accessor :data
7
+ attr_reader :output
8
+
9
+ def initialize(env, logical_path, pathname)
10
+ super
11
+ @data = {}
12
+ read_data # is this necessary?
13
+ end
14
+
15
+ def output_path
16
+ @env.output_dir.join(logical_path)
17
+ end
18
+
19
+ def mustache
20
+ MustacheTemplate.new(@pathname)
21
+ end
22
+
23
+ def render
24
+ read_data
25
+ result = pathname.read
26
+ processors.each do |processor|
27
+ template = processor.new(pathname.to_s) { result }
28
+ result = template.render(@data)
29
+ end
30
+
31
+ @output ||= result
32
+ result
33
+ end
34
+ alias_method :to_s, :render
35
+
36
+ def write
37
+ if changed?
38
+ FileUtils.mkdir_p(output_path.dirname)
39
+ File.open(output_path, "w") { |f| f.write(self.render()) }
40
+ end
41
+ end
42
+
43
+ protected
44
+
45
+ def processors
46
+ file_attributes.engines
47
+ end
48
+
49
+ def read_data
50
+ # Get site data
51
+ @data = @env.site_data || {}
52
+
53
+ basepath = file_attributes.logical_path.sub(/\..+/, '')
54
+ data_file = @env.data_dir.join(basepath + ".yml")
55
+
56
+ if File.exists?(data_file)
57
+ page_data = YAML.load_file(data_file)
58
+ else
59
+ page_data = {}
60
+ end
61
+
62
+ @data.merge!(page_data)
63
+
64
+ # TODO: YAML frontmatter?
65
+
66
+ @data
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,10 @@
1
+ module Soundwave
2
+ class StaticFile < Soundwave::Document
3
+ def write
4
+ if changed?
5
+ FileUtils.mkdir_p(output_path.dirname.to_s)
6
+ FileUtils.cp(@pathname, output_path)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,22 @@
1
+ module Soundwave
2
+ module Utils
3
+
4
+ # Prepends a leading "." to an extension if its missing.
5
+ #
6
+ # normalize_extension("js")
7
+ # # => ".js"
8
+ #
9
+ # normalize_extension(".css")
10
+ # # => ".css"
11
+ #
12
+ def self.normalize_extension(extension)
13
+ extension = extension.to_s
14
+ if extension[/^\./]
15
+ extension
16
+ else
17
+ ".#{extension}"
18
+ end
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,3 @@
1
+ module Soundwave
2
+ VERSION = "0.0.1"
3
+ end
data/lib/soundwave.rb ADDED
@@ -0,0 +1,17 @@
1
+ require "soundwave/version"
2
+ require "active_support/configurable"
3
+ require "digest/md5"
4
+
5
+ module Soundwave
6
+ include ActiveSupport::Configurable
7
+
8
+ self.configure do |config|
9
+ config.exclude = ["bin", "Gemfile", "Gemfile.lock"]
10
+ config.static_extensions = %w(.jpg .jpeg .png .gif)
11
+ end
12
+
13
+ config_accessor :static_extensions
14
+ end
15
+
16
+ # Load all submodules
17
+ Dir[File.expand_path("../soundwave/*.rb", __FILE__)].each { |f| require f }
data/soundwave.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/soundwave/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["David Demaree"]
6
+ gem.email = ["ddemaree@gmail.com"]
7
+ gem.description = %q{A simple static website generator}
8
+ gem.summary = %q{A simple static website generator based on Tilt and Mustache}
9
+ gem.homepage = "http://github.com/ddemaree/soundwave"
10
+
11
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
12
+ gem.files = `git ls-files`.split("\n")
13
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
14
+ gem.name = "soundwave"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Soundwave::VERSION
17
+
18
+ gem.executables = ["soundwave"]
19
+
20
+ gem.add_runtime_dependency "activesupport", ">= 3.1.0"
21
+ gem.add_runtime_dependency "mustache"
22
+ gem.add_runtime_dependency "tilt"
23
+ gem.add_runtime_dependency "fssm"
24
+
25
+ gem.add_development_dependency "bundler"
26
+ gem.add_development_dependency "rspec", "~> 2.8.0"
27
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ describe Soundwave::Document do
4
+
5
+ let(:root_dir) { File.expand_path("../fixtures/site", __FILE__) }
6
+ let(:environment) { Soundwave::Environment.new(root_dir) }
7
+ let(:pathname) { Pathname(File.join(root_dir, "index.mustache")) }
8
+ let(:document) { Soundwave::Document.new(environment, "index.html", pathname) }
9
+
10
+ describe "change tracking" do
11
+ it "stores the file's mtime at initialization" do
12
+ document.mtime.should == pathname.stat.mtime
13
+ end
14
+ describe "changed?" do
15
+ it "compares the current mtime with the cached value" do
16
+ document
17
+ sleep 1
18
+ FileUtils.touch pathname
19
+ document.should be_changed
20
+ end
21
+ end
22
+ describe "refresh" do
23
+ it "updates the stored mtime" do
24
+ document
25
+ sleep 1
26
+ FileUtils.touch pathname
27
+ document.refresh
28
+ document.should_not be_changed
29
+ end
30
+ end
31
+ end
32
+
33
+ describe "output path" do
34
+ it "is joined on the env's output dir" do
35
+ document = Soundwave::Document.new(environment, "index.html", pathname)
36
+ document.output_path.to_s.should == File.join(root_dir, "_site", "index.html")
37
+ end
38
+ end
39
+
40
+ end
@@ -0,0 +1,54 @@
1
+ require "spec_helper"
2
+
3
+ describe Soundwave::Environment do
4
+ let(:environment) { Soundwave::Environment.new }
5
+
6
+ before do
7
+ FileUtils.cd File.expand_path("../fixtures/site", __FILE__)
8
+ end
9
+
10
+ describe "initialization" do
11
+ it "creates an environment for the current directory" do
12
+ env = Soundwave::Environment.new
13
+ env.root_dir.expand_path.to_s.should == Dir.pwd
14
+ end
15
+ it "allows the root directory to be set" do
16
+ env = Soundwave::Environment.new("/tmp/soundwave")
17
+ env.root_dir.expand_path.to_s.should == "/tmp/soundwave"
18
+ end
19
+ end
20
+
21
+ # Indexes the content of the site and stores it as @paths
22
+ describe "read_directories" do
23
+ it "indexes pages and static files" do
24
+ environment.read_directories
25
+ pages = environment.instance_variable_get("@pages")
26
+ pages.keys.should have(3).items
27
+ pages.keys.sort.should == ["about.html", "css/site.css", "index.html"]
28
+ end
29
+ end
30
+
31
+ describe "filter_entries" do
32
+ it "filters out _files" do
33
+ environment.filter_entries(["_file", "a"]).should == ["a"]
34
+ end
35
+ it "filters out .files" do
36
+ environment.filter_entries([".rspec", "a"]).should == ["a"]
37
+ end
38
+ it "filters out #files" do
39
+ environment.filter_entries(["#wtf", "a"]).should == ["a"]
40
+ end
41
+ it "filters out files~" do
42
+ environment.filter_entries(["vimblows~", "~tildesrule", "a"]).should == ["~tildesrule","a"]
43
+ end
44
+ it "filters out files listed in #exclude" do
45
+ old_exclude = environment.exclude
46
+ environment.stub!(:exclude).and_return(old_exclude + ["Guardfile"])
47
+ environment.filter_entries(["Guardfile", "a"]).should == ["a"]
48
+ end
49
+ it "does not filter out .htaccess" do
50
+ environment.filter_entries([".htaccess", "a"]).should == [".htaccess", "a"]
51
+ end
52
+ end
53
+
54
+ end
@@ -0,0 +1,41 @@
1
+ require "spec_helper"
2
+
3
+ describe Soundwave::FileAttributes do
4
+ let(:root_dir) { File.expand_path("../fixtures/site", __FILE__) }
5
+ let(:environment) { Soundwave::Environment.new(root_dir) }
6
+
7
+ def attributes_for(pathname)
8
+ environment.attributes_for(pathname)
9
+ end
10
+
11
+ before do
12
+ FileUtils.cd(root_dir)
13
+ end
14
+
15
+ describe "logical paths" do
16
+ it "are relative to the env's root_dir" do
17
+ attributes_for("about/index.html").logical_path.should == "about/index.html"
18
+ end
19
+ it "strip engine extensions" do
20
+ attributes_for("about.html.mustache").logical_path.should == "about.html"
21
+ end
22
+ it "append a default format extension if necessary" do
23
+ attributes_for("about.mustache").logical_path.should == "about.html"
24
+ end
25
+ end
26
+
27
+ describe "extensions" do
28
+ it "gets an array of all extensions" do
29
+ attributes_for("index.html.mustache").extensions.should == [".html", ".mustache"]
30
+ end
31
+ it "gets the format extension" do
32
+ attributes_for("index.html.mustache").format_extension.should == ".html"
33
+ end
34
+ it "gets the engine extension(s)" do
35
+ attributes_for("index.html.mustache").engine_extensions.should == [".mustache"]
36
+ end
37
+ it "allows pathnames with just an engine extension" do
38
+ attributes_for("about.scss").engine_extensions.should == [".scss"]
39
+ end
40
+ end
41
+ end
@@ -0,0 +1 @@
1
+ page_title: "About this site"
@@ -0,0 +1 @@
1
+ page_title: "Hello World!"
@@ -0,0 +1 @@
1
+ <%= "Title: {{ page_title }}" %>
File without changes
@@ -0,0 +1,6 @@
1
+ <html>
2
+ <head><title>{{page_title}}</title></head>
3
+ <body>
4
+ <h1>{{page_title}}</h1>
5
+ </body>
6
+ </html>
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+
3
+ describe Soundwave::RenderedPage do
4
+ let(:root_dir) { Pathname(File.expand_path("../fixtures/site", __FILE__)) }
5
+ let(:environment) { Soundwave::Environment.new(root_dir) }
6
+
7
+ let(:pathname) { root_dir.join("index.mustache") }
8
+ let(:data_pathname) { root_dir.join("_data", "index.yml") }
9
+ let(:page) { Soundwave::RenderedPage.new(environment, "index.html", pathname) }
10
+
11
+ before do
12
+ FileUtils.cd(root_dir)
13
+ end
14
+
15
+ describe "with data" do
16
+ it "reads YAML data file on initialization" do
17
+ page.data.should == {"page_title" => "Hello World!"}
18
+ end
19
+ it "sets mtime to data file's mtime if it is later" do
20
+ pending "Need to add dependency tracking to RenderedPage"
21
+ FileUtils.touch(pathname)
22
+ sleep 1
23
+ FileUtils.touch(data_pathname)
24
+
25
+ page.mtime.should == data_pathname.stat.mtime
26
+ end
27
+ end
28
+
29
+ describe "rendering" do
30
+ it "renders content with data" do
31
+ output = page.render
32
+ output.should == <<-HTML
33
+ <html>
34
+ <head><title>Hello World!</title></head>
35
+ <body>
36
+ <h1>Hello World!</h1>
37
+ </body>
38
+ </html>
39
+ HTML
40
+ end
41
+ it "can chain multiple engines" do
42
+ page = Soundwave::RenderedPage.new(environment, "index.html", root_dir.join("about.mustache.erb"))
43
+ page.data = {"page_title" => "This is page title"}
44
+ output = page.render
45
+ output.should == "Title: About this site"
46
+ end
47
+ end
48
+
49
+ end
@@ -0,0 +1,4 @@
1
+ require 'spec_helper'
2
+
3
+ describe Soundwave do
4
+ end
@@ -0,0 +1,16 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper.rb"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+
8
+ # require 'bundler'
9
+ # Bundler.setup
10
+ require 'soundwave'
11
+
12
+ RSpec.configure do |config|
13
+ config.treat_symbols_as_metadata_keys_with_true_values = true
14
+ config.run_all_when_everything_filtered = true
15
+ config.filter_run :focus
16
+ end
@@ -0,0 +1,102 @@
1
+ # Implements the logic behind the rake tasks for annotations like
2
+ #
3
+ # rake notes
4
+ # rake notes:optimize
5
+ #
6
+ # and friends. See <tt>rake -T notes</tt> and <tt>railties/lib/tasks/annotations.rake</tt>.
7
+ #
8
+ # Annotation objects are triplets <tt>:line</tt>, <tt>:tag</tt>, <tt>:text</tt> that
9
+ # represent the line where the annotation lives, its tag, and its text. Note
10
+ # the filename is not stored.
11
+ #
12
+ # Annotations are looked for in comments and modulus whitespace they have to
13
+ # start with the tag optionally followed by a colon. Everything up to the end
14
+ # of the line (or closing ERB comment tag) is considered to be their text.
15
+ class SourceAnnotationExtractor
16
+ class Annotation < Struct.new(:line, :tag, :text)
17
+
18
+ # Returns a representation of the annotation that looks like this:
19
+ #
20
+ # [126] [TODO] This algorithm is simple and clearly correct, make it faster.
21
+ #
22
+ # If +options+ has a flag <tt>:tag</tt> the tag is shown as in the example above.
23
+ # Otherwise the string contains just line and text.
24
+ def to_s(options={})
25
+ s = "[%3d] " % line
26
+ s << "[#{tag}] " if options[:tag]
27
+ s << text
28
+ end
29
+ end
30
+
31
+ # Prints all annotations with tag +tag+ under the root directories +app+, +lib+,
32
+ # and +test+ (recursively). Only filenames with extension +.builder+, +.rb+,
33
+ # +.rxml+, +.rhtml+, or +.erb+ are taken into account. The +options+
34
+ # hash is passed to each annotation's +to_s+.
35
+ #
36
+ # This class method is the single entry point for the rake tasks.
37
+ def self.enumerate(tag, options={})
38
+ extractor = new(tag)
39
+ extractor.display(extractor.find, options)
40
+ end
41
+
42
+ attr_reader :tag
43
+
44
+ def initialize(tag)
45
+ @tag = tag
46
+ end
47
+
48
+ # Returns a hash that maps filenames under +dirs+ (recursively) to arrays
49
+ # with their annotations. Only files with annotations are included, and only
50
+ # those with extension +.builder+, +.rb+, +.rxml+, +.rhtml+, and +.erb+
51
+ # are taken into account.
52
+ def find(dirs=%w(app lib test))
53
+ dirs.inject({}) { |h, dir| h.update(find_in(dir)) }
54
+ end
55
+
56
+ # Returns a hash that maps filenames under +dir+ (recursively) to arrays
57
+ # with their annotations. Only files with annotations are included, and only
58
+ # those with extension +.builder+, +.rb+, +.rxml+, +.rhtml+, and +.erb+
59
+ # are taken into account.
60
+ def find_in(dir)
61
+ results = {}
62
+
63
+ Dir.glob("#{dir}/*") do |item|
64
+ next if File.basename(item)[0] == ?.
65
+
66
+ if File.directory?(item)
67
+ results.update(find_in(item))
68
+ elsif item =~ /\.(builder|(r(?:b|xml|js)))$/
69
+ results.update(extract_annotations_from(item, /#\s*(#{tag}):?\s*(.*)$/))
70
+ elsif item =~ /\.(rhtml|erb)$/
71
+ results.update(extract_annotations_from(item, /<%\s*#\s*(#{tag}):?\s*(.*?)\s*%>/))
72
+ end
73
+ end
74
+
75
+ results
76
+ end
77
+
78
+ # If +file+ is the filename of a file that contains annotations this method returns
79
+ # a hash with a single entry that maps +file+ to an array of its annotations.
80
+ # Otherwise it returns an empty hash.
81
+ def extract_annotations_from(file, pattern)
82
+ lineno = 0
83
+ result = File.readlines(file).inject([]) do |list, line|
84
+ lineno += 1
85
+ next list unless line =~ pattern
86
+ list << Annotation.new(lineno, $1, $2)
87
+ end
88
+ result.empty? ? {} : { file => result }
89
+ end
90
+
91
+ # Prints the mapping from filenames to annotations in +results+ ordered by filename.
92
+ # The +options+ hash is passed to each annotation's +to_s+.
93
+ def display(results, options={})
94
+ results.keys.sort.each do |file|
95
+ puts "#{file}:"
96
+ results[file].each do |note|
97
+ puts " * #{note.to_s(options)}"
98
+ end
99
+ puts
100
+ end
101
+ end
102
+ end
metadata ADDED
@@ -0,0 +1,152 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: soundwave
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - David Demaree
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-02-17 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activesupport
16
+ requirement: &70334812682320 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 3.1.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70334812682320
25
+ - !ruby/object:Gem::Dependency
26
+ name: mustache
27
+ requirement: &70334812681460 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70334812681460
36
+ - !ruby/object:Gem::Dependency
37
+ name: tilt
38
+ requirement: &70334812680460 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *70334812680460
47
+ - !ruby/object:Gem::Dependency
48
+ name: fssm
49
+ requirement: &70334812679880 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: *70334812679880
58
+ - !ruby/object:Gem::Dependency
59
+ name: bundler
60
+ requirement: &70334812679220 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *70334812679220
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: &70334812678060 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ~>
75
+ - !ruby/object:Gem::Version
76
+ version: 2.8.0
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *70334812678060
80
+ description: A simple static website generator
81
+ email:
82
+ - ddemaree@gmail.com
83
+ executables:
84
+ - soundwave
85
+ extensions: []
86
+ extra_rdoc_files: []
87
+ files:
88
+ - .gitignore
89
+ - .rspec
90
+ - Gemfile
91
+ - LICENSE
92
+ - README.md
93
+ - Rakefile
94
+ - bin/soundwave
95
+ - lib/soundwave.rb
96
+ - lib/soundwave/document.rb
97
+ - lib/soundwave/environment.rb
98
+ - lib/soundwave/file_attributes.rb
99
+ - lib/soundwave/mustache_template.rb
100
+ - lib/soundwave/rendered_page.rb
101
+ - lib/soundwave/static_file.rb
102
+ - lib/soundwave/utils.rb
103
+ - lib/soundwave/version.rb
104
+ - soundwave.gemspec
105
+ - spec/document_spec.rb
106
+ - spec/environment_spec.rb
107
+ - spec/file_attributes_spec.rb
108
+ - spec/fixtures/site/_data/about.yml
109
+ - spec/fixtures/site/_data/index.yml
110
+ - spec/fixtures/site/about.mustache.erb
111
+ - spec/fixtures/site/css/site.css.scss
112
+ - spec/fixtures/site/index.mustache
113
+ - spec/rendered_page_spec.rb
114
+ - spec/soundwave_spec.rb
115
+ - spec/spec_helper.rb
116
+ - vendor/source_annotation_extractor.rb
117
+ homepage: http://github.com/ddemaree/soundwave
118
+ licenses: []
119
+ post_install_message:
120
+ rdoc_options: []
121
+ require_paths:
122
+ - lib
123
+ required_ruby_version: !ruby/object:Gem::Requirement
124
+ none: false
125
+ requirements:
126
+ - - ! '>='
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ required_rubygems_version: !ruby/object:Gem::Requirement
130
+ none: false
131
+ requirements:
132
+ - - ! '>='
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ requirements: []
136
+ rubyforge_project:
137
+ rubygems_version: 1.8.11
138
+ signing_key:
139
+ specification_version: 3
140
+ summary: A simple static website generator based on Tilt and Mustache
141
+ test_files:
142
+ - spec/document_spec.rb
143
+ - spec/environment_spec.rb
144
+ - spec/file_attributes_spec.rb
145
+ - spec/fixtures/site/_data/about.yml
146
+ - spec/fixtures/site/_data/index.yml
147
+ - spec/fixtures/site/about.mustache.erb
148
+ - spec/fixtures/site/css/site.css.scss
149
+ - spec/fixtures/site/index.mustache
150
+ - spec/rendered_page_spec.rb
151
+ - spec/soundwave_spec.rb
152
+ - spec/spec_helper.rb