├── .gitignore ├── Gemfile ├── Gemfile.lock ├── Rakefile ├── LICENSE ├── README.md └── font /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gem 'launchy' 3 | gem 'rainbow' 4 | gem 'json' -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | addressable (2.3.5) 5 | json (1.8.0) 6 | launchy (2.3.0) 7 | addressable (~> 2.3) 8 | rainbow (1.1.4) 9 | 10 | PLATFORMS 11 | ruby 12 | 13 | DEPENDENCIES 14 | json 15 | launchy 16 | rainbow 17 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | task :default => [:install] 2 | 3 | def install_path 4 | (ENV['PREFIX'] || '/usr/local/bin') + '/font' 5 | end 6 | 7 | task :install do |t| 8 | `gem install bundler` 9 | `bundle` 10 | 11 | FileUtils.cp "font", install_path 12 | end 13 | 14 | task :uninstall do |t| 15 | FileUtils.rm [install_path] 16 | end -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Alyssa Ross and other contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | font 2 | ==== 3 | 4 | A command-line font manager for Google Web Fonts. 5 | 6 | Installing 7 | ---------- 8 | 9 | ```sh 10 | $ git clone https://github.com/alyssais/font.git 11 | $ cd font/ 12 | $ sudo rake # May not need to sudo, but it can't hurt. 13 | ``` 14 | 15 | Uninstalling 16 | ------------ 17 | 18 | ```sh 19 | $ rake uninstall 20 | ``` 21 | 22 | Usage 23 | ----- 24 | 25 | ### Show all available fonts 26 | 27 | ```sh 28 | $ font list 29 | ``` 30 | 31 | You can also show the fonts you currently have installed: 32 | 33 | ```sh 34 | $ font list installed 35 | ``` 36 | 37 | Or fonts that are currently not installed. 38 | 39 | ```sh 40 | $ font list not_installed 41 | ``` 42 | 43 | ### Show information for a particular font 44 | 45 | ```sh 46 | $ font show "Droid Sans" 47 | ``` 48 | 49 | ### Preview a font in a web browser 50 | 51 | ```sh 52 | $ font preview "Droid Sans" 53 | ``` 54 | 55 | ### Install fonts 56 | 57 | Fonts will be installed at `~/Library/Fonts` by default, but this can be changed by setting the `FONT_PATH` environment variable. 58 | 59 | The default font path is compatible with OS X. 60 | 61 | ```sh 62 | $ font install "Droid Sans" 63 | ``` 64 | 65 | You can specify a variant (available variants are shown with `$ font show `). 66 | 67 | ```sh 68 | $ font install "Droid Sans" 700 69 | ``` 70 | 71 | You can also install all fonts. (May take a while). 72 | 73 | ```sh 74 | $ font install all 75 | ``` 76 | 77 | ### Uninstall fonts 78 | 79 | ```sh 80 | $ font uninstall "Droid Sans" 81 | ``` 82 | 83 | You can specify a variant (available variants are shown with `$ font show `). 84 | 85 | ```sh 86 | $ font uninstall "Droid Sans" 700 87 | ``` 88 | 89 | You can also uninstall all fonts. 90 | 91 | ```sh 92 | $ font uninstall all 93 | ``` 94 | -------------------------------------------------------------------------------- /font: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | $VERSION = '1.0' 4 | 5 | require 'rubygems' 6 | require 'open-uri' 7 | require 'json' 8 | require 'launchy' 9 | require 'rainbow' 10 | 11 | class String 12 | # Convert CamelCase to under_scores. 13 | # Stolen from ActiveSupport 14 | def underscore 15 | self.gsub(/::/, '/'). 16 | gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). 17 | gsub(/([a-z\d])([A-Z])/,'\1_\2'). 18 | tr("-", "_"). 19 | downcase 20 | end 21 | 22 | def underscore! 23 | self.gsub!(/::/, '/'). 24 | gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). 25 | gsub!(/([a-z\d])([A-Z])/,'\1_\2'). 26 | tr!("-", "_"). 27 | downcase! 28 | end 29 | end 30 | 31 | class Font 32 | FONT_LIST_URL = "https://www.googleapis.com/webfonts/v1/webfonts?key=AIzaSyBsSKrux6m0tgiEnmy8r7_pjr0g3CAzo8E" 33 | 34 | @kind 35 | @family 36 | @variants 37 | @subsets 38 | @version 39 | @last_modified 40 | @files 41 | 42 | # Returns an Array with a Font object for every font in the repository. 43 | def self.all 44 | JSON.parse(open(FONT_LIST_URL).read)["items"].map { |hash| Font.new(hash) } 45 | end 46 | 47 | # Returns the font with a given family name 48 | def self.find(family) 49 | all.find { |font| font.family.downcase == family.downcase } 50 | end 51 | 52 | def self.installed 53 | all.find_all &:installed? 54 | end 55 | 56 | def self.not_installed 57 | all.find_all { |font| !font.installed? } 58 | end 59 | 60 | # The following method was taken from http://pullmonkey.com/2008/01/06/convert-a-ruby-hash-into-a-class-object/ 61 | def initialize(hash) 62 | hash.each do |k,v| 63 | k = k.to_s.underscore 64 | self.instance_variable_set("@#{k}", v) ## create and initialize an instance variable for this key/value pair 65 | self.class.send(:define_method, k, proc{self.instance_variable_get("@#{k}")}) ## create the getter that returns the instance variable 66 | self.class.send(:define_method, "#{k}=", proc{|v| self.instance_variable_set("@#{k}", v)}) ## create the setter that sets the instance variable 67 | end 68 | end 69 | 70 | # Install a font 71 | # If no weight is specified, all will be installed. 72 | def install(variants = []) 73 | variants = [*variants] 74 | 75 | variants.each do |variant| 76 | unless installed?(variant) 77 | 78 | puts "Installing #{@family} (#{variant.capitalize})." 79 | 80 | url = @files[variant] 81 | 82 | File.open(installed_file_path(variant), "wb") do |file| 83 | file.write open(url).read 84 | end 85 | 86 | puts "#{@family} (#{variant.capitalize}) was successfully installed." 87 | 88 | end 89 | end 90 | 91 | install_all_variants if variants.empty? 92 | end 93 | 94 | def install_all_variants 95 | @variants.each { |variant| install(variant) } 96 | end 97 | 98 | def uninstall(variants = []) 99 | variants = [*variants] 100 | 101 | variants.each do |variant| 102 | if installed?(variant) 103 | 104 | puts "Uninstalling #{@family} (#{variant.capitalize})." 105 | path = installed_file_path(variant) 106 | 107 | File.delete(path) if File.exist?(path) 108 | 109 | puts "#{@family} (#{variant.capitalize}) was successfully uninstalled." 110 | 111 | end 112 | end 113 | 114 | uninstall_all_variants if variants.empty? 115 | end 116 | 117 | def uninstall_all_variants 118 | @variants.each { |variant| uninstall(variant) } 119 | end 120 | 121 | def installed?(variant = nil) 122 | if variant 123 | File.exist? installed_file_path(variant) 124 | else 125 | any_variant_installed? 126 | end 127 | end 128 | 129 | # Make a pretty display 130 | def display 131 | puts 132 | display_heading family 133 | 134 | display_collection :variants do |variant| 135 | print "#{"*".color(:white)} #{variant.capitalize}" 136 | print " (installed)".color(:green) if installed?(variant) 137 | puts 138 | end 139 | 140 | display_collection :subsets 141 | puts "Last modified: #{last_modified}" 142 | puts 143 | end 144 | 145 | # Open a web browser to preview the font 146 | def preview 147 | Launchy.open "http://www.google.com/fonts/specimen/" + URI.encode(@family) + "#_charset" 148 | end 149 | 150 | private 151 | 152 | def any_variant_installed? 153 | @variants.each do |variant| 154 | return true if installed?(variant) 155 | end 156 | 157 | return false 158 | end 159 | 160 | # Returns the extenstion, including a leading . 161 | def extension(variant = nil) 162 | url = variant ? @files[variant] : @files.first 163 | File.extname(URI.parse(url).path) 164 | end 165 | 166 | def installed_file_name(variant) 167 | "#{@family}-#{@version}-#{variant.capitalize}#{extension(variant)}" 168 | end 169 | 170 | def installed_file_path(variant) 171 | File.expand_path((ENV["FONT_PATH"] || "~/Library/Fonts/") + installed_file_name(variant)) 172 | end 173 | 174 | def display_heading(name) 175 | puts name.color(:white) 176 | puts "=" * name.length 177 | puts 178 | end 179 | 180 | def display_collection(name, &block) 181 | name = name.to_s # could be a symbol. 182 | puts name.capitalize.color(:white) 183 | puts "-" * name.length 184 | collection = instance_variable_get("@#{name}") 185 | 186 | if block_given? 187 | collection.each(&block) 188 | else 189 | collection.each { |item| puts "#{"*".color(:white)} #{item.capitalize}" } 190 | end 191 | 192 | puts 193 | end 194 | end 195 | 196 | case ARGV[0] 197 | when "install" 198 | if ARGV[1] == "all" 199 | Font.all.each &:install 200 | else 201 | weights = ARGV[2..-1].map &:downcase 202 | Font.find(ARGV[1]).install(weights) 203 | end 204 | when "uninstall" 205 | if ARGV[1] == "all" 206 | Font.all.each &:uninstall 207 | else 208 | weights = ARGV[2..-1].map &:downcase 209 | Font.find(ARGV[1]).uninstall(weights) 210 | end 211 | when "list" 212 | fonts = nil 213 | show_installed = false 214 | 215 | case ARGV[1] 216 | when "installed" 217 | fonts = Font.installed 218 | when "not_installed" 219 | fonts = Font.not_installed 220 | else 221 | show_installed = true 222 | fonts = Font.all 223 | end 224 | 225 | if fonts.empty? 226 | puts "No Google fonts are installed." 227 | else 228 | fonts.each do |font| 229 | print font.family 230 | print " (installed)".color(:green) if show_installed && font.installed? 231 | puts 232 | end 233 | end 234 | when "show" 235 | Font.find(ARGV[1]).display 236 | when "preview" 237 | Font.find(ARGV[1]).preview 238 | when "-v", "--version" 239 | puts $VERSION 240 | end --------------------------------------------------------------------------------