devscript: add `cask_namer`
This script implements naming rules for App-based Casks as currently documented. After some real-world testing, this logic should be merged into `brew cask create`. This commit adds `doc/CASK_NAMING_REFERENCE.md`, and reduces `CONTRIBUTING.md` by 422 words.
This commit is contained in:
parent
6b5fee0e7e
commit
99ecfc3651
123
CONTRIBUTING.md
123
CONTRIBUTING.md
|
@ -56,18 +56,43 @@ class Vagrant < Cask
|
|||
end
|
||||
```
|
||||
|
||||
### Naming the Cask
|
||||
|
||||
We try to maintain consistent naming for the benefit of our users.
|
||||
|
||||
The Cask **name** is the string people will use to interact with the Cask
|
||||
via `brew cask install`, `brew cask search`, etc. The Cask **file**
|
||||
is simply the Cask name with the extension `.rb` appended.
|
||||
|
||||
The easiest way to name a Cask is to run this command:
|
||||
```bash
|
||||
$ "$(brew --prefix)/Library/Taps/phinze-cask/developer/bin/cask_namer" '/full/path/to/new/software.app'
|
||||
```
|
||||
|
||||
If the software you wish to Cask is not installed, or does not have an
|
||||
associated App bundle, just give the full proper name of the software
|
||||
instead of a pathname:
|
||||
```bash
|
||||
$ "$(brew --prefix)/Library/Taps/phinze-cask/developer/bin/cask_namer" 'Google Chrome'
|
||||
```
|
||||
|
||||
If the `cask_namer` script does not work for you, see [Cask Naming Details](#cask-naming-details).
|
||||
|
||||
|
||||
### The `brew cask create` Command
|
||||
|
||||
To get started, use the handy dandy `brew cask create` command.
|
||||
Once you know the name for your Cask, create it with the handy-dandy
|
||||
`brew cask create` command.
|
||||
|
||||
```bash
|
||||
$ brew cask create my-new-cask
|
||||
```
|
||||
|
||||
This will open `$EDITOR` with a template for your new Cask. Note that the
|
||||
convention is that hyphens in the name indicate casing in the class name, so
|
||||
the Cask name 'my-new-cask' becomes `MyNewCask` stored in `my-new-cask.rb`. So
|
||||
running the above command will get you a template that looks like this:
|
||||
This will open `$EDITOR` with a template for your new Cask. Hyphens in the
|
||||
Cask name indicate case-changes in the class name, so the Cask name
|
||||
'my-new-cask' becomes class `MyNewCask` stored in file `my-new-cask.rb`.
|
||||
Running the `create` command above will get you a template that looks like
|
||||
this:
|
||||
|
||||
```ruby
|
||||
class MyNewCask < Cask
|
||||
|
@ -141,91 +166,17 @@ When possible, it is best to use a download URL from the original developer
|
|||
or vendor, rather than an aggregator such as macupdate.com.
|
||||
|
||||
|
||||
### Naming Casks
|
||||
### Cask Naming Details
|
||||
|
||||
We try to maintain consistent naming so everything stays clean and predictable.
|
||||
If a Cask name conflicts with an already-existing Cask, authors should manually
|
||||
make the new Cask name unique by prepending the vendor name. Example:
|
||||
[unison.rb](../Casks/unison.rb) and [panic-unison.rb](../Casks/panic-unison.rb).
|
||||
|
||||
#### Find the Canonical Name of the Author's Distribution
|
||||
If possible, avoid creating Cask names which differ only by the placement of
|
||||
hyphens.
|
||||
|
||||
##### Canonical Names of Apps
|
||||
To name a Cask manually, or to learn about exceptions for unusual cases, see [CASK_NAMING_REFERENCE.md](doc/CASK_NAMING_REFERENCE.md).
|
||||
|
||||
* Start with the exact name of the Application bundle as it appears on disk,
|
||||
such as `Google Chrome.app`
|
||||
* Remove `.app` from the end
|
||||
* Translate the name into English if necessary
|
||||
* Remove from the end: version numbers or incremental release designations such
|
||||
as "alpha", "beta", or "release candidate". Strings which distinguish different
|
||||
capabilities or codebases such as "Community Edition" are currently accepted.
|
||||
Exception: when a number is not an incremental release counter, but a
|
||||
differentiator for a different product from a different vendor: [iterm2.rb](../Casks/iterm2.rb).
|
||||
* If the version number is arranged to occur in the middle of the App name,
|
||||
it should also be removed. Example: [IntelliJ IDEA 13 CE.app](../Casks/intellij-idea-ce.rb).
|
||||
* Remove from the end: "mac", "for mac", "for OS X". These terms are generally
|
||||
added to ports such as "MAME OS X.app". Exception: when the software is not
|
||||
a port, but "Mac" is an inseparable part of the name or branding, as in
|
||||
'PlayForMac.app'
|
||||
* Remove from the end: hardware designations such as "for x86", "32-bit", "ppc".
|
||||
* Remove from the end: software framework names such as "Qt", "Gtk", "Wx", "Java", "Oracle JVM", etc.
|
||||
Exception: the framework is the product being Casked: [java.rb](../Casks/java.rb).
|
||||
* Remove from the end: localization strings such as "en-US"
|
||||
* Pay attention to details, for example: `"Git Hub" != "git_hub" != "GitHub"`
|
||||
* If the result of that process is something unhelpful, such as `Macintosh Installer`,
|
||||
then just create the best name you can, based on the author's web page.
|
||||
* If the result conflicts with the name of an existing Cask, make yours unique
|
||||
by prepending the name of the vendor or developer, followed by a separator.
|
||||
Example: [unison.rb](../Casks/unison.rb) and [panic-unison.rb](../Casks/panic-unison.rb).
|
||||
|
||||
##### Canonical Names of `pkg`-based Installers
|
||||
|
||||
* The Canonical Name of a `pkg` may be more tricky to determine than that
|
||||
of an App. If a `pkg` installs an App, then use that App name with the
|
||||
rules above. If not, just create the best name you can, based on the
|
||||
author's web page.
|
||||
|
||||
#### Cask Name
|
||||
|
||||
A "Cask name" is the primary identifier for a package in our project. It's
|
||||
the string people will use to interact with this Cask on their system.
|
||||
|
||||
To get from the App's canonical name to the Cask name:
|
||||
|
||||
* convert all letters to lower case
|
||||
* hyphens stay hyphens
|
||||
* spaces become hyphens
|
||||
* digits stay digits
|
||||
* delete any character which is not alphanumeric or hyphen
|
||||
* collapse a series of multiple hyphens into one hyphen
|
||||
* delete a leading hyphen
|
||||
* a leading digit gets spelled out into English: `1password` becomes `onepassword`
|
||||
|
||||
Casks are stored in a Ruby file matching their name. If possible, avoid creating
|
||||
Cask files which differ only by the placement of hyphens.
|
||||
|
||||
#### Cask Class
|
||||
|
||||
Casks are implemented as Ruby classes, so a Cask's "class" needs to be a
|
||||
valid Ruby class name.
|
||||
|
||||
When going from a Cask's __name__ to its __class name__:
|
||||
|
||||
* UpperCamelCased
|
||||
* wherever a hyphen occurs in the __Cask name__, the __class__ has a case change
|
||||
* invalid characters are replaced with English word equivalents
|
||||
|
||||
|
||||
#### Cask Naming Examples
|
||||
|
||||
These illustrate most of the naming rules:
|
||||
|
||||
Canonical App Name | Cask Name | Cask Class
|
||||
-------------------|---------------------|------------------------
|
||||
Audio Hijack Pro | `audio-hijack-pro` | `AudioHijackPro`
|
||||
VLC | `vlc` | `Vlc`
|
||||
BetterTouchTool | `bettertouchtool` | `Bettertouchtool`
|
||||
iTerm2 | `iterm2` | `Iterm2`
|
||||
Akai LPK25 Editor | `akai-lpk25-editor` | `AkaiLpk25Editor`
|
||||
Sublime Text 3 | `sublime-text3` | `SublimeText3`
|
||||
1Password | `1password` | `Onepassword`
|
||||
|
||||
### Archives With Subfolders
|
||||
|
||||
|
|
|
@ -0,0 +1,441 @@
|
|||
#!/usr/bin/env ruby
|
||||
#
|
||||
# cask_namer
|
||||
#
|
||||
# todo:
|
||||
#
|
||||
# detect Cask files which differ only by the placement of hyphens.
|
||||
#
|
||||
|
||||
###
|
||||
### dependencies
|
||||
###
|
||||
|
||||
require 'pathname'
|
||||
require 'open3'
|
||||
require 'active_support/inflector'
|
||||
|
||||
###
|
||||
### configurable constants
|
||||
###
|
||||
|
||||
NUMBERS = {
|
||||
'0' => 'zero',
|
||||
'1' => 'one',
|
||||
'2' => 'two',
|
||||
'3' => 'three',
|
||||
'4' => 'four',
|
||||
'5' => 'five',
|
||||
'6' => 'six',
|
||||
'7' => 'seven',
|
||||
'8' => 'eight',
|
||||
'9' => 'nine',
|
||||
}
|
||||
|
||||
CASK_FILE_EXTENSION = '.rb'
|
||||
|
||||
# Hardcode App names that cannot be transformed automatically.
|
||||
# Example: in "x48.app", "x48" is not a version number.
|
||||
# The value in the hash should be a valid Cask name.
|
||||
APP_EXCEPTION_PATS = {
|
||||
%r{\Aiterm\Z}i => 'iterm2',
|
||||
%r{\Apgadmin3\Z}i => 'pgadmin3',
|
||||
%r{\Ax48\Z}i => 'x48',
|
||||
%r{\Avitamin-r[\s\d\.]*\Z}i => 'vitamin-r',
|
||||
%r{\Aimagealpha\Z}i => 'imagealpha',
|
||||
%r{\Aplayonmac\Z}i => 'playonmac',
|
||||
%r{\Akismac\Z}i => 'kismac',
|
||||
%r{\Avoicemac\Z}i => 'voicemac',
|
||||
%r{\Acleanmymac[\s\d\.]*\Z}i => 'cleanmymac',
|
||||
}
|
||||
|
||||
# Preserve trailing patterns on App names that could be mistaken
|
||||
# for version numbers, etc
|
||||
PRESERVE_TRAILING_PATS = [
|
||||
%r{id3}i,
|
||||
%r{mp3}i,
|
||||
%r{3[\s-]*d}i,
|
||||
%r{diff3}i,
|
||||
]
|
||||
|
||||
# The code that employs these patterns against App names
|
||||
# - hacks a \b (word-break) between CamelCase and snake_case transitions
|
||||
# - anchors the pattern to end-of-string
|
||||
# - applies the patterns repeatedly until there is no match
|
||||
REMOVE_TRAILING_PATS = [
|
||||
# spaces
|
||||
%r{\s+}i,
|
||||
|
||||
# generic terms
|
||||
%r{\bapp}i,
|
||||
# idea, but never discussed
|
||||
# %r{\blauncher}i,
|
||||
|
||||
# "mac", "for mac", "for OS X".
|
||||
%r{\b(?:for)?[\s-]*mac(?:intosh)?}i,
|
||||
%r{\b(?:for)?[\s-]*os[\s-]*x}i,
|
||||
|
||||
# hardware designations such as "for x86", "32-bit", "ppc"
|
||||
%r{(?:\bfor\s*)?x.?86}i,
|
||||
%r{(?:\bfor\s*)?\bppc}i,
|
||||
%r{(?:\bfor\s*)?\d+.?bits?}i,
|
||||
|
||||
# frameworks
|
||||
%r{\b(?:for)?[\s-]*(?:oracle|apple|sun)*[\s-]*(?:jvm|java|jre)}i,
|
||||
%r{\bgtk}i,
|
||||
%r{\bqt}i,
|
||||
%r{\bwx}i,
|
||||
|
||||
# localizations
|
||||
%r{en\s*-\s*us}i,
|
||||
|
||||
# version numbers
|
||||
%r{[^a-z0-9]+}i,
|
||||
%r{\b(?:version|alpha|beta|gamma|release|release.?candidate)(?:[\s\.\d-]*\d[\s\.\d-]*)?}i,
|
||||
%r{\b(?:v|ver|vsn|r|rc)[\s\.\d-]*\d[\s\.\d-]*}i,
|
||||
%r{\d+(?:[a-z\.]\d+)*}i,
|
||||
%r{\b\d+\s*[a-z]}i,
|
||||
%r{\d+\s*[a-c]}i, # constrained to a-c b/c of false positives
|
||||
]
|
||||
|
||||
# Patterns which are permitted (undisturbed) following an interior version number
|
||||
AFTER_INTERIOR_VERSION_PATS = [
|
||||
%r{ce}i,
|
||||
%r{pro}i,
|
||||
%r{professional}i,
|
||||
%r{client}i,
|
||||
%r{server}i,
|
||||
%r{host}i,
|
||||
%r{viewer}i,
|
||||
%r{launcher}i,
|
||||
%r{installer}i,
|
||||
]
|
||||
|
||||
###
|
||||
### classes
|
||||
###
|
||||
|
||||
class AppName < String
|
||||
def self.remove_trailing_pat
|
||||
@@remove_trailing_pat ||= %r{(?<=.)(?:#{REMOVE_TRAILING_PATS.join('|')})\Z}i
|
||||
end
|
||||
|
||||
def self.preserve_trailing_pat
|
||||
@@preserve_trailing_pat ||= %r{(?:#{PRESERVE_TRAILING_PATS.join('|')})\Z}i
|
||||
end
|
||||
|
||||
def self.after_interior_version_pat
|
||||
@@after_interior_version_pat ||= %r{(?:#{AFTER_INTERIOR_VERSION_PATS.join('|')})}i
|
||||
end
|
||||
|
||||
def english_from_app_bundle
|
||||
return self if self.ascii_only?
|
||||
return self unless File.exist?(self)
|
||||
|
||||
# check Info.plist CFBundleDisplayName
|
||||
bundle_name = Open3.popen3(*%w[
|
||||
/usr/libexec/PlistBuddy -c
|
||||
],
|
||||
'Print CFBundleDisplayName',
|
||||
Pathname.new(self).join('Contents', 'Info.plist').to_s
|
||||
) do |stdin, stdout, stderr|
|
||||
begin
|
||||
stdout.gets.force_encoding("UTF-8").chomp
|
||||
rescue
|
||||
end
|
||||
end
|
||||
return AppName.new(bundle_name) if bundle_name and bundle_name.ascii_only?
|
||||
|
||||
# check Info.plist CFBundleName
|
||||
bundle_name = Open3.popen3(*%w[
|
||||
/usr/libexec/PlistBuddy -c
|
||||
],
|
||||
'Print CFBundleName',
|
||||
Pathname.new(self).join('Contents', 'Info.plist').to_s
|
||||
) do |stdin, stdout, stderr|
|
||||
begin
|
||||
stdout.gets.force_encoding("UTF-8").chomp
|
||||
rescue
|
||||
end
|
||||
end
|
||||
return AppName.new(bundle_name) if bundle_name and bundle_name.ascii_only?
|
||||
|
||||
# check localization strings
|
||||
local_strings_file = Pathname.new(self).join('Contents', 'Resources', 'en.lproj', 'InfoPlist.strings')
|
||||
local_strings_file = Pathname.new(self).join('Contents', 'Resources', 'English.lproj', 'InfoPlist.strings') unless local_strings_file.exist?
|
||||
if local_strings_file.exist?
|
||||
bundle_name = File.open(local_strings_file, 'r:UTF-16LE:UTF-8') do |fh|
|
||||
%r{\ACFBundle(?:Display)?Name\s*=\s*"(.*)";\Z}.match(fh.readlines.grep(/^CFBundle(?:Display)?Name\s*=\s*/).first) do |match|
|
||||
match.captures.first
|
||||
end
|
||||
end
|
||||
return AppName.new(bundle_name) if bundle_name and bundle_name.ascii_only?
|
||||
end
|
||||
|
||||
# check Info.plist CFBundleExecutable
|
||||
bundle_name = Open3.popen3(*%w[
|
||||
/usr/libexec/PlistBuddy -c
|
||||
],
|
||||
'Print CFBundleExecutable',
|
||||
Pathname.new(self).join('Contents', 'Info.plist').to_s
|
||||
) do |stdin, stdout, stderr|
|
||||
begin
|
||||
stdout.gets.force_encoding("UTF-8").chomp
|
||||
rescue
|
||||
end
|
||||
end
|
||||
return AppName.new(bundle_name) if bundle_name and bundle_name.ascii_only?
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
def basename
|
||||
if Pathname.new(self).exist? then
|
||||
AppName.new(Pathname.new(self).basename.to_s)
|
||||
else
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
def remove_extension
|
||||
self.sub(/\.app\Z/i, '')
|
||||
end
|
||||
|
||||
def decompose_to_ascii
|
||||
# crudely (and incorrectly) decompose extended latin characters to ASCII
|
||||
return self if self.ascii_only?
|
||||
AppName.new(self.mb_chars.normalize(:kd).each_char.select(&:ascii_only?).join)
|
||||
end
|
||||
|
||||
def hardcoded_exception
|
||||
APP_EXCEPTION_PATS.each do |regexp, exception|
|
||||
if regexp.match(self) then
|
||||
return AppName.new(exception)
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
def insert_vertical_tabs_for_camel_case
|
||||
app_name = AppName.new(self)
|
||||
if app_name.sub!(/(#{self.class.preserve_trailing_pat})\Z/i, '')
|
||||
trailing = $1
|
||||
end
|
||||
app_name.gsub!(/([^A-Z])([A-Z])/, "\\1\v\\2")
|
||||
app_name.sub!(/\Z/, trailing) if trailing
|
||||
app_name
|
||||
end
|
||||
|
||||
def insert_vertical_tabs_for_snake_case
|
||||
self.gsub(/_/, "\v")
|
||||
end
|
||||
|
||||
def clean_up_vertical_tabs
|
||||
self.gsub(/\v/, '')
|
||||
end
|
||||
|
||||
def remove_interior_versions!
|
||||
# done separately from REMOVE_TRAILING_PATS because this
|
||||
# requires a substitution with a backreference
|
||||
self.sub!(%r{(?<=.)[\.\d]+(#{self.class.after_interior_version_pat})\Z}i, '\1')
|
||||
self.sub!(%r{(?<=.)[\s\.\d-]*\d[\s\.\d-]*(#{self.class.after_interior_version_pat})\Z}i, '-\1')
|
||||
end
|
||||
|
||||
def remove_trailing_strings_and_versions
|
||||
app_name = self.insert_vertical_tabs_for_camel_case
|
||||
.insert_vertical_tabs_for_snake_case
|
||||
while self.class.remove_trailing_pat.match(app_name) and
|
||||
not self.class.preserve_trailing_pat.match(app_name)
|
||||
app_name.sub!(self.class.remove_trailing_pat, '')
|
||||
end
|
||||
app_name.remove_interior_versions!
|
||||
app_name.clean_up_vertical_tabs
|
||||
end
|
||||
|
||||
def canonical
|
||||
return @canonical if @canonical
|
||||
@canonical = self.english_from_app_bundle
|
||||
.basename
|
||||
.decompose_to_ascii
|
||||
.remove_extension
|
||||
name_exception = @canonical.hardcoded_exception
|
||||
@canonical = name_exception ? name_exception : @canonical.remove_trailing_strings_and_versions
|
||||
end
|
||||
end
|
||||
|
||||
class CaskFileName < String
|
||||
def spaces_to_hyphens
|
||||
self.gsub(/ +/, '-')
|
||||
end
|
||||
|
||||
def delete_invalid_chars
|
||||
self.gsub(/[^a-z0-9-]+/, '')
|
||||
end
|
||||
|
||||
def collapse_multiple_hyphens
|
||||
self.gsub(/--+/, '-')
|
||||
end
|
||||
|
||||
def delete_leading_hyphens
|
||||
self.gsub(/^--+/, '')
|
||||
end
|
||||
|
||||
def delete_hyphens_before_numbers
|
||||
self.gsub(/-([0-9])/, '\1')
|
||||
end
|
||||
|
||||
def spell_out_leading_numbers
|
||||
cask_file_name = self
|
||||
NUMBERS.each do |k, v|
|
||||
cask_file_name.sub!(/^#{k}/, v)
|
||||
end
|
||||
cask_file_name
|
||||
end
|
||||
|
||||
def add_extension
|
||||
self.sub(/(?:#{escaped_cask_file_extension})?\Z/i, CASK_FILE_EXTENSION)
|
||||
end
|
||||
|
||||
def remove_extension
|
||||
self.sub(/#{escaped_cask_file_extension}\Z/i, '')
|
||||
end
|
||||
|
||||
def from_canonical_name
|
||||
return @from_canonical_name if @from_canonical_name
|
||||
@from_canonical_name = if APP_EXCEPTION_PATS.rassoc(self.remove_extension)
|
||||
self.remove_extension
|
||||
else
|
||||
self.remove_extension
|
||||
.downcase
|
||||
.spaces_to_hyphens
|
||||
.delete_invalid_chars
|
||||
.collapse_multiple_hyphens
|
||||
.delete_leading_hyphens
|
||||
.delete_hyphens_before_numbers
|
||||
.spell_out_leading_numbers
|
||||
end
|
||||
raise "Could not determine Cask name" unless @from_canonical_name.length > 0
|
||||
@from_canonical_name.add_extension
|
||||
end
|
||||
end
|
||||
|
||||
class CaskClassName < String
|
||||
def basename
|
||||
if Pathname.new(self).exist?
|
||||
CaskClassName.new(Pathname.new(self).basename.to_s)
|
||||
else
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
def remove_extension
|
||||
self.sub(/#{escaped_cask_file_extension}\Z/i, '')
|
||||
end
|
||||
|
||||
def hyphens_to_camel_case
|
||||
self.split('-').map(&:capitalize).join
|
||||
end
|
||||
|
||||
def from_cask_name
|
||||
# or from filename
|
||||
self.basename.remove_extension.hyphens_to_camel_case
|
||||
end
|
||||
end
|
||||
|
||||
###
|
||||
### methods
|
||||
###
|
||||
|
||||
def project_root
|
||||
Dir.chdir File.dirname(File.expand_path(__FILE__))
|
||||
@git_root ||= Open3.popen3(*%w[
|
||||
git rev-parse --show-toplevel
|
||||
]) do |stdin, stdout, stderr|
|
||||
begin
|
||||
Pathname.new(stdout.gets.chomp)
|
||||
rescue
|
||||
raise "could not find project root"
|
||||
end
|
||||
end
|
||||
raise "could not find project root" unless @git_root.exist?
|
||||
@git_root
|
||||
end
|
||||
|
||||
def escaped_cask_file_extension
|
||||
@escaped_cask_file_extension ||= Regexp.escape(CASK_FILE_EXTENSION)
|
||||
end
|
||||
|
||||
def canonical_name
|
||||
@canonical_name ||= AppName.new("#{ARGV.first}".force_encoding("UTF-8")).canonical
|
||||
end
|
||||
|
||||
def cask_file_name
|
||||
@cask_file_name ||= CaskFileName.new(canonical_name).from_canonical_name
|
||||
end
|
||||
|
||||
def cask_name
|
||||
@cask_name ||= cask_file_name.remove_extension
|
||||
end
|
||||
|
||||
def class_name
|
||||
@class_name ||= CaskClassName.new(cask_name).from_cask_name
|
||||
end
|
||||
|
||||
def warnings
|
||||
return @warnings if @warnings
|
||||
@warnings = []
|
||||
unless APP_EXCEPTION_PATS.rassoc(cask_name)
|
||||
if %r{\d}.match(cask_name)
|
||||
@warnings.push "WARNING: '#{cask_name}' contains digits. Digits which are version numbers should be removed."
|
||||
end
|
||||
end
|
||||
filename = project_root.join('Casks', cask_file_name)
|
||||
if filename.exist?
|
||||
@warnings.push "WARNING: the file '#{filename}' already exists. Prepend the vendor name if this is not a duplicate."
|
||||
end
|
||||
@warnings
|
||||
end
|
||||
|
||||
def report
|
||||
puts "Proposed canonical App name: #{canonical_name}" if $debug
|
||||
puts "Proposed Cask name: #{cask_name}"
|
||||
puts "Proposed file name: #{cask_file_name}"
|
||||
puts "First Line of Cask: class #{class_name} < Cask"
|
||||
if warnings.length > 0
|
||||
STDERR.puts "\n"
|
||||
STDERR.puts warnings
|
||||
STDERR.puts "\n"
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
###
|
||||
### main
|
||||
###
|
||||
|
||||
usage = <<-EOS
|
||||
Usage: cask_namer [ -debug ] <application.app>
|
||||
|
||||
Given an Application name or a path to an Application,
|
||||
propose a Cask name, filename and class name.
|
||||
|
||||
With -debug, provide the internal Canonical App Name.
|
||||
|
||||
EOS
|
||||
|
||||
if ARGV.first =~ %r{^-+h(elp)?$}i
|
||||
puts usage
|
||||
exit 0
|
||||
end
|
||||
|
||||
if ARGV.first =~ %r{^-+debug?$}i
|
||||
$debug = 1
|
||||
ARGV.shift
|
||||
end
|
||||
|
||||
unless ARGV.length == 1
|
||||
puts usage
|
||||
exit 1
|
||||
end
|
||||
|
||||
report
|
|
@ -0,0 +1,108 @@
|
|||
# Cask Naming Reference
|
||||
|
||||
This document describes the algorithm implemented in the `cask_namer`
|
||||
script, and covers detailed rules and exceptions which are not needed in
|
||||
most cases.
|
||||
|
||||
* [Find the Canonical Name of the Developer's Distribution](#find-the-canonical-name-of-the-developers-distribution)
|
||||
* [Cask Name](#cask-name)
|
||||
* [Cask Class](#cask-class)
|
||||
* [Cask Naming Examples](#cask-naming-examples)
|
||||
|
||||
## Find the Canonical Name of the Developer's Distribution
|
||||
|
||||
### Canonical Names of Apps
|
||||
|
||||
* Start with the exact name of the Application bundle as it appears on disk,
|
||||
such as `Google Chrome.app`
|
||||
* Translate the name into English if necessary
|
||||
* Remove `.app` from the end
|
||||
* Remove the term "app" from the end, if the developer styles the name like
|
||||
"Software App.app". Exception: if the term "app" describes functionality,
|
||||
as in [rcdefaultapp.rb](../Casks/rcdefaultapp.rb).
|
||||
* Remove from the end: version numbers or incremental release designations such
|
||||
as "alpha", "beta", or "release candidate". Strings which distinguish different
|
||||
capabilities or codebases such as "Community Edition" are currently accepted.
|
||||
Exception: when a number is not an incremental release counter, but a
|
||||
differentiator for a different product from a different vendor: [pgadmin3.rb](../Casks/pgadmin3.rb).
|
||||
* If the version number is arranged to occur in the middle of the App name,
|
||||
it should also be removed. Example: [IntelliJ IDEA 13 CE.app](../Casks/intellij-idea-ce.rb).
|
||||
* Remove from the end: "mac", "for mac", "for OS X". These terms are generally
|
||||
added to ports such as "MAME OS X.app". Exception: when the software is not
|
||||
a port, but "Mac" is an inseparable part of the name or branding, as in
|
||||
'PlayForMac.app'
|
||||
* Remove from the end: hardware designations such as "for x86", "32-bit", "ppc".
|
||||
* Remove from the end: software framework names such as "Qt", "Gtk", "Wx", "Java", "Oracle JVM", etc.
|
||||
Exception: the framework is the product being Casked: [java.rb](../Casks/java.rb).
|
||||
* Remove from the end: localization strings such as "en-US"
|
||||
* Pay attention to details, for example: `"Git Hub" != "git_hub" != "GitHub"`
|
||||
* If the result of that process is something unhelpful, such as `Macintosh Installer`,
|
||||
then just create the best name you can, based on the developer's web page.
|
||||
* If the result conflicts with the name of an existing Cask, make yours unique
|
||||
by prepending the name of the vendor or developer, followed by a separator.
|
||||
Example: [unison.rb](../Casks/unison.rb) and [panic-unison.rb](../Casks/panic-unison.rb).
|
||||
* Inevitably, there are a small number of exceptions not covered by the rules.
|
||||
Don't hesitate to [contact the maintainers](../../../issues) if you have a problem.
|
||||
|
||||
### Canonical Names of `pkg`-based Installers
|
||||
|
||||
* The Canonical Name of a `pkg` may be more tricky to determine than that
|
||||
of an App. If a `pkg` installs an App, then use that App name with the
|
||||
rules above. If not, just create the best name you can, based on the
|
||||
developer's web page.
|
||||
|
||||
### Canonical Names of non-App Software
|
||||
|
||||
* Currently, naming rules are not well-defined for Preference Panes,
|
||||
QuickLook plugins, and other types of software installable by
|
||||
homebrew-cask. Just create the best name you can, based on the filename
|
||||
on disk or the developer's web page. Watch out for duplicates.
|
||||
|
||||
|
||||
## Cask Name
|
||||
|
||||
The "Cask name" is the primary identifier for a package in our project. It's
|
||||
the string people will use to interact with the Cask on their system.
|
||||
|
||||
To get from the App's canonical name to the Cask name:
|
||||
|
||||
* convert all letters to lower case
|
||||
* hyphens stay hyphens
|
||||
* spaces become hyphens
|
||||
* digits stay digits
|
||||
* delete any character which is not alphanumeric or hyphen
|
||||
* collapse a series of multiple hyphens into one hyphen
|
||||
* delete a leading hyphen
|
||||
* a leading digit gets spelled out into English: `1password` becomes `onepassword`
|
||||
|
||||
Casks are stored in a Ruby file matching their name. If possible, avoid creating
|
||||
Cask files which differ only by the placement of hyphens.
|
||||
|
||||
|
||||
## Cask Class
|
||||
|
||||
Casks are implemented as Ruby classes, so a Cask's "class" needs to be a
|
||||
valid Ruby class name.
|
||||
|
||||
When converting a __Cask name__ to its corresponding __class name__:
|
||||
|
||||
* convert to UpperCamelCase
|
||||
* wherever a hyphen occurs in the __Cask name__, remove the hyphen and
|
||||
create a case change in the __class name__
|
||||
|
||||
|
||||
## Cask Naming Examples
|
||||
|
||||
These illustrate most of the naming rules:
|
||||
|
||||
App Name on Disk | Canonical App Name | Cask Name | Cask File | Cask Class
|
||||
-----------------------|--------------------|--------------------|-----------------------|------------------------
|
||||
`Audio Hijack Pro.app` | Audio Hijack Pro | `audio-hijack-pro` | `audio-hijack-pro.rb` | `AudioHijackPro`
|
||||
`VLC.app` | VLC | `vlc` | `vlc.rb` | `Vlc`
|
||||
`BetterTouchTool.app` | BetterTouchTool | `bettertouchtool` | `bettertouchtool.rb` | `Bettertouchtool`
|
||||
`LPK25 Editor.app` | LPK25 Editor | `lpk25-editor` | `lpk25-editor.rb` | `Lpk25Editor`
|
||||
`Sublime Text 2.app` | Sublime Text | `sublime-text` | `sublime-text.rb` | `SublimeText`
|
||||
`1Password.app` | 1Password | `onepassword` | `onepassword.rb` | `Onepassword`
|
||||
|
||||
|
||||
# <3 THANK YOU TO ALL CONTRIBUTORS! <3
|
Loading…
Reference in New Issue