montage 0.2.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.
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