montage 0.2.1 → 0.3.0
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/History.md +23 -0
- data/Rakefile +2 -4
- data/VERSION +1 -1
- data/bin/montage +11 -4
- data/lib/montage/commands/generate.rb +72 -11
- data/lib/montage/commands/init.rb +5 -11
- data/lib/montage/commands.rb +51 -18
- data/lib/montage/project.rb +54 -39
- data/lib/montage/source.rb +15 -44
- data/lib/montage/sprite.rb +25 -37
- data/lib/montage/sprite_definition.rb +127 -0
- data/lib/montage/templates/montage.yml +29 -19
- data/lib/montage/templates/sass_mixins.erb +1 -1
- data/lib/montage/templates/sources/{book.png → one/book.png} +0 -0
- data/lib/montage/templates/sources/{box-label.png → one/box-label.png} +0 -0
- data/lib/montage/templates/sources/{calculator.png → one/calculator.png} +0 -0
- data/lib/montage/templates/sources/{calendar-month.png → one/calendar-month.png} +0 -0
- data/lib/montage/templates/sources/{camera.png → one/camera.png} +0 -0
- data/lib/montage/templates/sources/{eraser.png → one/eraser.png} +0 -0
- data/lib/montage/templates/sources/two/inbox-image.png +0 -0
- data/lib/montage/templates/sources/two/magnet.png +0 -0
- data/lib/montage/templates/sources/two/newspaper.png +0 -0
- data/lib/montage/templates/sources/two/television.png +0 -0
- data/lib/montage/templates/sources/two/wand-hat.png +0 -0
- data/lib/montage/templates/sources/two/wooden-box-label.png +0 -0
- data/lib/montage.rb +10 -1
- data/montage.gemspec +17 -34
- data/spec/lib/project_helper.rb +41 -6
- data/spec/lib/shared_project_specs.rb +10 -11
- data/spec/montage/commands/generate_spec.rb +86 -132
- data/spec/montage/commands/init_spec.rb +18 -43
- data/spec/montage/project_spec.rb +77 -63
- data/spec/montage/sass_builder_spec.rb +33 -57
- data/spec/montage/source_spec.rb +10 -31
- data/spec/montage/sprite_definition_spec.rb +361 -0
- data/spec/montage/sprite_spec.rb +58 -57
- metadata +31 -70
- data/spec/fixtures/custom_dirs/montage.yml +0 -8
- data/spec/fixtures/default/montage.yml +0 -7
- data/spec/fixtures/default/public/images/sprites/src/one.png +0 -0
- data/spec/fixtures/default/public/images/sprites/src/three.png +0 -0
- data/spec/fixtures/default/public/images/sprites/src/two.png +0 -0
- data/spec/fixtures/directory_config/config/montage.yml +0 -5
- data/spec/fixtures/missing_source/montage.yml +0 -3
- data/spec/fixtures/missing_source_dir/montage.yml +0 -5
- data/spec/fixtures/root_config/montage.yml +0 -5
- data/spec/fixtures/root_config/public/images/sprites/src/source_one.png +0 -0
- data/spec/fixtures/root_config/public/images/sprites/src/source_three.jpg +0 -0
- data/spec/fixtures/root_config/public/images/sprites/src/source_two +0 -0
- data/spec/fixtures/sources/hundred.png +0 -0
- data/spec/fixtures/sources/mammoth.png +0 -0
- data/spec/fixtures/sources/other.png +0 -0
- data/spec/fixtures/sources/twenty.png +0 -0
- data/spec/fixtures/subdirs/montage.yml +0 -5
- data/spec/fixtures/subdirs/sub/sub/keep +0 -0
- data/spec/lib/fixtures.rb +0 -7
    
        data/History.md
    CHANGED
    
    | @@ -1,3 +1,26 @@ | |
| 1 | 
            +
            v0.3.0 - 2010-04-12
         | 
| 2 | 
            +
            -------------------
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            * The "montage.yml" file has been replaced with ".montage" which should be
         | 
| 5 | 
            +
              located in your project root. In addition, the file is now rather different,
         | 
| 6 | 
            +
              and in most cases will never need to be edited when you want to add new
         | 
| 7 | 
            +
              sources to the sprite.
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            * By default Montage will now save sprites to public/images, expected source
         | 
| 10 | 
            +
              images to be in public/images/subdir -- where "subdir" will become the name
         | 
| 11 | 
            +
              of the sprite. All sources in a subdirectory will be added to the same
         | 
| 12 | 
            +
              sprite.
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              This behavior is entirely customisable in the .montage file.
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            * The ".montage_cache" file which was previously saved in the same directory
         | 
| 17 | 
            +
              as sprites is now saved in the project root.
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            * The `montage` command now allows you to specify a path to a Montage
         | 
| 20 | 
            +
              configuration file; for example `montage path/to/montage.yml`. When using
         | 
| 21 | 
            +
              a non-standard directory structure, you can specify a "config.root" option
         | 
| 22 | 
            +
              in the configuration file, containing the path to the project root.
         | 
| 23 | 
            +
             | 
| 1 24 | 
             
            v0.2.0 - 2010-04-08
         | 
| 2 25 | 
             
            -------------------
         | 
| 3 26 |  | 
    
        data/Rakefile
    CHANGED
    
    | @@ -20,13 +20,11 @@ begin | |
| 20 20 | 
             
                gem.has_rdoc    = false
         | 
| 21 21 |  | 
| 22 22 | 
             
                # Dependencies.
         | 
| 23 | 
            -
                gem.add_dependency ' | 
| 24 | 
            -
                gem.add_dependency ' | 
| 25 | 
            -
                gem.add_dependency 'highline',      '>= 1.5'
         | 
| 23 | 
            +
                gem.add_dependency 'rmagick',  '>= 2.12'
         | 
| 24 | 
            +
                gem.add_dependency 'highline', '>= 1.5'
         | 
| 26 25 |  | 
| 27 26 | 
             
                # Development dependencies.
         | 
| 28 27 | 
             
                gem.add_development_dependency 'rspec',    '>= 1.3.0'
         | 
| 29 | 
            -
                gem.add_development_dependency 'cucumber', '>= 0.6'
         | 
| 30 28 | 
             
                gem.add_development_dependency 'open4',    '>= 1.0'
         | 
| 31 29 | 
             
                gem.add_development_dependency 'haml',     '>= 3.0.0.beta.1'
         | 
| 32 30 | 
             
                gem.add_development_dependency 'yard',     '>= 0.5'
         | 
    
        data/VERSION
    CHANGED
    
    | @@ -1 +1 @@ | |
| 1 | 
            -
            0. | 
| 1 | 
            +
            0.3.0
         | 
    
        data/bin/montage
    CHANGED
    
    | @@ -4,19 +4,26 @@ unless $:.include?(File.dirname(__FILE__) + '/../lib') | |
| 4 4 | 
             
              $:.unshift(File.dirname(__FILE__) + '/../lib')
         | 
| 5 5 | 
             
            end
         | 
| 6 6 |  | 
| 7 | 
            +
            require 'optparse'
         | 
| 7 8 | 
             
            require 'rubygems' unless ENV['NORUBYGEMS']
         | 
| 8 9 |  | 
| 9 10 | 
             
            require 'montage'
         | 
| 10 11 | 
             
            require 'montage/commands'
         | 
| 11 12 |  | 
| 12 | 
            -
             | 
| 13 | 
            +
            require 'highline/import'
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            module Kernel
         | 
| 16 | 
            +
              def_delegators :$terminal, :color
         | 
| 17 | 
            +
            end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            args = Montage::Commands.parse_options!(ARGV.dup)
         | 
| 20 | 
            +
            puts
         | 
| 13 21 |  | 
| 14 22 | 
             
            case ARGV[0]
         | 
| 15 23 | 
             
              when 'init'
         | 
| 16 24 | 
             
                require 'montage/commands/init'
         | 
| 17 | 
            -
                Montage::Commands::Init.run( | 
| 25 | 
            +
                Montage::Commands::Init.run(args)
         | 
| 18 26 | 
             
              else
         | 
| 19 27 | 
             
                require 'montage/commands/generate'
         | 
| 20 | 
            -
                Montage::Commands::Generate.run( | 
| 28 | 
            +
                Montage::Commands::Generate.run(args)
         | 
| 21 29 | 
             
            end
         | 
| 22 | 
            -
             | 
| @@ -17,7 +17,20 @@ module Montage | |
| 17 17 | 
             
                  #   The arguments given on the command line.
         | 
| 18 18 | 
             
                  #
         | 
| 19 19 | 
             
                  def self.run(argv)
         | 
| 20 | 
            -
                     | 
| 20 | 
            +
                    # If there are any arguments, the first one is a path to a montage
         | 
| 21 | 
            +
                    # config file.
         | 
| 22 | 
            +
                    if argv.first and not Pathname.new(argv.first).file?
         | 
| 23 | 
            +
                      say color(<<-ERROR.compress_lines, :red)
         | 
| 24 | 
            +
                        Couldn't find `#{argv.first}' configuration file. Are you
         | 
| 25 | 
            +
                        sure you got the path right?
         | 
| 26 | 
            +
                      ERROR
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                      exit(1)
         | 
| 29 | 
            +
                    end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
             | 
| 32 | 
            +
                    new(Montage::Project.find(argv.first || Dir.pwd),
         | 
| 33 | 
            +
                        Montage::Commands.config[:force]).run!
         | 
| 21 34 |  | 
| 22 35 | 
             
                  rescue Montage::MissingProject
         | 
| 23 36 | 
             
                    say color(<<-ERROR.compress_lines, :red)
         | 
| @@ -52,6 +65,7 @@ module Montage | |
| 52 65 | 
             
                      optimise_with_pngout!
         | 
| 53 66 | 
             
                      write_cache!
         | 
| 54 67 | 
             
                      write_sass!
         | 
| 68 | 
            +
                      warn_deviants!
         | 
| 55 69 | 
             
                    end
         | 
| 56 70 | 
             
                  end
         | 
| 57 71 |  | 
| @@ -63,7 +77,7 @@ module Montage | |
| 63 77 | 
             
                  #
         | 
| 64 78 | 
             
                  def cache
         | 
| 65 79 | 
             
                    @_sprite_caches ||= begin
         | 
| 66 | 
            -
                      cache_path = @project.paths. | 
| 80 | 
            +
                      cache_path = @project.paths.root + '.montage_cache'
         | 
| 67 81 | 
             
                      cache_path.file? ? YAML.load_file(cache_path) || {} : {}
         | 
| 68 82 | 
             
                    end
         | 
| 69 83 | 
             
                  end
         | 
| @@ -75,15 +89,16 @@ module Montage | |
| 75 89 | 
             
                  #   Returns true if at least one sprite has been updated.
         | 
| 76 90 | 
             
                  #
         | 
| 77 91 | 
             
                  def generate_sprites!
         | 
| 78 | 
            -
                    unless @project.paths.sprites.directory?
         | 
| 79 | 
            -
                      @project.paths.sprites.mkpath
         | 
| 80 | 
            -
                    end
         | 
| 81 | 
            -
             | 
| 82 92 | 
             
                    @project.sprites.each do |sprite|
         | 
| 83 93 | 
             
                      digest = sprite.digest
         | 
| 84 94 |  | 
| 95 | 
            +
                      # Ensure that we can write to the output directory.
         | 
| 96 | 
            +
                      unless sprite.save_path.dirname.directory?
         | 
| 97 | 
            +
                        sprite.save_path.dirname.mkpath
         | 
| 98 | 
            +
                      end
         | 
| 85 99 |  | 
| 86 | 
            -
                      if @force or cache[sprite.name] != digest or | 
| 100 | 
            +
                      if @force or cache[sprite.name] != digest or
         | 
| 101 | 
            +
                          not sprite.save_path.file?
         | 
| 87 102 | 
             
                        with_feedback %(Generating "#{sprite.name}"), 'Generating' do
         | 
| 88 103 | 
             
                          sprite.write
         | 
| 89 104 | 
             
                          cache[sprite.name] = digest
         | 
| @@ -124,18 +139,19 @@ module Montage | |
| 124 139 | 
             
                    end
         | 
| 125 140 |  | 
| 126 141 | 
             
                    @generated.each do |sprite|
         | 
| 127 | 
            -
                      original_size = sprite. | 
| 142 | 
            +
                      original_size = sprite.save_path.size
         | 
| 143 | 
            +
                      save_path = sprite.save_path
         | 
| 128 144 |  | 
| 129 145 | 
             
                      with_feedback %(Optimising "#{sprite.name}"), 'Optimising' do
         | 
| 130 146 | 
             
                        5.times do |i|
         | 
| 131 147 | 
             
                          # Optimise until pngout reports that it can't compress further,
         | 
| 132 148 | 
             
                          # or until we've tried five times.
         | 
| 133 | 
            -
                          out = `#{pngout} #{ | 
| 149 | 
            +
                          out = `#{pngout} #{save_path} #{save_path} -s0 -k0 -y`
         | 
| 134 150 | 
             
                          break if out =~ /Unable to compress further/
         | 
| 135 151 | 
             
                        end
         | 
| 136 152 | 
             
                      end
         | 
| 137 153 |  | 
| 138 | 
            -
                      new_size  =  | 
| 154 | 
            +
                      new_size  = save_path.size
         | 
| 139 155 |  | 
| 140 156 | 
             
                      reduction = ('%.1fkb (%d' % [
         | 
| 141 157 | 
             
                        (original_size.to_f - new_size) / 1024,
         | 
| @@ -158,7 +174,7 @@ module Montage | |
| 158 174 | 
             
                  # Step 3: Writes the cached digests to the cache file.
         | 
| 159 175 | 
             
                  #
         | 
| 160 176 | 
             
                  def write_cache!
         | 
| 161 | 
            -
                    cache_path = @project.paths. | 
| 177 | 
            +
                    cache_path = @project.paths.root + '.montage_cache'
         | 
| 162 178 |  | 
| 163 179 | 
             
                    File.open(cache_path, 'w') do |cache_writer|
         | 
| 164 180 | 
             
                      cache_writer.puts YAML.dump(cache)
         | 
| @@ -176,6 +192,37 @@ module Montage | |
| 176 192 | 
             
                    end
         | 
| 177 193 | 
             
                  end
         | 
| 178 194 |  | 
| 195 | 
            +
                  # Step 5: Warn about images which are more than one standard deviation
         | 
| 196 | 
            +
                  # from the mean width.
         | 
| 197 | 
            +
                  #
         | 
| 198 | 
            +
                  def warn_deviants!
         | 
| 199 | 
            +
                    @generated.each do |sprite|
         | 
| 200 | 
            +
                      next if sprite.sources.size < 2
         | 
| 201 | 
            +
             | 
| 202 | 
            +
                      mean, std_dev = standard_deviation(sprite.sources.map do |source|
         | 
| 203 | 
            +
                        source.image.columns
         | 
| 204 | 
            +
                      end)
         | 
| 205 | 
            +
             | 
| 206 | 
            +
                      next if std_dev < 100 # Skip relatively narrow images.
         | 
| 207 | 
            +
             | 
| 208 | 
            +
                      sprite.sources.each do |source|
         | 
| 209 | 
            +
                        width = source.image.columns
         | 
| 210 | 
            +
                        if width > mean + std_dev || width < mean - std_dev
         | 
| 211 | 
            +
                          say <<-MESSAGE.compress_lines
         | 
| 212 | 
            +
                            The "#{source.name}" source image in the "#{sprite.name}"
         | 
| 213 | 
            +
                            sprite deviates significantly from the average width. You
         | 
| 214 | 
            +
                            might want to consider removing this source from the sprite.
         | 
| 215 | 
            +
             | 
| 216 | 
            +
                            The mean width for sources in this sprite is #{mean}px,
         | 
| 217 | 
            +
                            while this source is #{width}px wide.
         | 
| 218 | 
            +
             | 
| 219 | 
            +
                          MESSAGE
         | 
| 220 | 
            +
                          say Montage::Commands::BLANK
         | 
| 221 | 
            +
                        end
         | 
| 222 | 
            +
                      end
         | 
| 223 | 
            +
                    end
         | 
| 224 | 
            +
                  end
         | 
| 225 | 
            +
             | 
| 179 226 | 
             
                  # --- Optimisation Output ----------------------------------------------
         | 
| 180 227 |  | 
| 181 228 | 
             
                  # Executes a block while providing live feedback to the user.
         | 
| @@ -229,6 +276,20 @@ module Montage | |
| 229 276 | 
             
                    say "#{RESET}#{prefix}"
         | 
| 230 277 | 
             
                  end
         | 
| 231 278 |  | 
| 279 | 
            +
                  # Knuth. via Wikipedia. :/
         | 
| 280 | 
            +
                  def standard_deviation(data)
         | 
| 281 | 
            +
                    n, mean, m2 = 0, 0, 0
         | 
| 282 | 
            +
             | 
| 283 | 
            +
                    data.each do |x|
         | 
| 284 | 
            +
                      n = n + 1
         | 
| 285 | 
            +
                      delta = x - mean
         | 
| 286 | 
            +
                      mean = mean + delta / n
         | 
| 287 | 
            +
                      m2 = m2 + delta * (x - mean)
         | 
| 288 | 
            +
                    end
         | 
| 289 | 
            +
             | 
| 290 | 
            +
                    [mean, Math.sqrt(m2 / (n - 1))]
         | 
| 291 | 
            +
                  end
         | 
| 292 | 
            +
             | 
| 232 293 | 
             
                end # Generate
         | 
| 233 294 | 
             
              end # Commands
         | 
| 234 295 | 
             
            end # Montage
         | 
| @@ -72,13 +72,13 @@ module Montage | |
| 72 72 |  | 
| 73 73 | 
             
                    @sprites_path =
         | 
| 74 74 | 
             
                      ask("Where do you want generated sprites to be stored?") do |query|
         | 
| 75 | 
            -
                        query.default     = 'public | 
| 75 | 
            +
                        query.default     = File.join('public', 'images')
         | 
| 76 76 | 
             
                        query.answer_type = normalise_path
         | 
| 77 77 | 
             
                      end
         | 
| 78 78 |  | 
| 79 79 | 
             
                    @sources_path =
         | 
| 80 80 | 
             
                      ask("Where are the source images stored?") do |query|
         | 
| 81 | 
            -
                        query.default     =  | 
| 81 | 
            +
                        query.default     = (@sprites_path + "sprites").to_s
         | 
| 82 82 | 
             
                        query.answer_type = normalise_path
         | 
| 83 83 | 
             
                      end
         | 
| 84 84 | 
             
                  end
         | 
| @@ -94,16 +94,10 @@ module Montage | |
| 94 94 | 
             
                  #
         | 
| 95 95 | 
             
                  def create_config!
         | 
| 96 96 | 
             
                    template = File.read(TEMPLATES + 'montage.yml')
         | 
| 97 | 
            -
                    template.gsub!(/<sprites>/,  | 
| 98 | 
            -
                    template.gsub!(/<sources>/,  | 
| 97 | 
            +
                    template.gsub!(/<sprites>/, @sprites_path.to_s)
         | 
| 98 | 
            +
                    template.gsub!(/<sources>/, @sources_path.to_s)
         | 
| 99 99 |  | 
| 100 | 
            -
                     | 
| 101 | 
            -
                      config_path = @dir + 'config/montage.yml'
         | 
| 102 | 
            -
                    else
         | 
| 103 | 
            -
                      config_path = @dir + 'montage.yml'
         | 
| 104 | 
            -
                    end
         | 
| 105 | 
            -
             | 
| 106 | 
            -
                    File.open(config_path, 'w') do |config|
         | 
| 100 | 
            +
                    File.open(@dir + '.montage', 'w') do |config|
         | 
| 107 101 | 
             
                      config.puts template
         | 
| 108 102 | 
             
                    end
         | 
| 109 103 | 
             
                  end
         | 
    
        data/lib/montage/commands.rb
    CHANGED
    
    | @@ -1,29 +1,62 @@ | |
| 1 | 
            -
            require 'highline/import'
         | 
| 2 | 
            -
             | 
| 3 | 
            -
            HighLine.use_color = ! ARGV.delete('--no-color') && ! ARGV.delete('--no-colour')
         | 
| 4 | 
            -
            HighLine.use_color = false if !STDOUT.tty? && !ENV.has_key?("AUTOTEST")
         | 
| 5 | 
            -
             | 
| 6 | 
            -
            module Kernel
         | 
| 7 | 
            -
              def_delegators :$terminal, :color
         | 
| 8 | 
            -
            end
         | 
| 9 | 
            -
             | 
| 10 1 | 
             
            module Montage
         | 
| 11 2 | 
             
              module Commands
         | 
| 3 | 
            +
                extend self
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                # A blank line; HighLine doesn't allow calling +say+ without an argument.
         | 
| 12 6 | 
             
                BLANK = "\n".freeze
         | 
| 13 7 |  | 
| 14 | 
            -
                 | 
| 8 | 
            +
                # Returns a configuration hash, containing options defined on the
         | 
| 9 | 
            +
                # command-line.
         | 
| 10 | 
            +
                #
         | 
| 11 | 
            +
                # @return [Hash]
         | 
| 12 | 
            +
                #
         | 
| 13 | 
            +
                def config
         | 
| 14 | 
            +
                  @config ||= { :force => false, :quiet => false }
         | 
| 15 | 
            +
                end
         | 
| 15 16 |  | 
| 16 | 
            -
                #  | 
| 17 | 
            -
                # | 
| 18 | 
            -
                 | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 21 | 
            -
                   | 
| 22 | 
            -
             | 
| 17 | 
            +
                # Uses OptParse to parse command-line arguments.
         | 
| 18 | 
            +
                #
         | 
| 19 | 
            +
                # Returns any unparsed command-line arguments.
         | 
| 20 | 
            +
                #
         | 
| 21 | 
            +
                def parse_options!(argv)
         | 
| 22 | 
            +
                  HighLine.use_color = false if !STDOUT.tty? && !ENV.has_key?("AUTOTEST")
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  OptionParser.new do |opts|
         | 
| 25 | 
            +
                    opts.banner = "Usage: montage [config file path] [options]"
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                    opts.on('-c', '--[no-]color', '--[no-]colour',
         | 
| 28 | 
            +
                            'Enables and disables colour output.') do |color|
         | 
| 29 | 
            +
                      HighLine.use_color = color
         | 
| 30 | 
            +
                    end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                    opts.on('-f', '--force',
         | 
| 33 | 
            +
                            'Regenerate sprites even if no changes have been made.') do
         | 
| 34 | 
            +
                      Montage::Commands.config[:force] = true
         | 
| 35 | 
            +
                    end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                    # opts.on('-q', '--quiet',
         | 
| 38 | 
            +
                    #         'Tell Montage to shut up. No messages sent to STDOUT.') do
         | 
| 39 | 
            +
                    #   Montage::Commands.config[:quiet] = true
         | 
| 40 | 
            +
                    # end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                    opts.on_tail("-h", "--help", "Shows this message.") do
         | 
| 43 | 
            +
                      say BLANK
         | 
| 44 | 
            +
                      say opts.to_s
         | 
| 45 | 
            +
                      exit
         | 
| 46 | 
            +
                    end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                    opts.on_tail("--version", "Print the current Montage version.") do
         | 
| 49 | 
            +
                      say BLANK
         | 
| 50 | 
            +
                      say "Montage v#{Montage::VERSION}"
         | 
| 51 | 
            +
                      exit
         | 
| 52 | 
            +
                    end
         | 
| 53 | 
            +
                  end.parse!(argv)
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                  argv
         | 
| 23 56 | 
             
                end
         | 
| 24 57 |  | 
| 25 58 | 
             
                # Exits immediately, outputting a blank line first.
         | 
| 26 | 
            -
                def exit(status)
         | 
| 59 | 
            +
                def exit(status = 0)
         | 
| 27 60 | 
             
                  say BLANK
         | 
| 28 61 | 
             
                  Kernel.exit(status)
         | 
| 29 62 | 
             
                end
         | 
    
        data/lib/montage/project.rb
    CHANGED
    
    | @@ -3,14 +3,16 @@ module Montage | |
| 3 3 | 
             
              # configuration file, and source images.
         | 
| 4 4 | 
             
              class Project
         | 
| 5 5 | 
             
                DEFAULTS = {
         | 
| 6 | 
            -
                  :sources | 
| 7 | 
            -
                  :sprites | 
| 8 | 
            -
                  :sass | 
| 9 | 
            -
                  : | 
| 6 | 
            +
                  :sources => 'public/images/sprites/src',
         | 
| 7 | 
            +
                  :sprites => 'public/images/sprites',
         | 
| 8 | 
            +
                  :sass    => 'public/stylesheets/sass',
         | 
| 9 | 
            +
                  :to      => "public/images/:name.png",
         | 
| 10 | 
            +
                  :url     => "/images/:name.png",
         | 
| 11 | 
            +
                  :padding => 20
         | 
| 10 12 | 
             
                }
         | 
| 11 13 |  | 
| 12 14 | 
             
                # Stores all the paths the project needs.
         | 
| 13 | 
            -
                Paths = Struct.new(:root, :config, : | 
| 15 | 
            +
                Paths = Struct.new(:root, :config, :sass, :url)
         | 
| 14 16 |  | 
| 15 17 | 
             
                # Returns the Paths instance for the project.
         | 
| 16 18 | 
             
                #
         | 
| @@ -37,31 +39,29 @@ module Montage | |
| 37 39 | 
             
                # to be correct. If you're not sure of the exact paths, use +Project.find+
         | 
| 38 40 | 
             
                # instead.
         | 
| 39 41 | 
             
                #
         | 
| 40 | 
            -
                # @param [String, Pathname] root_path
         | 
| 41 | 
            -
                #   Path to the root of the Montage project.
         | 
| 42 42 | 
             
                # @param [String, Pathname] config_path
         | 
| 43 43 | 
             
                #   Path to the config file.
         | 
| 44 44 | 
             
                #
         | 
| 45 | 
            -
                def initialize( | 
| 46 | 
            -
                  root_path   = Pathname.new(root_path)
         | 
| 45 | 
            +
                def initialize(config_path)
         | 
| 47 46 | 
             
                  config_path = Pathname.new(config_path)
         | 
| 47 | 
            +
                  config      = YAML.load_file(config_path)
         | 
| 48 | 
            +
                  root_path   = determine_project_root(config_path, config)
         | 
| 48 49 |  | 
| 49 | 
            -
                   | 
| 50 | 
            +
                  # Sass path may be a string representing a path, or `false`.
         | 
| 51 | 
            +
                  sass_path = config.delete("config.sass") { DEFAULTS[:sass] }
         | 
| 52 | 
            +
                  sass_path = sass_path.is_a?(String) ? root_path + sass_path : sass_path
         | 
| 50 53 |  | 
| 51 54 | 
             
                  @paths = Paths.new(
         | 
| 52 | 
            -
                    root_path, config_path,
         | 
| 53 | 
            -
                     | 
| 54 | 
            -
                    extract_path_from_config(config, :sprites, root_path),
         | 
| 55 | 
            -
                    extract_path_from_config(config, :sass,    root_path),
         | 
| 56 | 
            -
                    config.delete('config.sprite_url') { DEFAULTS[:sprite_url] }
         | 
| 55 | 
            +
                    root_path, config_path, sass_path,
         | 
| 56 | 
            +
                    config.delete('config.url') { DEFAULTS[:url] }
         | 
| 57 57 | 
             
                  )
         | 
| 58 58 |  | 
| 59 59 | 
             
                  @padding = (config.delete('config.padding') || 20).to_i
         | 
| 60 60 |  | 
| 61 61 | 
             
                  # All remaining config keys are sprite defintions.
         | 
| 62 | 
            -
                  @sprites = config. | 
| 63 | 
            -
                     | 
| 64 | 
            -
                  end
         | 
| 62 | 
            +
                  @sprites = config.map do |path, opts|
         | 
| 63 | 
            +
                    Montage::SpriteDefinition.new(self, path, opts).to_sprites
         | 
| 64 | 
            +
                  end.flatten
         | 
| 65 65 | 
             
                end
         | 
| 66 66 |  | 
| 67 67 | 
             
                # Returns a particular sprite identified by +name+.
         | 
| @@ -82,9 +82,9 @@ module Montage | |
| 82 82 | 
             
                #
         | 
| 83 83 | 
             
                # The configuration item will be _removed_ from the hash.
         | 
| 84 84 | 
             
                #
         | 
| 85 | 
            -
                # @param [Hash] | 
| 86 | 
            -
                # @param [Symbol] | 
| 87 | 
            -
                # @param [Pathname] root | 
| 85 | 
            +
                # @param [Hash]     config  The configuration Hash.
         | 
| 86 | 
            +
                # @param [Symbol]   key     The configuration key.
         | 
| 87 | 
            +
                # @param [Pathname] root    The project root path.
         | 
| 88 88 | 
             
                #
         | 
| 89 89 | 
             
                # @return [Pathname, false]
         | 
| 90 90 | 
             
                #
         | 
| @@ -93,6 +93,30 @@ module Montage | |
| 93 93 | 
             
                  value.is_a?(String) ? root + value : value
         | 
| 94 94 | 
             
                end
         | 
| 95 95 |  | 
| 96 | 
            +
                # Attempts to find the project root for the configuration file. If the
         | 
| 97 | 
            +
                # config file is in a directory called 'config' then the project root is
         | 
| 98 | 
            +
                # assumed to be one level up.
         | 
| 99 | 
            +
                #
         | 
| 100 | 
            +
                # @param [Pathname] config_path  The path to the config file.
         | 
| 101 | 
            +
                # @param [Hash]     config       The project configuration.
         | 
| 102 | 
            +
                #
         | 
| 103 | 
            +
                # @return [Pathname]
         | 
| 104 | 
            +
                #
         | 
| 105 | 
            +
                def determine_project_root(config_path, config)
         | 
| 106 | 
            +
                  config_dir = config_path.dirname
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                  if config.has_key?('config.root')
         | 
| 109 | 
            +
                    root_dir = Pathname.new(config.delete('config.root'))
         | 
| 110 | 
            +
                    root_dir.absolute? ? root_dir : (config_dir + root_dir)
         | 
| 111 | 
            +
                  else
         | 
| 112 | 
            +
                    if config_dir.split.last.to_s == 'config'
         | 
| 113 | 
            +
                      config_dir + '..'
         | 
| 114 | 
            +
                    else
         | 
| 115 | 
            +
                      config_dir
         | 
| 116 | 
            +
                    end
         | 
| 117 | 
            +
                  end
         | 
| 118 | 
            +
                end
         | 
| 119 | 
            +
             | 
| 96 120 | 
             
                # === Class Methods ======================================================
         | 
| 97 121 |  | 
| 98 122 | 
             
                class << self
         | 
| @@ -111,7 +135,7 @@ module Montage | |
| 111 135 | 
             
                  #
         | 
| 112 136 | 
             
                  # If given a path to a directory:
         | 
| 113 137 | 
             
                  #
         | 
| 114 | 
            -
                  #   * Montage will look for montage | 
| 138 | 
            +
                  #   * Montage will look for a .montage file.
         | 
| 115 139 | 
             
                  #
         | 
| 116 140 | 
             
                  #   * If a configuration couldn't be found, +find+ looks in the next
         | 
| 117 141 | 
             
                  #     directory up. It continues until it finds a valid project or runs
         | 
| @@ -131,37 +155,28 @@ module Montage | |
| 131 155 | 
             
                    config_path, root_path = nil, nil
         | 
| 132 156 |  | 
| 133 157 | 
             
                    if path.file?
         | 
| 134 | 
            -
                      root_path = find_root(path)
         | 
| 135 158 | 
             
                      config_path = path
         | 
| 136 159 | 
             
                    elsif path.directory?
         | 
| 137 | 
            -
                       | 
| 138 | 
            -
                         | 
| 139 | 
            -
                      else
         | 
| 140 | 
            -
                        # Assume we're in a subdirectory of the current project.
         | 
| 141 | 
            -
                        path.split.first.ascend do |directory|
         | 
| 142 | 
            -
                          if config_path = find_config(directory)
         | 
| 143 | 
            -
                            break if root_path = find_root(config_path)
         | 
| 144 | 
            -
                          end
         | 
| 145 | 
            -
                        end
         | 
| 160 | 
            +
                      path.ascend do |directory|
         | 
| 161 | 
            +
                        break if config_path = contains_config?(directory)
         | 
| 146 162 | 
             
                      end
         | 
| 147 163 | 
             
                    end
         | 
| 148 164 |  | 
| 149 165 | 
             
                    raise MissingProject, "Montage couldn't find a project to work " \
         | 
| 150 | 
            -
                      "on at `#{path}'"  | 
| 166 | 
            +
                      "on at `#{path}'" unless config_path
         | 
| 151 167 |  | 
| 152 | 
            -
                    new( | 
| 168 | 
            +
                    new(config_path)
         | 
| 153 169 | 
             
                  end
         | 
| 154 170 |  | 
| 155 171 | 
             
                  private
         | 
| 156 172 |  | 
| 157 | 
            -
                  #  | 
| 158 | 
            -
                  # ./montage.yml, then ./config/montage.yml
         | 
| 173 | 
            +
                  # Looks for a .montage configuration file in the given directory.
         | 
| 159 174 | 
             
                  #
         | 
| 160 175 | 
             
                  # @return [String]
         | 
| 161 176 | 
             
                  #
         | 
| 162 | 
            -
                  def  | 
| 163 | 
            -
                     | 
| 164 | 
            -
                     | 
| 177 | 
            +
                  def contains_config?(dir)
         | 
| 178 | 
            +
                    expected = (dir + '.montage')
         | 
| 179 | 
            +
                    expected.file? and expected
         | 
| 165 180 | 
             
                  end
         | 
| 166 181 |  | 
| 167 182 | 
             
                  # Attempts to find the project root for the configuration file. If the
         | 
    
        data/lib/montage/source.rb
    CHANGED
    
    | @@ -3,56 +3,21 @@ module Montage | |
| 3 3 | 
             
              #
         | 
| 4 4 | 
             
              class Source
         | 
| 5 5 |  | 
| 6 | 
            -
                attr_reader :name
         | 
| 6 | 
            +
                attr_reader :name, :path
         | 
| 7 7 | 
             
                alias_method :to_s, :name
         | 
| 8 8 |  | 
| 9 9 | 
             
                # Creates a new Source instance.
         | 
| 10 10 | 
             
                #
         | 
| 11 | 
            -
                # @param [Pathname]  | 
| 12 | 
            -
                #   The  | 
| 13 | 
            -
                # @param [String] name
         | 
| 14 | 
            -
                #   The name of the source image, sans extension
         | 
| 15 | 
            -
                # @param [String] sprite
         | 
| 16 | 
            -
                #   The name of the sprite to which the source belongs. Used only in
         | 
| 17 | 
            -
                #   error messages.
         | 
| 11 | 
            +
                # @param [Pathname] path
         | 
| 12 | 
            +
                #   The path the the source image.
         | 
| 18 13 | 
             
                #
         | 
| 19 | 
            -
                def initialize( | 
| 20 | 
            -
                  @ | 
| 14 | 
            +
                def initialize(path)
         | 
| 15 | 
            +
                  @path = Pathname.new(path)
         | 
| 16 | 
            +
                  @name = @path.basename.to_s.chomp(@path.extname.to_s)
         | 
| 21 17 | 
             
                end
         | 
| 22 18 |  | 
| 23 19 | 
             
                def inspect # :nodoc
         | 
| 24 | 
            -
                  "#<Montage::Source #{@ | 
| 25 | 
            -
                end
         | 
| 26 | 
            -
             | 
| 27 | 
            -
                # Returns the full path to the source image.
         | 
| 28 | 
            -
                #
         | 
| 29 | 
            -
                # @return [Pathname]
         | 
| 30 | 
            -
                # @raise  [Montage::MissingSource]
         | 
| 31 | 
            -
                #
         | 
| 32 | 
            -
                def path
         | 
| 33 | 
            -
                  @path ||= begin
         | 
| 34 | 
            -
                    unless @dir.directory?
         | 
| 35 | 
            -
                      raise MissingSource, <<-MESSAGE.compress_lines
         | 
| 36 | 
            -
                        Couldn't find the source directory for the `#{@sprite_name}'
         | 
| 37 | 
            -
                        sprite. Montage was looking for #{@dir}; if your sprites are in a
         | 
| 38 | 
            -
                        different location, add a 'config.sources' option your config
         | 
| 39 | 
            -
                        file.
         | 
| 40 | 
            -
                      MESSAGE
         | 
| 41 | 
            -
                    end
         | 
| 42 | 
            -
             | 
| 43 | 
            -
                    path = @dir.entries.detect do |entry|
         | 
| 44 | 
            -
                      entry.to_s.chomp(entry.extname) == @name
         | 
| 45 | 
            -
                    end
         | 
| 46 | 
            -
             | 
| 47 | 
            -
                    if path.nil?
         | 
| 48 | 
            -
                      raise MissingSource, <<-MESSAGE.compress_lines
         | 
| 49 | 
            -
                        Couldn't find a matching file for source image `#{@name}' as part
         | 
| 50 | 
            -
                        of the `#{@sprite_name}' sprite. Was looking in `#{@dir}'.
         | 
| 51 | 
            -
                      MESSAGE
         | 
| 52 | 
            -
                    end
         | 
| 53 | 
            -
             | 
| 54 | 
            -
                    @dir + path
         | 
| 55 | 
            -
                  end
         | 
| 20 | 
            +
                  "#<Montage::Source #{@name}>"
         | 
| 56 21 | 
             
                end
         | 
| 57 22 |  | 
| 58 23 | 
             
                # Returns the RMagick image instance representing the source.
         | 
| @@ -60,7 +25,10 @@ module Montage | |
| 60 25 | 
             
                # @return [Magick::Image]
         | 
| 61 26 | 
             
                #
         | 
| 62 27 | 
             
                def image
         | 
| 63 | 
            -
                   | 
| 28 | 
            +
                  raise MissingSource, "Couldn't find the source file `#{@path}'" \
         | 
| 29 | 
            +
                    unless @path.file?
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  @image ||= Magick::Image.read(@path).first
         | 
| 64 32 | 
             
                end
         | 
| 65 33 |  | 
| 66 34 | 
             
                # Returns a digest which represents the sprite name and file contents.
         | 
| @@ -68,7 +36,10 @@ module Montage | |
| 68 36 | 
             
                # @return [String]
         | 
| 69 37 | 
             
                #
         | 
| 70 38 | 
             
                def digest
         | 
| 71 | 
            -
                   | 
| 39 | 
            +
                  raise MissingSource, "Couldn't find the source file `#{@path}'" \
         | 
| 40 | 
            +
                    unless @path.file?
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  Digest::SHA256.hexdigest(@name + Digest::SHA256.file(@path).to_s)
         | 
| 72 43 | 
             
                end
         | 
| 73 44 |  | 
| 74 45 | 
             
              end # Source
         |