├── Gemfile
├── .gitignore
├── .travis.yml
├── test
├── fixtures
│ ├── test.torrent
│ └── python.torrent
├── bittorrent_test.rb
├── encoding_test.rb
├── utf8_decoding_test.rb
├── shoulda_macros
│ ├── encoding.rb
│ └── decoding.rb
├── benchmark
│ ├── encoding.rb
│ └── decoding.rb
├── environment.rb
└── decoding_test.rb
├── .yardopts
├── .github
└── main.workflow
├── lib
├── bencode
│ ├── core_ext
│ │ ├── object.rb
│ │ ├── io.rb
│ │ ├── integer.rb
│ │ ├── array.rb
│ │ ├── hash.rb
│ │ └── string.rb
│ ├── decode.rb
│ └── parser.rb
└── bencode.rb
├── LICENSE
├── README.md
├── bencode.gemspec
└── Rakefile
/Gemfile:
--------------------------------------------------------------------------------
1 | source "http://rubygems.org"
2 |
3 | gemspec
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | doc
2 | *.gem
3 | .idea
4 | Gemfile.lock
5 | .yardoc
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 | rvm:
3 | - 1.9.3
4 | - 2.0.0
5 | - 2.1.1
6 |
--------------------------------------------------------------------------------
/test/fixtures/test.torrent:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dasch/ruby-bencode/HEAD/test/fixtures/test.torrent
--------------------------------------------------------------------------------
/test/fixtures/python.torrent:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dasch/ruby-bencode/HEAD/test/fixtures/python.torrent
--------------------------------------------------------------------------------
/.yardopts:
--------------------------------------------------------------------------------
1 | --no-private
2 | --title "BEncode Documentation"
3 | --readme README.md
4 | lib/**/*.rb -
5 | LICENSE
6 |
--------------------------------------------------------------------------------
/test/bittorrent_test.rb:
--------------------------------------------------------------------------------
1 | require 'environment'
2 |
3 | describe "bittorrent" do
4 | it "should load a bencoded torrent file" do
5 | BEncode.load_file("test/fixtures/python.torrent")
6 | pass
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/test/encoding_test.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | require 'environment'
3 |
4 | describe "encoding" do
5 | it_should_encode "i42e", 42
6 | it_should_encode "3:foo", "foo"
7 | it_should_encode "5:café", "café"
8 | end
9 |
--------------------------------------------------------------------------------
/test/utf8_decoding_test.rb:
--------------------------------------------------------------------------------
1 | require 'environment'
2 |
3 | describe "utf encoding" do
4 | it "should be able to handle UTF8-encoded data" do
5 | BEncode.load_file('test/fixtures/test.torrent')
6 | pass
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/.github/main.workflow:
--------------------------------------------------------------------------------
1 | workflow "Code Style" {
2 | resolves = ["Rubocop checks"]
3 | on = "push"
4 | }
5 |
6 | action "Rubocop checks" {
7 | uses = "gimenete/rubocop-action@1.0"
8 | secrets = ["GITHUB_TOKEN"]
9 | }
10 |
--------------------------------------------------------------------------------
/test/shoulda_macros/encoding.rb:
--------------------------------------------------------------------------------
1 |
2 | class MiniTest::Test
3 | def self.it_should_encode(expected, value)
4 | it "should encode #{value.inspect} into #{expected.inspect}" do
5 | assert_equal expected, value.bencode
6 | end
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/lib/bencode/core_ext/object.rb:
--------------------------------------------------------------------------------
1 |
2 | class Object
3 | #
4 | # Raises an exception. Subclasses of Object must themselves
5 | # define meaningful #bencode methods.
6 | def bencode
7 | raise BEncode::EncodeError, "cannot bencode #{self.class}"
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/test/shoulda_macros/decoding.rb:
--------------------------------------------------------------------------------
1 |
2 | class MiniTest::Test
3 | def self.it_should_decode(expected, encoded, opts = {})
4 | it "should decode #{encoded.inspect} into #{expected.inspect}" do
5 | assert_equal expected, BEncode.load(encoded, opts)
6 | end
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/test/benchmark/encoding.rb:
--------------------------------------------------------------------------------
1 | $:.unshift(File.dirname(__FILE__)+"/../../lib")
2 | require 'benchmark'
3 | require 'bencode'
4 |
5 | Benchmark.bmbm do |x|
6 | x.report("Encoding an array of integers") do
7 | 100.times do
8 | ((1..50).to_a * 100).bencode
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/test/environment.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'bundler/setup'
3 |
4 | require 'minitest/autorun'
5 | require 'shoulda'
6 |
7 | require File.dirname(__FILE__) + '/../lib/bencode'
8 | Dir.glob(File.dirname(__FILE__) + '/shoulda_macros/*.rb').each do |macro|
9 | require macro
10 | end
11 |
--------------------------------------------------------------------------------
/lib/bencode/core_ext/io.rb:
--------------------------------------------------------------------------------
1 | class IO
2 | def self.bdecode(filename)
3 | open(filename, 'rb') {|io| io.bdecode}
4 | end
5 |
6 | def self.bencode(filename)
7 | open(filename, 'rb') {|io| io.bencode}
8 | end
9 |
10 | def bdecode
11 | read.chomp.bdecode
12 | end
13 |
14 | def bencode
15 | read.chomp.bencode
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/lib/bencode/core_ext/integer.rb:
--------------------------------------------------------------------------------
1 |
2 | class Integer
3 | #
4 | # Bencodes the Integer object. Bencoded integers are represented
5 | # as +ixe+, where +x+ is the integer with an optional
6 | # hyphen prepended, indicating negativity.
7 | #
8 | # 42.bencode #=> "i42e"
9 | # -7.bencode #=> "i-7e"
10 | def bencode
11 | "i#{self}e"
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/bencode.rb:
--------------------------------------------------------------------------------
1 |
2 | # Support for loading and dumping bencoded data.
3 | #
4 | # See {BEncode.load} and {BEncode.dump}.
5 | module BEncode
6 | VERSION = "1.0.0"
7 |
8 | class DecodeError < StandardError
9 | end
10 |
11 | class EncodeError < StandardError
12 | end
13 | end
14 |
15 | glob = File.join(File.dirname(__FILE__), 'bencode/**/*.rb')
16 |
17 | Dir[glob].sort.each {|file| require file }
18 |
--------------------------------------------------------------------------------
/lib/bencode/core_ext/array.rb:
--------------------------------------------------------------------------------
1 |
2 | class Array
3 | #
4 | # Bencodes the Array object. Bencoded arrays are represented as
5 | # +lxe+, where +x+ is zero or more bencoded objects.
6 | #
7 | # [1, "foo"].bencode #=> "li1e3:fooe"
8 | #
9 | def bencode
10 | begin
11 | "l#{map{|obj| obj.bencode }.join}e"
12 | rescue BEncode::EncodeError
13 | raise BEncode::EncodeError, "array items must be encodable"
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/test/benchmark/decoding.rb:
--------------------------------------------------------------------------------
1 | $:.unshift(File.dirname(__FILE__)+"/../../lib")
2 | require 'benchmark'
3 | require 'bencode'
4 |
5 | Benchmark.bmbm do |x|
6 | x.report("Decoding an integer") do
7 | 100_000.times do
8 | BEncode.load("i42e")
9 | end
10 | end
11 |
12 | x.report("Decoding an string") do
13 | 100_000.times do
14 | BEncode.load("6:foobar")
15 | end
16 | end
17 | x.report("Decoding a long integer array") do
18 | 100.times do
19 | BEncode.load("l" + ("i42e" * 1000) + "e")
20 | end
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/lib/bencode/core_ext/hash.rb:
--------------------------------------------------------------------------------
1 |
2 | class Hash
3 | #
4 | # Bencodes the Hash object. Bencoded hashes are represented as
5 | # +dxe+, where +x+ is zero or a power of two bencoded objects.
6 | # each key is immediately followed by its associated value.
7 | # All keys must be strings. The keys of the bencoded hash will
8 | # be in lexicographical order.
9 | def bencode
10 | pairs = sort.map{|key, val| [key.to_s.bencode, val.bencode] }
11 | "d#{pairs.join}e"
12 | rescue NoMethodError
13 | raise BEncode::EncodeError, "dictionary keys must be strings"
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/test/decoding_test.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | require 'environment'
3 |
4 | describe "decoding" do
5 | it_should_decode 42, "i42e"
6 | it_should_decode 0, "i0e"
7 | it_should_decode -42, "i-42e"
8 |
9 | it_should_decode "foo", "3:foo"
10 | it_should_decode "", "0:"
11 |
12 | it_should_decode [1, 2, 3], "li1ei2ei3ee"
13 |
14 | hsh = {"foo" => "bar", "baz" => "qux"}
15 | it_should_decode hsh, "d3:foo3:bar3:baz3:quxe"
16 |
17 | it_should_decode 42, "i42eBOGUS", :ignore_trailing_junk => true
18 |
19 | it_should_decode "café", "5:café"
20 | it_should_decode ["你好", "中文"], "l6:你好6:中文e"
21 | end
22 |
--------------------------------------------------------------------------------
/lib/bencode/core_ext/string.rb:
--------------------------------------------------------------------------------
1 |
2 | class String
3 | #
4 | # Bencodes the String object. Bencoded strings are represented
5 | # as x:y, where +y+ is the string and +x+
6 | # is the length of the string.
7 | #
8 | # "foo".bencode #=> "3:foo"
9 | # "".bencode #=> "0:"
10 | #
11 | def bencode
12 | "#{bytesize}:#{self}"
13 | end
14 |
15 | #
16 | # Bdecodes the String object and returns the data serialized
17 | # through bencoding.
18 | #
19 | # "li1ei2ei3ee".bdecode #=> [1, 2, 3]
20 | #
21 | def bdecode
22 | BEncode.load(self)
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Copyright (c) 2009 Daniel Schierbeck
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is
9 | furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in
12 | all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/lib/bencode/decode.rb:
--------------------------------------------------------------------------------
1 |
2 | require 'strscan'
3 |
4 | module BEncode
5 | # Encodes the Ruby object +obj+ into a bencoded string.
6 | #
7 | # @param [Hash, Array, Integer, String] obj the object to encode
8 | # @return [String] a bencoded string
9 | # @raise [EncodeError] if +obj+ is not a supported object type
10 | def self.dump(obj)
11 | obj.bencode
12 | end
13 |
14 | # Decodes +str+ into a Ruby structure.
15 | #
16 | # @param [String] str a bencoded string
17 | # @option opts [Boolean] :ignore_trailing_junk (false) whether
18 | # to ignore invalid bencode at the end of +str+
19 | # @return [Object] a Ruby object
20 | # @raise [DecodeError] if +str+ is malformed
21 | def self.load(str, opts = {})
22 | scanner = BEncode::Parser.new(str)
23 | obj = scanner.parse!
24 | raise BEncode::DecodeError unless (opts[:ignore_trailing_junk] || scanner.eos?)
25 | obj
26 | end
27 |
28 | # Decodes the file located at +path+.
29 | #
30 | # @param [String] path path to the bencoded file
31 | # @option (see .load)
32 | # @return (see .load)
33 | def self.load_file(path, opts = {})
34 | File.open(path, 'rb') do |io|
35 | load(io, opts)
36 | end
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Ruby bencode binding
2 | ====================
3 |
4 | This is a simple library for reading and writing bencoded data.
5 |
6 |
7 | What is bencode?
8 | ----------------
9 |
10 | Bencode is a simple data serialization format used by the popular
11 | [BitTorrent](http://bittorrent.org/) P2P file sharing system.
12 |
13 | It contains only four data types, namely:
14 |
15 | - byte strings
16 | - integers
17 | - lists
18 | - dictionaries
19 |
20 |
21 | Examples
22 | --------
23 |
24 | Encoding objects is as simple as calling `#bencode` on them:
25 |
26 | ```ruby
27 | "foo bar".bencode # => "7:foo bar"
28 | 42.bencode # => "i42e"
29 | [1, 2, 3].bencode # => "li1ei2ei3ee"
30 | {"foo" => 1, "bar" => -10}.bencode # => "d3:bari-10e3:fooi1ee"
31 | ```
32 |
33 | Decoding a complete data stream is as easy as calling `BEncode.load(data)`.
34 |
35 | Decoding a data stream in chunks works as follows:
36 |
37 | ```ruby
38 | irb(main):007:0> stream = BEncode::Parser.new(StringIO.new "d3:foo3:bared3:baz3:quxe")
39 | => #>
40 | irb(main):008:0> stream.parse!
41 | => {"foo"=>"bar"}
42 | irb(main):009:0> stream.parse!
43 | => {"baz"=>"qux"}
44 | ```
45 |
46 | License
47 | -------
48 |
49 | Released under the MIT license.
50 |
51 |
52 | Contributors
53 | ------------
54 |
55 | - Daniel Schierbeck ([dasch](https://github.com/dasch))
56 | - Mike Hodgson ([mikehodgson](https://github.com/mikehodgson))
57 | - Andrew Danforth
58 | - Eric Himmelreich
59 | - Allen Madsen ([blatyo](https://github.com/blatyo))
60 | - Ian Levesque ([ianlevesque](https://github.com/ianlevesque))
61 | - Dirk Geurs ([Dirklectisch](https://github.com/Dirklectisch))
62 | - Xinyue Lu ([msg7086](https://github.com/msg7086))
63 |
--------------------------------------------------------------------------------
/bencode.gemspec:
--------------------------------------------------------------------------------
1 | Gem::Specification.new do |s|
2 | s.specification_version = 2 if s.respond_to? :specification_version=
3 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
4 | s.rubygems_version = '1.3.5'
5 |
6 | ## Leave these as is they will be modified for you by the rake gemspec task.
7 | ## If your rubyforge_project name is different, then edit it and comment out
8 | ## the sub! line in the Rakefile
9 | s.name = 'bencode'
10 | s.version = '1.0.0'
11 | s.date = '2024-08-12'
12 |
13 | s.summary = "Encode and decode bencoded data"
14 | s.description = "A simple encoder and decoder for the BitTorrent serialization format."
15 |
16 | s.authors = ["Daniel Schierbeck"]
17 | s.email = "daniel.schierbeck@gmail.com"
18 | s.homepage = "http://github.com/dasch/ruby-bencode"
19 | s.license = 'MIT'
20 |
21 | s.require_paths = %w[lib]
22 |
23 | s.rdoc_options = ["--charset=UTF-8"]
24 | s.extra_rdoc_files = %w[README.md LICENSE]
25 |
26 | s.add_development_dependency('rdoc')
27 | s.add_development_dependency('rake')
28 | s.add_development_dependency('minitest')
29 | s.add_development_dependency('shoulda')
30 |
31 | # = MANIFEST =
32 | s.files = %w[
33 | Gemfile
34 | LICENSE
35 | README.md
36 | Rakefile
37 | bencode.gemspec
38 | lib/bencode.rb
39 | lib/bencode/core_ext/array.rb
40 | lib/bencode/core_ext/hash.rb
41 | lib/bencode/core_ext/integer.rb
42 | lib/bencode/core_ext/io.rb
43 | lib/bencode/core_ext/object.rb
44 | lib/bencode/core_ext/string.rb
45 | lib/bencode/decode.rb
46 | lib/bencode/parser.rb
47 | test/benchmark/decoding.rb
48 | test/benchmark/encoding.rb
49 | test/bittorrent_test.rb
50 | test/decoding_test.rb
51 | test/encoding_test.rb
52 | test/environment.rb
53 | test/fixtures/python.torrent
54 | test/fixtures/test.torrent
55 | test/shoulda_macros/decoding.rb
56 | test/shoulda_macros/encoding.rb
57 | test/utf8_decoding_test.rb
58 | ]
59 | # = MANIFEST =
60 |
61 | s.test_files = s.files.select { |path| path =~ /^test\/test_.*\.rb/ }
62 | end
63 |
--------------------------------------------------------------------------------
/lib/bencode/parser.rb:
--------------------------------------------------------------------------------
1 | require 'stringio'
2 |
3 | module BEncode
4 | class Parser
5 | attr_reader :stream, :encoding
6 |
7 | def initialize(stream)
8 | @stream =
9 | if stream.kind_of?(IO) || stream.kind_of?(StringIO)
10 | stream
11 | elsif stream.respond_to? :string
12 | StringIO.new stream.string
13 | elsif stream.respond_to? :to_s
14 | StringIO.new stream.to_s
15 | end
16 | @encoding = @stream.external_encoding || Encoding::default_external
17 | end
18 |
19 | def parse!
20 | case peek
21 | when ?i then parse_integer!
22 | when ?l then parse_list!
23 | when ?d then parse_dict!
24 | when ?0 .. ?9 then parse_string!
25 | end
26 | end
27 |
28 | def eos?
29 | stream.eof?
30 | end
31 |
32 | private
33 |
34 | def parse_integer!
35 | stream.getc
36 | num = stream.gets("e") or raise BEncode::DecodeError
37 | num.chop.to_i
38 | end
39 |
40 | def parse_list!
41 | stream.getc
42 | ary = []
43 | ary.push(parse!) until peek == ?e
44 | stream.getc
45 | ary
46 | end
47 |
48 | def parse_dict!
49 | stream.getc
50 | hsh = {}
51 | until peek == ?e
52 | key = parse!
53 |
54 | unless key.is_a? String or key.is_a? Integer
55 | raise BEncode::DecodeError, "key must be a string or number"
56 | end
57 |
58 | val = parse!
59 |
60 | hsh.store(key.to_s, val)
61 | end
62 | stream.getc
63 | hsh
64 | end
65 |
66 | def parse_string!
67 | num = stream.gets(":") or
68 | raise BEncode::DecodeError, "invalid string length (no colon)"
69 |
70 | begin
71 | length = num.chop.to_i
72 | return "" if length == 0 # Workaround for Rubinius bug
73 | str = stream.read(length).force_encoding(encoding)
74 | rescue
75 | raise BEncode::DecodeError, "invalid string length"
76 | end
77 |
78 | str
79 | end
80 |
81 | def peek
82 | c = stream.getc
83 | stream.ungetc(c)
84 | c
85 | end
86 | end
87 | end
88 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'rake'
3 | require 'date'
4 | require 'rake/testtask'
5 | require 'rdoc/task'
6 |
7 | #############################################################################
8 | #
9 | # Helper functions
10 | #
11 | #############################################################################
12 |
13 | def name
14 | @name ||= Dir['*.gemspec'].first.split('.').first
15 | end
16 |
17 | def version
18 | line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
19 | line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
20 | end
21 |
22 | def date
23 | Date.today.to_s
24 | end
25 |
26 | def rubyforge_project
27 | name
28 | end
29 |
30 | def gemspec_file
31 | "#{name}.gemspec"
32 | end
33 |
34 | def gem_file
35 | "#{name}-#{version}.gem"
36 | end
37 |
38 | def replace_header(head, header_name)
39 | head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
40 | end
41 |
42 | #############################################################################
43 | #
44 | # Standard tasks
45 | #
46 | #############################################################################
47 |
48 | task :default => :test
49 |
50 | Rake::TestTask.new(:test) do |test|
51 | test.libs << 'lib' << 'test'
52 | test.pattern = 'test/**/*_test.rb'
53 | test.verbose = false
54 | end
55 |
56 | desc "Run all the benchmarks"
57 | task :benchmark do
58 | Dir["test/benchmark/*.rb"].each do |file|
59 | puts ">> Running #{file}"
60 | puts %x[ruby #{file}]
61 | puts
62 | end
63 | end
64 |
65 | Rake::RDocTask.new do |rdoc|
66 | rdoc.rdoc_dir = 'rdoc'
67 | rdoc.title = "#{name} #{version}"
68 | rdoc.rdoc_files.include('README*')
69 | rdoc.rdoc_files.include('lib/**/*.rb')
70 | end
71 |
72 | desc "Open an irb session preloaded with this library"
73 | task :console do
74 | sh "irb -rubygems -r ./lib/#{name}.rb"
75 | end
76 |
77 | #############################################################################
78 | #
79 | # Packaging tasks
80 | #
81 | #############################################################################
82 |
83 | desc "Create tag v#{version} and build and push #{gem_file} to Rubygems"
84 | task :release => :build do
85 | unless `git branch` =~ /^\* master$/
86 | puts "You must be on the master branch to release!"
87 | exit!
88 | end
89 | sh "git commit --allow-empty -a -m 'Release #{version}'"
90 | sh "git tag v#{version}"
91 | sh "git push origin master"
92 | sh "git push origin v#{version}"
93 | sh "gem push pkg/#{name}-#{version}.gem"
94 | end
95 |
96 | desc "Build #{gem_file} into the pkg directory"
97 | task :build => :gemspec do
98 | sh "mkdir -p pkg"
99 | sh "gem build #{gemspec_file}"
100 | sh "mv #{gem_file} pkg"
101 | end
102 |
103 | desc "Generate #{gemspec_file}"
104 | task :gemspec => :validate do
105 | # read spec file and split out manifest section
106 | spec = File.read(gemspec_file)
107 | head, manifest, tail = spec.split(" # = MANIFEST =\n")
108 |
109 | # replace name version and date
110 | replace_header(head, :name)
111 | replace_header(head, :version)
112 | replace_header(head, :date)
113 | #comment this out if your rubyforge_project has a different name
114 | replace_header(head, :rubyforge_project)
115 |
116 | # determine file list from git ls-files
117 | files = `git ls-files`.
118 | split("\n").
119 | sort.
120 | reject { |file| file =~ /^\./ }.
121 | reject { |file| file =~ /^(rdoc|pkg)/ }.
122 | map { |file| " #{file}" }.
123 | join("\n")
124 |
125 | # piece file back together and write
126 | manifest = " s.files = %w[\n#{files}\n ]\n"
127 | spec = [head, manifest, tail].join(" # = MANIFEST =\n")
128 | File.open(gemspec_file, 'w') { |io| io.write(spec) }
129 | puts "Updated #{gemspec_file}"
130 | end
131 |
132 | desc "Validate #{gemspec_file}"
133 | task :validate do
134 | libfiles = Dir['lib/*'] - ["lib/#{name}.rb", "lib/#{name}"]
135 | unless libfiles.empty?
136 | puts "Directory `lib` should only contain a `#{name}.rb` file and `#{name}` dir."
137 | exit!
138 | end
139 | unless Dir['VERSION*'].empty?
140 | puts "A `VERSION` file at root level violates Gem best practices."
141 | exit!
142 | end
143 | end
144 |
--------------------------------------------------------------------------------