apropos 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 108512a68128ab79c042acc053dfdbaa25acbd0a
4
+ data.tar.gz: 992deab384d11673b20ae55c10e783f78c8d03d6
5
+ SHA512:
6
+ metadata.gz: 87339533a21983bf2ef19b408f18864fe3ef345cf3319f39e0097b232b7c703b728e260eef746e3de5e23767812af5523f45f204aa21c7b1cf3c43e80e8ae253
7
+ data.tar.gz: 7d6f80d6f12b3d425c307582750e928df2e932534b6cfcfb8ad6dc7efadae6e0737387016393d159c5d1a8e1e31efacbb914aeffa3f726df927f38c7dd137a8b
data/.gitignore ADDED
@@ -0,0 +1,18 @@
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
18
+ .sass-cache
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 (2013-08-21)
4
+
5
+ Initial release.
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in apropos.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,14 @@
1
+
2
+ Copyright 2013 Square Inc.
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,94 @@
1
+ # Apropos
2
+
3
+ Apropos helps your site serve up the appropriate image for every visitor. Serving multiple versions of an image in responsive and/or localized web sites can be a chore, but Apropos simplifies and automates this task. Instead of manually writing a lot of CSS rules to swap different images, Apropos generates CSS for you based on a simple file naming convention.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'apropos'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install apropos
18
+
19
+ ## Usage
20
+
21
+ Apropos depends on [Compass](http://compass-style.org/), so make sure you have that installed and configured in your project.
22
+
23
+ ### Sample configuration
24
+
25
+ It's easy to get up and running with Apropos' basic configuration. Here's a sample stylesheet:
26
+
27
+ ```sass
28
+ // Put this in a .sass (or .scss) file, such as application.css.sass
29
+
30
+ // Substitute with your own breakpoint names and sizes
31
+ $apropos-breakpoints: (medium, 768px), (large, 1024px)
32
+ @import "apropos"
33
+
34
+ .hero
35
+ // Use hero.jpg as the background of this element, and load any image
36
+ // variants that exist. If you use $generate-height: true, the function
37
+ // will also generate height definitions based on the height of each
38
+ // image (except dpi variants, since you want to display those at the
39
+ // original dimensions).
40
+ +apropos-bg-variants('hero.jpg', $generate-height: true)
41
+
42
+ // Customize other background styles
43
+ background-size: auto 100%
44
+ background-position: 50%
45
+ ```
46
+
47
+ With that configuration set up, you can include any set of variants on your image with a simple file naming convention:
48
+
49
+ # File listing e.g. app/assets/images:
50
+ hero.jpg
51
+ hero.medium.jpg
52
+ hero.large.jpg
53
+ hero.2x.jpg
54
+ hero.2x.medium.jpg
55
+ hero.2x.large.jpg
56
+
57
+ In this example, `hero.jpg` would be your base image, most likely a mobile version. `hero.medium.jpg` would be swapped in at the 768px breakpoint, and `hero.large.jpg` would be swapped in at 1024px. On a high-dpi device, `hero.2x.jpg`, `hero.2x.medium.jpg`, and `hero.2x.large.jpg` would be used instead. Note that the order of the file extensions doesn't matter; `hero.2x.medium.jpg` and `hero.medium.2x.jpg` work exactly the same.
58
+
59
+ ### Customization
60
+
61
+ You can customize Apropos' breakpoints as shown above, and you can also customize the definition of the "high dpi" variant:
62
+
63
+ ```sass
64
+ // The default extension name is "2x", we're overriding to use "hidpi"
65
+ $apropos-hidpi-extension: "hidpi"
66
+ // The default ratio is 1.75 (or 168 dpi), but here we're overriding that
67
+ $apropos-hidpi-query: "(-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi)"
68
+ @import "apropos"
69
+ ```
70
+
71
+ If you want to do more advanced configuration like adding variants for localization, you can [customize Apropos in Ruby](doc-src/customization.md).
72
+
73
+ ## Why use Apropos?
74
+
75
+ There are many tools and techniques for using responsive images. What makes Apropos different? A few key principles:
76
+
77
+ - Let the browser do what it does best. CSS rules are more efficient and reliable than a solution that relies on Javascript or setting cookies for each visitor.
78
+ - Avoid duplicate downloads. Almost all Javascript solutions, including polyfills for things like `srcset`, require unnecessary extra downloads, which CSS classes and media queries avoid.
79
+ - No server logic should be required. Rather than setting a cookie and serving up different assets based on the cookie, we should be able to push compiled CSS and images to a CDN and rely on the browser to request the right images.
80
+ - Take advantage of the "metadata" encoded in file names. We need to create separate assets for high-dpi devices, breakpoints, locales, etc anyway. We can lean on the filesystem with a simple naming convention rather than hand-coding a bunch of CSS.
81
+
82
+ ## Contributing
83
+
84
+ 1. Fork it
85
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
86
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
87
+ 4. Push to the branch (`git push origin my-new-feature`)
88
+ 5. Create new Pull Request
89
+
90
+ Before any changes are merged to master, we need you to sign a very simple
91
+ [Individual Contributor Agreement](https://spreadsheets.google.com/a/squareup.com/spreadsheet/viewform?formkey=dDViT2xzUHAwRkI3X3k5Z0lQM091OGc6MQ&ndplr=1)
92
+ (Google Form).
93
+
94
+ © 2013 Square, Inc.
data/Rakefile ADDED
@@ -0,0 +1,20 @@
1
+ require 'rspec/core/rake_task'
2
+ require 'bundler/gem_tasks'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
7
+
8
+ begin
9
+ require 'cane/rake_task'
10
+
11
+ desc "Run cane to check quality metrics"
12
+ Cane::RakeTask.new(:quality) do |cane|
13
+ cane.abc_max = 10
14
+ cane.style_glob = 'lib/**/*.rb'
15
+ end
16
+
17
+ task :default => :quality
18
+ rescue LoadError
19
+ warn "cane not available, quality task not provided."
20
+ end
data/apropos.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'apropos/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "apropos"
8
+ spec.version = Apropos::VERSION
9
+ spec.authors = ["Gabriel Gilder"]
10
+ spec.email = ["gabriel@squareup.com"]
11
+ spec.description = %q{Apropos helps your site serve up the appropriate image for every visitor. Serving multiple versions of an image in responsive and/or localized web sites can be a chore, but Apropos simplifies and automates this task. Instead of manually writing a lot of CSS rules to swap different images, Apropos generates CSS for you based on a simple file naming convention.}
12
+ spec.summary = %q{Apropos helps your site serve up the appropriate image for every visitor.}
13
+ spec.homepage = "https://github.com/square/apropos"
14
+ spec.license = "Apache 2.0"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "compass"
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.3"
24
+ spec.add_development_dependency "rake"
25
+ spec.add_development_dependency "rspec", "~> 2.13"
26
+ spec.add_development_dependency "cane"
27
+ spec.add_development_dependency "simplecov"
28
+ end
@@ -0,0 +1,56 @@
1
+ # Apropos
2
+
3
+ ## Advanced Customization
4
+
5
+ If you want to go beyond breakpoint and resolution variants, you can use Apropos' Ruby interface to customize it for your app.
6
+
7
+ Your customization code should go in a initializer file or in your Compass config file.
8
+
9
+ ### Example: localized images
10
+
11
+ This example creates a variant that will recognize images for different languages. We assume that your app adds a class such as "lang-en" to the body to indicate the language of the page. With this code, you could have a base file "image.jpg" and variants such as "image.fr.jpg" and "image.ja.jpg" for different languages.
12
+
13
+ And of course, this works in combination with other variants, so if you're using Apropos' hidpi and breakpoints you could have files like "image.medium.2x.fr.jpg" and all the proper rules would be generated.
14
+
15
+ ```ruby
16
+ # This would be in your Compass config.rb or in a Rails initializer.
17
+ # You may need to add `require 'apropos'` depending on load order in your app.
18
+
19
+ SUPPORTED_LANGUAGES = ['en', 'fr', 'ja']
20
+
21
+ # Use a broad regex to match the file extension...
22
+ Apropos.add_class_image_variant(/^[a-z]{2}$/) do |match|
23
+ # ... but validate it against our app's supported languages
24
+ if SUPPORTED_LANGUAGES.include? match[0]
25
+ ".lang-#{match[0]}"
26
+ end
27
+ end
28
+ ```
29
+
30
+ ### Example: country + language
31
+
32
+ Here's a more complex example where we recognize simple locale identifiers that encode country as well as language. We also recognize just the language code, or just the country code. This means you could have images like "image.ca.jpg", "image.fr-ca.jpg", "image.fr.jpg", etc...
33
+
34
+ ```ruby
35
+ SUPPORTED_COUNTRIES = ['us', 'ca', 'fr']
36
+ SUPPORTED_LANGUAGES = ['en', 'fr']
37
+
38
+ Apropos.add_class_image_variant(/^([a-z]{2})(-[a-z]{2})?$/) do |match|
39
+ lang_or_country = match[1]
40
+ # Strip off the dash
41
+ country = match[2][1..-1] if match[2]
42
+ if country
43
+ if SUPPORTED_COUNTRIES.include?(country) && SUPPORTED_LANGUAGES.include?(lang_or_country)
44
+ # Return a class like ".locale-fr-ca"
45
+ ".locale-#{lang_or_country}-#{country}"
46
+ end
47
+ else
48
+ # Determine if the two-letter code is a country, language, or both (like "fr")
49
+ classes = []
50
+ classes << ".lang-#{lang_or_country}" if SUPPORTED_LANGUAGES.include? lang_or_country
51
+ classes << ".country-#{lang_or_country}" if SUPPORTED_COUNTRIES.include? lang_or_country
52
+ # Return nil if the code is not a supported language or country
53
+ classes unless classes.empty?
54
+ end
55
+ end
56
+ ```
data/lib/apropos.rb ADDED
@@ -0,0 +1,16 @@
1
+ require 'compass'
2
+
3
+ module Apropos
4
+ SEPARATOR = '.'
5
+ STYLESHEETS_DIR = File.expand_path('../../stylesheets', __FILE__)
6
+ end
7
+
8
+ Dir.glob(File.expand_path('../apropos/*.rb', __FILE__), &method(:require))
9
+
10
+ module Sass::Script::Functions
11
+ include Apropos::SassFunctions
12
+ end
13
+
14
+ Compass::Frameworks.register('apropos', {
15
+ :stylesheets_directory => Apropos::STYLESHEETS_DIR
16
+ })
@@ -0,0 +1,26 @@
1
+ module Apropos
2
+ # ClassList wraps a list of CSS class selectors with several abilities:
3
+ # - Can be combined with other ClassLists
4
+ # - Can be compared to MediaQuery or ClassList objects via #sort_value, #type
5
+ # - Can be converted to CSS output
6
+ class ClassList
7
+ attr_reader :list, :sort_value
8
+
9
+ def initialize(list, sort_value=0)
10
+ @list = list
11
+ @sort_value = sort_value
12
+ end
13
+
14
+ def combine(other)
15
+ self.class.new(list + other.list)
16
+ end
17
+
18
+ def to_css
19
+ list.join(', ')
20
+ end
21
+
22
+ def type
23
+ "class"
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,35 @@
1
+ module Apropos
2
+ # ExtensionParser manages registered variant parsers and provides a base
3
+ # class which new parsers subclass. Parsers are initialized with a pattern
4
+ # (String or Regexp) and a block that is called to generate ClassList or
5
+ # MediaQuery objects from the provided match data.
6
+ class ExtensionParser
7
+ @parsers = {}
8
+
9
+ def self.parsers
10
+ @parsers
11
+ end
12
+
13
+ def self.add_parser(extension, &block)
14
+ @parsers[extension] = new(extension, &block)
15
+ end
16
+
17
+ def self.each_parser(&block)
18
+ parsers.values.each(&block)
19
+ end
20
+
21
+ attr_reader :pattern
22
+
23
+ def initialize(pattern, &block)
24
+ @pattern = pattern
25
+ @match_block = block
26
+ end
27
+
28
+ def match(extension)
29
+ matchdata = pattern.match(extension)
30
+ if matchdata
31
+ @match_block.call(matchdata)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,68 @@
1
+ # The Apropos module provides several functions for configuration and for
2
+ # supplying rules to the Sass functions. See the README for configuration
3
+ # examples.
4
+ #
5
+ # It also provides convenience functions used by the Sass functions.
6
+ module Apropos
7
+ module_function
8
+
9
+ def image_variant_rules(path)
10
+ set = Set.new(path, images_dir)
11
+ set.variants.select(&:valid?).map do |variant|
12
+ variant.rule
13
+ end
14
+ end
15
+
16
+ def add_dpi_image_variant(id, query, order=0)
17
+ ExtensionParser.add_parser(id) do |match|
18
+ MediaQuery.new(query, order)
19
+ end
20
+ end
21
+
22
+ def add_breakpoint_image_variant(id, query, order=0)
23
+ ExtensionParser.add_parser(id) do |match|
24
+ MediaQuery.new(query, order)
25
+ end
26
+ end
27
+
28
+ def add_class_image_variant(id, class_list=[], order=0, &block)
29
+ parser = if block_given?
30
+ lambda do |match|
31
+ result = block.call(match)
32
+ create_class_rule(result) if result
33
+ end
34
+ else
35
+ lambda do |match|
36
+ create_class_rule(class_list, order)
37
+ end
38
+ end
39
+
40
+ ExtensionParser.add_parser(id, &parser)
41
+ end
42
+
43
+ def create_class_rule(class_list, order=0)
44
+ list = Array(class_list).map {|name| name[0] == '.' ? name : ".#{name}"}
45
+ ClassList.new(list, order)
46
+ end
47
+
48
+ def clear_image_variants
49
+ ExtensionParser.parsers.clear
50
+ end
51
+
52
+ def images_dir
53
+ config = Compass.configuration
54
+ Pathname.new(config.project_path).join(config.images_dir || '')
55
+ end
56
+
57
+ def convert_to_sass_value(val)
58
+ case val
59
+ when String
60
+ Sass::Script::String.new(val)
61
+ when Array
62
+ converted = val.map {|element| convert_to_sass_value(element) }
63
+ Sass::Script::List.new(converted, :space)
64
+ else
65
+ raise "convert_to_sass_value doesn't understand type #{val.class.inspect}"
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,41 @@
1
+ module Apropos
2
+ # MediaQuery wraps a media query string with several features:
3
+ # - Parenthesizes queries when necessary
4
+ # - Can be combined with other MediaQuery objects
5
+ # - Can be compared to ClassList or MediaQuery objects via #type, #sort_value
6
+ # - Can be converted to CSS output
7
+ class MediaQuery
8
+ attr_reader :query_list, :sort_value
9
+
10
+ def initialize(query_string, sort_value=0)
11
+ @query_list = query_string.split(',').map { |q| parenthesize(q.strip) }
12
+ @sort_value = sort_value
13
+ end
14
+
15
+ def parenthesize(query)
16
+ unless query =~ /^\(.+\)$/
17
+ "(#{query})"
18
+ else
19
+ query
20
+ end
21
+ end
22
+
23
+ def combine(other)
24
+ other_ql = other.query_list
25
+ combo = query_list.map do |q|
26
+ other_ql.map do |q2|
27
+ "#{q} and #{q2}"
28
+ end
29
+ end.flatten
30
+ self.class.new(combo.join(', '))
31
+ end
32
+
33
+ def to_css
34
+ query_list.join(", ")
35
+ end
36
+
37
+ def type
38
+ "media"
39
+ end
40
+ end
41
+ end