soundwave 0.0.1

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