Ruby中非常便宜的命令行选项解析


114

编辑:拜托,拜托,在回复之前,请先阅读此帖子底部列出的两个要求。人们一直在发布他们的新宝石和资源库,而这些显然不符合要求。

有时,我想非常便宜地将一些命令行选项修改成一个简单的脚本。一个有趣的方法,无需处理getopts或解析或类似的事情,是:

...
$quiet       = ARGV.delete('-d')
$interactive = ARGV.delete('-i')
...
# Deal with ARGV as usual here, maybe using ARGF or whatever.

它不是普通的Unix选项语法,因为它将接受选项非选项命令行参数,例如“ myprog -i foo bar -q”,但是我可以接受。(某些人,例如Subversion开发人员,更喜欢这样做。有时我也这样做。)

仅存在或不存在的选项无法比上述实现简单得多。(一个赋值,一个函数调用,一个副作用。)是否有同样简单的方法来处理带有参数的选项,例如“ -f filename ”?

编辑:

我之前没有提过一点,因为直到Trollop的作者提到该库适合“在一个[800行]文件中”时,我才意识到这一点,因为我不仅在寻找干净的东西。语法,但具有以下特征的技术:

  1. 整个代码都可以包含在脚本文件中(而不会占用实际的脚本本身,可能只有几十行),因此,可以在bin具有标准Ruby 1.8的任何系统上将单个文件放入目录中。[5-7]安装并使用它。如果您无法编写没有require语句的Ruby脚本,并且解析几个选项的代码不到12行,那么您将无法满足此要求。

  2. 该代码小而简单,以至于人们可以记住足够多的代码以直接键入将完成此操作的代码,而不是从其他位置进行剪切和粘贴。考虑一下您处于无法访问Internet的防火墙服务器的控制台上的情况,并且希望将一个快速的脚本放在一起供客户端使用。我不了解您,但是(除未达到上述要求之外)我什至不打算记住45行简化的微光学显微镜。


2
只是对反对getoptlong的异议感到好奇吗?
Mark Carey 2009年

它的冗长。使用getoptlog,有时选项解析代码比实际执行脚本的部分更长。这不仅是美学问题,而且是维护成本问题。
cjs

8
我不明白的脚本包含要求-既getoptlongoptparse在标准Ruby库,这样你就不需要在部署脚本时复制它们-如果红宝石的作品那台机器上,然后require 'optparse'require 'getoptlong'将工作过。
猖ion

请参阅stackoverflow.com/questions/21357953/…,以及威廉姆·摩根(William Morgan)在下面有关Trollop的答案。
fearless_fool 2014年

@CurtSampson我不敢相信有多少人没有回答你的问题。无论哪种方式,最终都能很好地回答XD XD上的3个帖子
OneChillDude

Answers:


235

作为Trollop的作者,我不能相信人们认为在选项分析器中合理的内容。说真的 它使心灵感到困惑。

为什么我必须制作一个扩展其他模块以解析选项的模块?为什么我必须继承任何东西?为什么我必须订阅一些“框架”才能解析命令行?

这是上面的Trollop版本:

opts = Trollop::options do
  opt :quiet, "Use minimal output", :short => 'q'
  opt :interactive, "Be interactive"
  opt :filename, "File to process", :type => String
end

就是这样。opts现在是带有键的哈希:quiet:interactive:filename。您可以用它做任何您想做的事。然后,您会得到一个漂亮的帮助页面,该页面的格式适合您的屏幕宽度,自动短参数名称,类型检查...您需要的一切。

这是一个文件,因此如果您不需要正式的依赖关系,可以将其放在lib /目录中。它具有易于拾取的最小DSL。

每个选项人员的LOC。这很重要。


39
顺便说一句,+ 1是写了Trollop(这里已经提到过),但是可以随意调低第一段的内容。
cjs

33
恐怕在这种情况下,他有权提出申诉。当您查看以下选择:[1 ] [2 ] [3 ]时,对于基本上只处理一个简单的字符串数组的东西(不是的,让它沉入),您不禁要问为什么?你从所有的膨胀中得到什么?这不是C,字符串是“有问题的”。当然要每个人自己。:)
srcspider

50
请不要对此轻声一点。兄弟,这是一个正义的冗长的句子。
威廉·皮耶特里

7
随意调低第十个单词。
安德鲁·格林

3
特罗洛普+1。我将其用于我的测试自动化系统,并且可以正常工作。另外,编写代码非常容易,有时我会重新排列横幅以体验它的乐趣。
kinofrost

76

我同意您的意见require 'getopts',主要是因为它的出色表现OptionParser

% cat temp.rb                                                            
require 'optparse'
OptionParser.new do |o|
  o.on('-d') { |b| $quiet = b }
  o.on('-i') { |b| $interactive = b }
  o.on('-f FILENAME') { |filename| $filename = filename }
  o.on('-h') { puts o; exit }
  o.parse!
end
p :quiet => $quiet, :interactive => $interactive, :filename => $filename
% ruby temp.rb                                                           
{:interactive=>nil, :filename=>nil, :quiet=>nil}
% ruby temp.rb -h                                                        
Usage: temp [options]
    -d
    -i
    -f FILENAME
    -h
% ruby temp.rb -d                                                        
{:interactive=>nil, :filename=>nil, :quiet=>true}
% ruby temp.rb -i                                                        
{:interactive=>true, :filename=>nil, :quiet=>nil}
% ruby temp.rb -di                                                       
{:interactive=>true, :filename=>nil, :quiet=>true}
% ruby temp.rb -dif apelad                                               
{:interactive=>true, :filename=>"apelad", :quiet=>true}
% ruby temp.rb -f apelad -i                                              
{:interactive=>true, :filename=>"apelad", :quiet=>nil}

6
谢谢,我看不到这与OP的要求不符,特别是考虑到标准库中的所有内容,而不是安装/提供任何非标准代码的需要
dolzenko 2012年

3
它看起来像trollop版本,只是不需要额外的文件。
Claudiu

59

这是我通常使用的标准技术:

#!/usr/bin/env ruby

def usage(s)
    $stderr.puts(s)
    $stderr.puts("Usage: #{File.basename($0)}: [-l <logfile] [-q] file ...")
    exit(2)
end

$quiet   = false
$logfile = nil

loop { case ARGV[0]
    when '-q' then  ARGV.shift; $quiet = true
    when '-l' then  ARGV.shift; $logfile = ARGV.shift
    when /^-/ then  usage("Unknown option: #{ARGV[0].inspect}")
    else break
end; }

# Program carries on here.
puts("quiet: #{$quiet} logfile: #{$logfile.inspect} args: #{ARGV.inspect}")

3
回答了这个问题,但是伙计,Trollop似乎更容易处理。当预制的轮子更加光滑时,为什么要重新发明轮子呢?
Mikey TK

7
预制的轮子不光滑。请仔细阅读问题,并注意要求。
cjs 2012年

2
+1有时您需要重新发明轮子,因为您不希望或者根本无法使用Trollop之类的其他依赖项。
lzap 2014年

Trollop不需要作为gem安装。您只需将一个文件放到lib文件夹或代码中即可使用,甚至无需触摸rubygems。
2015年

对我来说,我不得不更改when /^-/ then usage("Unknown option: #{ARGV[0].inspect}")when /^-/ then usage("Unknown option: #{ARGV.shift.inspect}")它,否则它将陷入一个无限的使用循环中
Casey 2015年

36

由于似乎没有人提及它,所以标题确实是指廉价的命令行解析,所以为什么不让Ruby解释器为您完成这项工作呢?如果通过-s开关(例如,在shebang中),则将免费获得分配给单字母全局变量的简单易用的开关。这是您使用该开关的示例:

#!/usr/bin/env ruby -s
puts "#$0: Quiet=#$q Interactive=#$i, ARGV=#{ARGV.inspect}"

这是我将其另存为时的输出 ./test chmod+x

$ ./test
./test: Quiet= Interactive=, ARGV=[]
$ ./test -q foo
./test: Quiet=true Interactive=, ARGV=["foo"]
$ ./test -q -i foo bar baz
./test: Quiet=true Interactive=true, ARGV=["foo", "bar", "baz"]
$ ./test -q=very foo
./test: Quiet=very Interactive=, ARGV=["foo"]

看到 ruby -h详细信息,。

一定要便宜得多。如果尝试使用类似的开关,则会引发NameError-:,,因此这里有一些验证。当然,在非切换参数之后不能有任何切换,但是如果您需要一些奇特的东西,则实际上应该使用最小的OptionParser。实际上,令我烦恼的是,在访问未设置的全局变量时,您会收到一条警告(如果启用了它们),但是它仍然是错误的,因此它对于一次性工具和快速操作都很好脚本。

FelipeC在“ 如何在Ruby中进行真正便宜的命令行选项解析 ”注释中指出的一个警告是,您的shell可能不支持3令牌shebang。您可能需要替换/usr/bin/env ruby -w为红宝石的实际路径(例如/usr/local/bin/ruby -w),或者从包装脚本运行它,或执行其他操作。


2
谢谢:)我当然希望他在过去两年中一直没有等待这个答案。
DarkHeart

3
在过去的两年中,我确实一直在等待这个答案。:-)更严重的是,这是我一直在寻找的一种聪明的想法。警告的事情有点烦人,但是我可以想到减轻这种情况的方法。
cjs 2014年

很高兴我可以(最终)帮忙,@CurtSampson,MRI的标志直接从Perl中撕下来,在Perl中,它们通常被免费地用在外壳一线衬里中。如果答案对您仍然有用,请随时接受。:)
bjjb 2014年

1
您不能在Linux的shebang中使用多个参数。/ usr / bin / env:'ruby -s':没有这样的文件或目录
FelipeC

13

我建立了微光学显微镜以满足对简短但易于使用的选项分析器的明显需求。它的语法类似于Trollop,短70行。如果您不需要验证并且可以不用空行,则可以将其缩减为45行。我认为这正是您想要的。

简短示例:

options = Parser.new do |p|
  p.version = "fancy script version 1.0"
  p.option :verbose, "turn on verbose mode"
  p.option :number_of_chairs, "defines how many chairs are in the classroom", :default => 1
  p.option :room_number, "select room number", :default => 2, :value_in_set => [1,2,3,4]
end.process!

-h或调用脚本--help将打印

Usage: micro-optparse-example [options]
    -v, --[no-]verbose               turn on verbose mode
    -n, --number-of-chairs 1         defines how many chairs are in the classroom
    -r, --room-number 2              select room number
    -h, --help                       Show this message
    -V, --version                    Print version

它检查输入的类型是否与默认值相同,并生成短访问者和长访问者,如果给出了无效的参数等,则输出描述性错误消息。

我使用每个选项解析器比较了几个选项解析器,以解决我遇到的问题。您可以使用这些示例和我的摘要来做出有意义的决定。随意在列表中添加更多实现。:)


该库本身看起来可能很棒。但是,将行数与Trollop进行比较并不是不明智的,因为您依赖并要求optparse(给定或采用)1937行。
Telemachus,2012年

6
比较行数绝对是可以的,因为optparse它是默认库,即每个ruby安装都附带了该库。Trollop是第三方库,因此,每次要将其包含在项目中时,都必须导入完整的代码。µ-optparse始终只需要〜70行,因为optparse它已经存在。
弗洛里安·皮尔兹

8

我完全理解为什么您要避免使用optparse-它可能会变得太多。但是有一些作为库提供的“较轻”的解决方案(与OptParse相比),但是很简单,因此值得进行单个gem安装。

例如,查看此OptiFlag示例。只需几行即可处理。根据您的情况量身定制的示例略有删节:

require 'optiflag'

module Whatever extend OptiFlagSet
  flag "f"
  and_process!
end 

ARGV.flags.f # => .. whatever ..

也有大量的定制示例。我记得使用另一个甚至更容易的方法,但是它现在已经使我逃脱了,但是如果我发现它,我会回来并在此处添加评论。


随意编辑您的答案,使其更适合所澄清的问题。
cjs

4

这是我用于真正非常便宜的arg的方法:

def main
  ARGV.each { |a| eval a }
end

main

因此,如果您运行programname foo bar它,它将先调用foo,然后调用bar。它对于一次性脚本很方便。


3

您可以尝试类似:

if( ARGV.include( '-f' ) )
  file = ARGV[ARGV.indexof( '-f' ) + 1 )]
  ARGV.delete('-f')
  ARGV.delete(file)
end

3

您是否考虑过wycats的Thor?我认为这比optparse干净得多。如果您已经编写了脚本,则可能需要更多的工作来格式化它或对其进行重构,但这确实使处理选项非常简单。

这是自述文件中的示例片段:

class MyApp < Thor                                                # [1]
  map "-L" => :list                                               # [2]

  desc "install APP_NAME", "install one of the available apps"    # [3]
  method_options :force => :boolean, :alias => :optional          # [4]
  def install(name)
    user_alias = options[:alias]
    if options.force?
      # do something
    end
    # ... other code ...
  end

  desc "list [SEARCH]", "list all of the available apps, limited by SEARCH"
  def list(search = "")
    # list everything
  end
end

Thor会这样自动映射命令:

app install myname --force

转换为:

MyApp.new.install("myname")
# with {'force' => true} as options hash
  1. 从Thor继承以将类变成选项映射器
  2. 将其他无效标识符映射到特定方法。在这种情况下,将-L转换为:list
  3. 在下面立即描述方法。第一个参数是使用信息,第二个参数是描述。
  4. 提供任何其他选项。这些将从-和-参数中整理。在这种情况下,将添加--force和-f选项。

我喜欢命令映射的东西,因为我经常会执行带有一堆子命令的单个二进制文件。尽管如此,尽管您已经摆脱了“光”的影响。您能找到一种更简单的方法来表达相同的功能吗?如果您不需要打印--help输出怎么办?如果帮助输出为“ head myprogram.rb”怎么办?
cjs

3

这是我最喜欢的快捷选项解析器:

case ARGV.join
when /-h/
  puts "help message"
  exit
when /-opt1/
  puts "running opt1"
end

这些选项是正则表达式,因此“ -h”也将匹配“ --help”。

可读性强,易于记忆,无需外部库且代码最少。


是的,会的。如果存在问题,则可以添加更多正则表达式,例如/-h(\b|elp)
EdwardTeach '18

2

Trollop非常便宜。


就是< trollop.rubyforge.org >。我想我更喜欢它,尽管我确实不是在寻找图书馆。
cjs

是的,这是一个图书馆。但是,在<800 LOC时,这几乎可以忽略不计。gitorious.org/trollop/mainline/blobs/master/lib/trollop.rb
g33kz0r

1
我当时有点想,如果我要使用一个“库”,也许30至50行会很好。但是话又说回来,我想一旦您有了一个充满代码的单独文件,API设计比行数更重要。不过,我不确定我是否要将它包含在一次性脚本中,而我只想将其放入随机系统的bin目录中。
cjs

1
您已经倒退了:关键是要避免采用更复杂的部署策略。
cjs 2012年

1
您错了,因为您完全误解了(或无视了)提出问题的人的需求和意图。我建议您仔细阅读问题,尤其是最后两点。
cjs 2012年

2

如果您想要一个简单的命令行解析器来执行键/值命令而不使用gem:

但这在您始终具有键/值对时才有效。

# example
# script.rb -u username -p mypass

# check if there are even set of params given
if ARGV.count.odd? 
    puts 'invalid number of arguments'
    exit 1
end

# holds key/value pair of cl params {key1 => value1, key2 => valye2, ...}
opts = {} 

(ARGV.count/2).times do |i|
    k,v = ARGV.shift(2)
    opts[k] = v # create k/v pair
end

# set defaults if no params are given
opts['-u'] ||= 'root'

# example use of opts
puts "username:#{opts['-u']} password:#{opts['-p']}"

如果您不需要任何检查,则可以使用:

opts = {} 

(ARGV.count/2).times do |i|
    k,v = ARGV.shift(2)
    opts[k] = v # create k/v pair
end

2

这是我大多数脚本顶部使用的代码片段:

arghash = Hash.new.tap { |h| # Parse ARGV into a hash
    i = -1                      
    ARGV.map{  |s| /(-[a-zA-Z_-])?([^=]+)?(=)?(.+)?/m.match(s).to_a }
     .each{ |(_,a,b,c,d)| h[ a ? "#{a}#{b}#{c}" : (i+=1) ] =
                             (a ? (c ? "#{d}" : true) : "#{b}#{c}#{d}") 
          }
    [[:argc,Proc.new  {|| h.count{|(k,_)| !k.is_a?(String)}}],
     [:switches, Proc.new {|| h.keys.select{|k| k[0] == '-' }}]
    ].each{|(n,p)| h.define_singleton_method(n,&p) }
}

我也讨厌在我的快速脚本中要求其他文件。我的解决方案几乎是您的要求。我将10行代码片段粘贴到我的任何脚本的顶部,这些脚本将分析命令行并将位置args粘贴并切换到Hash对象(通常分配给在以下示例中称为arghash的对象)。

这是您可能想解析的示例命令行...

./myexampleprog.rb -s -x=15 --longswitch arg1 --longswitch2=val1 arg2

这将成为这样的哈希。

 { 
   '-s' => true, 
   '-x=' => '15', 
   '--longswitch' => true, 
   '--longswitch2=' => 'val1', 
   0 => 'arg1', 
   1 => 'arg2'
 }

除此之外,还为哈希添加了两种便捷方法:

  • argc() 将返回非切换参数的计数。
  • switches() 将返回一个数组,其中包含存在的开关的键

这意味着允许一些快速和肮脏的东西,例如...

  • 验证无论输入(arghash.argc == 2)中的切换如何,我都有正确数量的位置参数
  • 通过位置参数的相对位置访问位置参数,而不管位置参数之前或位置之间是否存在开关(例如,arghash[1]始终获取第二个非开关参数)。
  • 在命令行中支持值分配的开关,例如“ --max = 15”,在arghash['--max=']给出示例命令行的情况下,可以访问该开关产生值“ 15”。
  • 使用非常简单的表示法测试命令行中是否存在开关,例如,arghash['-s']如果存在,则评估为true,如果不存在,则评估为nil。
  • 使用诸如set之类的设置操作测试是否存在开关或其他开关

    puts USAGETEXT if !(%w(-h --help) & arghash.switches()).empty?

  • 通过设置操作来识别无效开关的使用,例如

    puts "Invalid switch found!" if !(arghash.switches - %w(-valid1 -valid2)).empty?

  • 使用简单Hash.merge()的示例(例如下面的示例)指定缺少的参数的默认值,如果未设置-max =,则填充-max =的值;如果未通过,则添加第4个位置参数。

    with_defaults = {'-max=' => 20, 3 => 'default.txt'}.merge(arghash)


(我对此进行了编辑,以清洁地改进代码格式,主要是使用对齐方式使块和控件结构更清晰,这在标点符号如此密集的情况下尤为重要。但是,如果您讨厌新的格式,请放心撤消编辑。)
cjs

即使不是世界上最容易阅读的东西,这也非常不错。我喜欢它还演示了更改参数“语法”(此处,允许在位置args之后使用选项,并且不允许使用选项参数,除非使用=)可以使您所需的代码有所不同。
cjs

感谢重新格式化。绝对是晦涩难懂的,为了清晰起见,可以轻松地交换代码的长度。现在,我或多或少都信任此代码,我将其视为一颗宝石,而且我从不试图弄清楚其幕后工作(因此,有了我的信任,清晰度不再重要)。
大卫·福斯特

1

这与接受的答案非常相似,但是使用的ARGV.delete_if是我在简单解析器中使用的答案。唯一的真正区别是带参数的选项必须在一起(例如-l=file)。

def usage
  "usage: #{File.basename($0)}: [-l=<logfile>] [-q] file ..."
end

$quiet = false
$logfile = nil

ARGV.delete_if do |cur|
  next false if cur[0] != '-'
  case cur
  when '-q'
    $quiet = true
  when /^-l=(.+)$/
    $logfile = $1
  else
    $stderr.puts "Unknown option: #{cur}"
    $stderr.puts usage
    exit 1
  end
end

puts "quiet: #{$quiet} logfile: #{$logfile.inspect} args: #{ARGV.inspect}"

0

显然,@ WilliamMorgan和我也一样。昨晚我刚刚在Github上发布了,在Github上搜索OptionParser之后,我现在看到的是与Trollop类似的库(命名方式?),请参阅开关

有一些区别,但是原理是相同的。一个明显的区别是Switches依赖于OptionParser。


0

我正在开发自己的名为Acclaim的可选分析器gem

我之所以写它,是因为我想创建git样式的命令行界面,并能够将每个命令的功能清晰地分离到单独的类中,但是也可以在没有整个命令框架的情况下使用它:

(options = []) << Acclaim::Option.new(:verbose, '-v', '--verbose')
values = Acclaim::Option::Parser.new(ARGV, options).parse!
puts 'Verbose.' if values.verbose?

到目前为止还没有稳定的版本,但是我已经实现了一些功能,例如:

  • 自定义选项解析器
  • 灵活的选项参数解析,允许最小和可选
  • 支持许多选项样式
  • 替换,附加或引发同一选项的多个实例
  • 自定义选项处理程序
  • 自定义类型处理程序
  • 通用标准库类的预定义处理程序

对命令的强调很多,因此对于简单的命令行解析来说可能有点繁重,但是它运行良好,并且我在所有项目中都使用了它。如果您对命令界面方面感兴趣,请查看项目的GitHub页面以获取更多信息和示例。


1
我强烈推荐Acclaim。它易于使用,并具有您所需的所有选项。
bowsersenior 2012年

0

假设一个命令最多具有一个动作和任意数量的选项,如下所示:

cmd.rb
cmd.rb action
cmd.rb action -a -b ...
cmd.rb action -ab ...

未经验证的解析可能是这样的:

ACTION = ARGV.shift
OPTIONS = ARGV.join.tr('-', '')

if ACTION == '***'
  ...
  if OPTIONS.include? '*'
    ...
  end
  ...
end

0

https://github.com/soveran/clap

other_args = Clap.run ARGV,
  "-s" => lambda { |s| switch = s },
  "-o" => lambda { other = true }

46LOC(在1.0.0版),不依赖于外部选项解析器。完成工作。可能功能不如其他功能强大,但它是46LOC。

如果您检查代码,则可以很容易地复制底层技术-如果您确实不希望使用外部库,则分配lambda并使用Arity确保在标志后跟随适当数量的args。

简单。贱。


编辑:基本概念归根结底,因为我想您可以将其复制/粘贴到脚本中以制作合理的命令行解析器。绝对不是我要记住的东西,但是将lambda arity用作便宜的解析器是一个新主意:

flag = false
option = nil
opts = {
  "--flag" => ->() { flag = true },
  "--option" => ->(v) { option = v }
}

argv = ARGV
args = []

while argv.any?
  item = argv.shift
  flag = opts[item]

  if flag
    raise ArgumentError if argv.size < arity
    flag.call(*argv.shift(arity))
  else
    args << item
  end
end

# ...do stuff...

请阅读问题末尾的第1点。如果您无法在此处的答案中键入所有必需的代码,则不是问题的答案。
cjs

好点子!我认为当时我认为lib足够小,您可以将整个内容复制/粘贴到您正在使用的脚本中,而无需外部依赖,但这绝对不是我要承诺的干净的单行代码完成您的第二点。我确实认为基本概念很新颖而且很酷,因此我继续做一个精简的版本,可以更恰当地回答您的问题。
Ben Alavi

-1

我将分享自己已经工作了一段时间的简单选项解析器。它只有74行代码,它具有Git内部选项解析器的基本功能。我以OptionParser为灵感,也是Git的灵感。

https://gist.github.com/felipec/6772110

看起来像这样:

opts = ParseOpt.new
opts.usage = "git foo"

opts.on("b", "bool", help: "Boolean") do |v|
 $bool = v
end

opts.on("s", "string", help: "String") do |v|
 $str = v
end

opts.on("n", "number", help: "Number") do |v|
 $num = v.to_i
end

opts.parse

您甚至都没有检查代码。我提出了另一个答案,将解析代码取出。
FelipeC

我不需要您说它有74行。但是,我现在才看它,它仍然违反了要求2的第一句话。(此响应还违反了Stack Overflow约定,即您应在答案中包含代码,而不是提供异地链接。)
cjs18年

-1

EasyOptions完全不需要任何选项解析代码。只需编写帮助文本,就可以完成。

## Options:
##   -i, --interactive  Interactive mode
##   -q, --quiet        Silent mode

require 'easyoptions'
unless EasyOptions.options[:quiet]
    puts 'Interactive mode enabled' if EasyOptions.options[:interactive]
    EasyOptions.arguments.each { |item| puts "Argument: #{item}" }
end

EasyOptions是一个没有Require语句的单个Ruby文件,并且根本没有要记住的解析代码。似乎您反而想要一种功能强大但易于记忆的可嵌入的东西。
雷纳托·席尔瓦
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.