montage 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/.document +5 -0
  2. data/.gitignore +31 -0
  3. data/History.md +40 -0
  4. data/LICENSE +19 -0
  5. data/README.md +89 -0
  6. data/Rakefile +41 -0
  7. data/VERSION +1 -0
  8. data/bin/montage +22 -0
  9. data/lib/montage.rb +33 -0
  10. data/lib/montage/commands.rb +32 -0
  11. data/lib/montage/commands/generate.rb +234 -0
  12. data/lib/montage/commands/init.rb +119 -0
  13. data/lib/montage/core_ext.rb +80 -0
  14. data/lib/montage/project.rb +185 -0
  15. data/lib/montage/sass_builder.rb +37 -0
  16. data/lib/montage/source.rb +75 -0
  17. data/lib/montage/sprite.rb +132 -0
  18. data/lib/montage/templates/montage.yml +26 -0
  19. data/lib/montage/templates/sass_mixins.erb +20 -0
  20. data/lib/montage/templates/sources/book.png +0 -0
  21. data/lib/montage/templates/sources/box-label.png +0 -0
  22. data/lib/montage/templates/sources/calculator.png +0 -0
  23. data/lib/montage/templates/sources/calendar-month.png +0 -0
  24. data/lib/montage/templates/sources/camera.png +0 -0
  25. data/lib/montage/templates/sources/eraser.png +0 -0
  26. data/lib/montage/version.rb +3 -0
  27. data/montage.gemspec +145 -0
  28. data/spec/fixtures/custom_dirs/montage.yml +8 -0
  29. data/spec/fixtures/default/montage.yml +7 -0
  30. data/spec/fixtures/default/public/images/sprites/src/one.png +0 -0
  31. data/spec/fixtures/default/public/images/sprites/src/three.png +0 -0
  32. data/spec/fixtures/default/public/images/sprites/src/two.png +0 -0
  33. data/spec/fixtures/directory_config/config/montage.yml +5 -0
  34. data/spec/fixtures/missing_source/montage.yml +3 -0
  35. data/spec/fixtures/missing_source_dir/montage.yml +5 -0
  36. data/spec/fixtures/root_config/montage.yml +5 -0
  37. data/spec/fixtures/root_config/public/images/sprites/src/source_one.png +0 -0
  38. data/spec/fixtures/root_config/public/images/sprites/src/source_three.jpg +0 -0
  39. data/spec/fixtures/root_config/public/images/sprites/src/source_two +0 -0
  40. data/spec/fixtures/sources/hundred.png +0 -0
  41. data/spec/fixtures/sources/mammoth.png +0 -0
  42. data/spec/fixtures/sources/other.png +0 -0
  43. data/spec/fixtures/sources/twenty.png +0 -0
  44. data/spec/fixtures/subdirs/montage.yml +5 -0
  45. data/spec/fixtures/subdirs/sub/sub/keep +0 -0
  46. data/spec/lib/command_runner.rb +140 -0
  47. data/spec/lib/fixtures.rb +7 -0
  48. data/spec/lib/have_public_method_defined.rb +19 -0
  49. data/spec/lib/project_helper.rb +135 -0
  50. data/spec/lib/shared_project_specs.rb +32 -0
  51. data/spec/lib/shared_sprite_specs.rb +30 -0
  52. data/spec/montage/commands/generate_spec.rb +308 -0
  53. data/spec/montage/commands/init_spec.rb +120 -0
  54. data/spec/montage/core_ext_spec.rb +33 -0
  55. data/spec/montage/project_spec.rb +181 -0
  56. data/spec/montage/sass_builder_spec.rb +269 -0
  57. data/spec/montage/source_spec.rb +53 -0
  58. data/spec/montage/spec/have_public_method_defined_spec.rb +31 -0
  59. data/spec/montage/sprite_spec.rb +170 -0
  60. data/spec/rcov.opts +8 -0
  61. data/spec/spec.opts +4 -0
  62. data/spec/spec_helper.rb +19 -0
  63. data/tasks/spec.rake +17 -0
  64. data/tasks/yard.rake +11 -0
  65. metadata +249 -0
@@ -0,0 +1,132 @@
1
+ module Montage
2
+ # Represents a collection of images which will be used to make a sprite.
3
+ #
4
+ class Sprite
5
+ attr_reader :name
6
+
7
+ # Creates a new Sprite instance.
8
+ #
9
+ # @param [String] name
10
+ # The name of the sprite. Will be used as the sprite filename (with an
11
+ # extension added).
12
+ # @param [Array<String>] sources
13
+ # The name of each source image.
14
+ # @param [Project] Project
15
+ # The project to which the sprite belongs.
16
+ #
17
+ def initialize(name, sources, project)
18
+ @name, @project = name, project
19
+
20
+ @sources =
21
+ sources.inject(ActiveSupport::OrderedHash.new) do |hash, source|
22
+ hash[source] = Source.new(@project.paths.sources, source, @name)
23
+ hash
24
+ end
25
+ end
26
+
27
+ # Returns an array of Source instances held by this Sprite.
28
+ #
29
+ # @return [Array<Source>]
30
+ #
31
+ def sources
32
+ @sources.map { |_, source| source }
33
+ end
34
+
35
+ # Returns an array of RMagick image instances; one for each source.
36
+ #
37
+ # @return [Array<Magick::Image>]
38
+ # The Image instances for the sources.
39
+ #
40
+ def images
41
+ sources.map { |source| source.image }
42
+ end
43
+
44
+ # Returns the path to the sprite on disk.
45
+ #
46
+ # @return [Pathname]
47
+ #
48
+ def path
49
+ @project.paths.sprites + "#{@name}.png"
50
+ end
51
+
52
+ # Returns the y-position of a given source.
53
+ #
54
+ # @return [Integer, Source]
55
+ # The vertical position of the source image.
56
+ #
57
+ def position_of(source)
58
+ source = source.name if source.is_a?(Source)
59
+
60
+ unless @sources.keys.include?(source)
61
+ raise MissingSource,
62
+ "Source image '#{source}' is not present in the '#{@name}' sprite"
63
+ end
64
+
65
+ unless @positions
66
+ # Rather than calculate each time we call position_of, cache the
67
+ # position of each image the first time it is called. Since we almost
68
+ # always want the position of each image at some point (when
69
+ # generating CSS), it works out faster to fetch each source height
70
+ # just once.
71
+ @positions = {}
72
+ @sources.inject(0) do |offset, (name, src)|
73
+ @positions[name] = offset
74
+ offset + src.image.rows + 20
75
+ end
76
+ end
77
+
78
+ @positions[source]
79
+ end
80
+
81
+ # Returns a digest which represents the sprite and it's contents. If any
82
+ # of the file _contents_ or source names change, so will the hash.
83
+ #
84
+ # @return [Digest::SHA256]
85
+ #
86
+ def digest
87
+ Digest::SHA256.hexdigest(sources.map { |source| source.digest }.join)
88
+ end
89
+
90
+ # Uses RMagick to creates a 8-bit (with alpha) PNG containing all of the
91
+ # source files.
92
+ #
93
+ # If a file exists at the output path, it will be overwritten.
94
+ #
95
+ # @raise [Montage::TargetNotWritable]
96
+ # Raised when the output directory can not be written to.
97
+ #
98
+ def write
99
+ unless @project.paths.sprites.writable?
100
+ raise TargetNotWritable, <<-MESSAGE
101
+ Montage can't save the sprite in `#{@project.paths.sprites.to_s}'
102
+ as it isn't writable.
103
+ MESSAGE
104
+ end
105
+
106
+ list = sources.inject(Magick::ImageList.new) do |list, source|
107
+ list << source.image
108
+ list << Magick::Image.new(1, @project.padding) do
109
+ self.background_color = '#FFF0'
110
+ end
111
+ end
112
+
113
+ # RMagick uses instance_eval, @set isn't available in the block below.
114
+ sources_length = sources.length
115
+
116
+ montage = list.montage do
117
+ self.gravity = Magick::NorthWestGravity
118
+ # Transparent background.
119
+ self.background_color = '#FFF0'
120
+ # Allow each image to take up as much space as it needs.
121
+ self.geometry = '+0+0'
122
+ # columns=1, rows=Sources plus padding.
123
+ self.tile = Magick::Geometry.new(1, sources_length * 2)
124
+ end
125
+
126
+ # Remove the blank space from the bottom of the image.
127
+ montage.crop!(0, 0, 0, (montage.first.rows) - @project.padding)
128
+ montage.write("PNG32:#{path}")
129
+ end
130
+
131
+ end # Set
132
+ end # Montage
@@ -0,0 +1,26 @@
1
+ ---
2
+
3
+ # If you need to specify non-standard locations for the source
4
+ # files, or the output locations for the sprites or SASS, change
5
+ # the relevant option below. Directories are relative to the
6
+ # project root unless they have a leading slash.
7
+
8
+ config.sources: <sources>
9
+ config.sprites: <sprites>
10
+ config.sass: "public/stylesheets/sass"
11
+ config.sprite_url: "/images/sprites"
12
+
13
+ # --------------------------------------------------------------------------
14
+
15
+ # The sample below will produce two sprites (one.png and two.png) with the
16
+ # specified source images. You can omit file extensions.
17
+
18
+ one:
19
+ - book
20
+ - box-label
21
+ - calculator
22
+
23
+ two:
24
+ - calendar-month
25
+ - camera
26
+ - eraser
@@ -0,0 +1,20 @@
1
+ // The mixins in this file are automatically generated by the 'montage'
2
+ // command provided by the montage' gem. Don't edit this file directly; change
3
+ // the montage.yml configuration file, then re-run 'montage'
4
+
5
+ <% @project.sprites.each do |sprite| %>
6
+ =<%= sprite.name %>-sprite($icon, $x_offset: 0px, $y_offset: 0px)
7
+ <% sprite.sources.each_with_index do |source, index| %>
8
+ @<%= index == 0 ? '' : 'else ' %>if $icon == "<%= source.name %>"
9
+ $y_offset: $y_offset - <%= sprite.position_of(source) %>px
10
+ <% end %>
11
+ background: url(<%= @project.paths.url + "/#{sprite.name}.png" %>) $x_offset $y_offset no-repeat
12
+
13
+ =<%= sprite.name %>-sprite-pos($icon, $x_offset: 0px, $y_offset: 0px)
14
+ <% sprite.sources.each_with_index do |source, index| %>
15
+ @<%= index == 0 ? '' : 'else ' %>if $icon == "<%= source.name %>"
16
+ $y_offset: $y_offset - <%= sprite.position_of(source) %>px
17
+ <% end %>
18
+ background-position: $x_offset $y_offset
19
+
20
+ <% end %>
@@ -0,0 +1,3 @@
1
+ module Montage
2
+ VERSION = File.read(File.expand_path('../../../VERSION', __FILE__)).strip
3
+ end
data/montage.gemspec ADDED
@@ -0,0 +1,145 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{montage}
8
+ s.version = "0.2.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Anthony Williams"]
12
+ s.date = %q{2010-04-08}
13
+ s.default_executable = %q{montage}
14
+ s.description = %q{Even Rocky had a montage.}
15
+ s.email = %q{hi@antw.me}
16
+ s.executables = ["montage"]
17
+ s.extra_rdoc_files = [
18
+ "LICENSE",
19
+ "README.md"
20
+ ]
21
+ s.files = [
22
+ ".document",
23
+ ".gitignore",
24
+ "History.md",
25
+ "LICENSE",
26
+ "README.md",
27
+ "Rakefile",
28
+ "VERSION",
29
+ "bin/montage",
30
+ "lib/montage.rb",
31
+ "lib/montage/commands.rb",
32
+ "lib/montage/commands/generate.rb",
33
+ "lib/montage/commands/init.rb",
34
+ "lib/montage/core_ext.rb",
35
+ "lib/montage/project.rb",
36
+ "lib/montage/sass_builder.rb",
37
+ "lib/montage/source.rb",
38
+ "lib/montage/sprite.rb",
39
+ "lib/montage/templates/montage.yml",
40
+ "lib/montage/templates/sass_mixins.erb",
41
+ "lib/montage/templates/sources/book.png",
42
+ "lib/montage/templates/sources/box-label.png",
43
+ "lib/montage/templates/sources/calculator.png",
44
+ "lib/montage/templates/sources/calendar-month.png",
45
+ "lib/montage/templates/sources/camera.png",
46
+ "lib/montage/templates/sources/eraser.png",
47
+ "lib/montage/version.rb",
48
+ "montage.gemspec",
49
+ "spec/fixtures/custom_dirs/montage.yml",
50
+ "spec/fixtures/default/montage.yml",
51
+ "spec/fixtures/default/public/images/sprites/src/one.png",
52
+ "spec/fixtures/default/public/images/sprites/src/three.png",
53
+ "spec/fixtures/default/public/images/sprites/src/two.png",
54
+ "spec/fixtures/directory_config/config/montage.yml",
55
+ "spec/fixtures/missing_source/montage.yml",
56
+ "spec/fixtures/missing_source_dir/montage.yml",
57
+ "spec/fixtures/root_config/montage.yml",
58
+ "spec/fixtures/root_config/public/images/sprites/src/source_one.png",
59
+ "spec/fixtures/root_config/public/images/sprites/src/source_three.jpg",
60
+ "spec/fixtures/root_config/public/images/sprites/src/source_two",
61
+ "spec/fixtures/sources/hundred.png",
62
+ "spec/fixtures/sources/mammoth.png",
63
+ "spec/fixtures/sources/other.png",
64
+ "spec/fixtures/sources/twenty.png",
65
+ "spec/fixtures/subdirs/montage.yml",
66
+ "spec/fixtures/subdirs/sub/sub/keep",
67
+ "spec/lib/command_runner.rb",
68
+ "spec/lib/fixtures.rb",
69
+ "spec/lib/have_public_method_defined.rb",
70
+ "spec/lib/project_helper.rb",
71
+ "spec/lib/shared_project_specs.rb",
72
+ "spec/lib/shared_sprite_specs.rb",
73
+ "spec/montage/commands/generate_spec.rb",
74
+ "spec/montage/commands/init_spec.rb",
75
+ "spec/montage/core_ext_spec.rb",
76
+ "spec/montage/project_spec.rb",
77
+ "spec/montage/sass_builder_spec.rb",
78
+ "spec/montage/source_spec.rb",
79
+ "spec/montage/spec/have_public_method_defined_spec.rb",
80
+ "spec/montage/sprite_spec.rb",
81
+ "spec/rcov.opts",
82
+ "spec/spec.opts",
83
+ "spec/spec_helper.rb",
84
+ "tasks/spec.rake",
85
+ "tasks/yard.rake"
86
+ ]
87
+ s.has_rdoc = false
88
+ s.homepage = %q{http://github.com/antw/montage}
89
+ s.rdoc_options = ["--charset=UTF-8"]
90
+ s.require_paths = ["lib"]
91
+ s.rubygems_version = %q{1.3.6}
92
+ s.summary = %q{Montage}
93
+ s.test_files = [
94
+ "spec/lib/command_runner.rb",
95
+ "spec/lib/fixtures.rb",
96
+ "spec/lib/have_public_method_defined.rb",
97
+ "spec/lib/project_helper.rb",
98
+ "spec/lib/shared_project_specs.rb",
99
+ "spec/lib/shared_sprite_specs.rb",
100
+ "spec/montage/commands/generate_spec.rb",
101
+ "spec/montage/commands/init_spec.rb",
102
+ "spec/montage/core_ext_spec.rb",
103
+ "spec/montage/project_spec.rb",
104
+ "spec/montage/sass_builder_spec.rb",
105
+ "spec/montage/source_spec.rb",
106
+ "spec/montage/spec/have_public_method_defined_spec.rb",
107
+ "spec/montage/sprite_spec.rb",
108
+ "spec/spec_helper.rb"
109
+ ]
110
+
111
+ if s.respond_to? :specification_version then
112
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
113
+ s.specification_version = 3
114
+
115
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
116
+ s.add_runtime_dependency(%q<activesupport>, [">= 3.0.0.beta"])
117
+ s.add_runtime_dependency(%q<rmagick>, [">= 2.12"])
118
+ s.add_runtime_dependency(%q<highline>, [">= 1.5"])
119
+ s.add_development_dependency(%q<rspec>, [">= 1.3.0"])
120
+ s.add_development_dependency(%q<cucumber>, [">= 0.6"])
121
+ s.add_development_dependency(%q<open4>, [">= 1.0"])
122
+ s.add_development_dependency(%q<haml>, [">= 3.0.0.beta.1"])
123
+ s.add_development_dependency(%q<yard>, [">= 0.5"])
124
+ else
125
+ s.add_dependency(%q<activesupport>, [">= 3.0.0.beta"])
126
+ s.add_dependency(%q<rmagick>, [">= 2.12"])
127
+ s.add_dependency(%q<highline>, [">= 1.5"])
128
+ s.add_dependency(%q<rspec>, [">= 1.3.0"])
129
+ s.add_dependency(%q<cucumber>, [">= 0.6"])
130
+ s.add_dependency(%q<open4>, [">= 1.0"])
131
+ s.add_dependency(%q<haml>, [">= 3.0.0.beta.1"])
132
+ s.add_dependency(%q<yard>, [">= 0.5"])
133
+ end
134
+ else
135
+ s.add_dependency(%q<activesupport>, [">= 3.0.0.beta"])
136
+ s.add_dependency(%q<rmagick>, [">= 2.12"])
137
+ s.add_dependency(%q<highline>, [">= 1.5"])
138
+ s.add_dependency(%q<rspec>, [">= 1.3.0"])
139
+ s.add_dependency(%q<cucumber>, [">= 0.6"])
140
+ s.add_dependency(%q<open4>, [">= 1.0"])
141
+ s.add_dependency(%q<haml>, [">= 3.0.0.beta.1"])
142
+ s.add_dependency(%q<yard>, [">= 0.5"])
143
+ end
144
+ end
145
+
@@ -0,0 +1,8 @@
1
+ ---
2
+ config.sources: "custom/sources"
3
+ config.sprites: "custom/output"
4
+ config.sass: "custom/sass"
5
+ config.sprite_url: "custom/images"
6
+
7
+ sprite_one:
8
+ - source_one
@@ -0,0 +1,7 @@
1
+ ---
2
+ sprite_one:
3
+ - one
4
+ - two
5
+
6
+ sprite_two:
7
+ - three
@@ -0,0 +1,5 @@
1
+ ---
2
+ sprite_one:
3
+ - source_one
4
+ - source_two
5
+ - source_three
@@ -0,0 +1,3 @@
1
+ ---
2
+ sprite_one:
3
+ - source_one
@@ -0,0 +1,5 @@
1
+ ---
2
+ sprite_one:
3
+ - source_one
4
+ - source_two
5
+ - source_three
@@ -0,0 +1,5 @@
1
+ ---
2
+ sprite_one:
3
+ - source_one
4
+ - source_two
5
+ - source_three
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,5 @@
1
+ ---
2
+ sprite_one:
3
+ - source_one
4
+ - source_two
5
+ - source_three
File without changes
@@ -0,0 +1,140 @@
1
+ require 'rbconfig'
2
+ require 'tempfile'
3
+ require 'open4'
4
+
5
+ require Pathname(__FILE__).dirname + 'project_helper'
6
+
7
+ module Montage
8
+ module Spec
9
+ # Runs montage commands in a subprocess and reports back on their exit
10
+ # status and output.
11
+ #
12
+ # See spec/montage/commands/*_spec.rb.
13
+ #
14
+ class CommandRunner < ProjectHelper
15
+
16
+ # Path to the Ruby binary.
17
+ RUBY = Pathname.new(Config::CONFIG['bindir']) +
18
+ Config::CONFIG['ruby_install_name']
19
+
20
+ # Path to the montage executable.
21
+ EXECUTABLE = Pathname.new(__FILE__).dirname.
22
+ expand_path + '../../bin/montage'
23
+
24
+ attr_reader :status, :stderr, :stdout
25
+
26
+ # ----------------------------------------------------------------------
27
+
28
+ # Creates a new CommandRunner instance.
29
+ #
30
+ # @param [String] command
31
+ # The command to be run (exactly as it would be on the command-line).
32
+ #
33
+ def initialize(command)
34
+ super()
35
+ @command = command
36
+ end
37
+
38
+ # Runs the command in the test directory.
39
+ #
40
+ # @return [CommandRunner]
41
+ # Returns self.
42
+ #
43
+ def run!(&block)
44
+ run(@command, &block)
45
+ end
46
+
47
+ # Runs the given command in the test directory.
48
+ #
49
+ # @param [String] command
50
+ # The command to be run.
51
+ #
52
+ # @return [CommandRunner]
53
+ # Returns self.
54
+ #
55
+ def run(command, &block)
56
+ if command =~ /^montage(.*)$/
57
+ command = "#{RUBY} -rubygems #{EXECUTABLE}#{$1} --no-color"
58
+ end
59
+
60
+ @status, @stderr, @stdout = nil, nil, nil
61
+
62
+ in_project_dir do
63
+ @status = Open4.popen4(command.to_s) do |_, stdin, stdout, stderr|
64
+ yield stdin if block_given?
65
+
66
+ @stdout = stdout.read
67
+ @stderr = stderr.read
68
+ end.exitstatus
69
+ end
70
+
71
+ self
72
+ end
73
+
74
+ # Returns if the latest command completed successfully.
75
+ #
76
+ # @return [Boolean]
77
+ #
78
+ def success?
79
+ @status == 0
80
+ end
81
+
82
+ # Returns if the latest command failed to complete successfully.
83
+ #
84
+ # @return [Boolean]
85
+ #
86
+ def failure?
87
+ not success?
88
+ end
89
+
90
+ # Returns the dimensions of a generated sprite image.
91
+ #
92
+ # @param [String] name
93
+ # The name of the sprite file.
94
+ #
95
+ # @return [Array<Integer, Integer>]
96
+ #
97
+ def dimensions_of(name)
98
+ info = Magick::Image.ping path_to_sprite(name)
99
+ [info.first.columns, info.first.rows]
100
+ end
101
+
102
+ private # --------------------------------------------------------------
103
+
104
+ # Temporarily switches to the test directory for running commands.
105
+ def in_project_dir(&blk)
106
+ Dir.chdir(project_dir, &blk)
107
+ end
108
+
109
+ end # CommandRunner
110
+
111
+ # Runs the montage init command, providing the command with the various
112
+ # inputs it wants.
113
+ #
114
+ class InitCommandRunner < CommandRunner
115
+ # Creates a new InitCommandRunner instance.
116
+ #
117
+ # @param [Hash] responses
118
+ # Takes inputs to be given to the highline GUI. Accepts :sources and
119
+ # :sprites
120
+ #
121
+ def initialize(responses = {})
122
+ super 'montage init'
123
+ @responses = responses
124
+ end
125
+
126
+ # Runs the command in the test directory.
127
+ #
128
+ # @return [InitCommandRunner]
129
+ # Returns self.
130
+ #
131
+ def run!
132
+ super do |stdin|
133
+ stdin.puts @responses.fetch(:sprites, "\n")
134
+ stdin.puts @responses.fetch(:sources, "\n")
135
+ end
136
+ end
137
+ end
138
+
139
+ end # Spec
140
+ end # Montage