frag 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +1 -0
- data/CHANGELOG +3 -0
- data/Gemfile +6 -0
- data/LICENSE +20 -0
- data/README.markdown +111 -0
- data/Rakefile +1 -0
- data/bin/frag +5 -0
- data/frag.gemspec +19 -0
- data/lib/frag/app.rb +123 -0
- data/lib/frag/version.rb +11 -0
- data/lib/frag.rb +4 -0
- data/test/test_helper.rb +30 -0
- data/test/test_integration.rb +49 -0
- data/test/unit/test_app.rb +372 -0
- metadata +86 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
/Gemfile.lock
|
data/CHANGELOG
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) George Ogata
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
## Frag
|
2
|
+
|
3
|
+
Generate fragments of files from the output of shell commands.
|
4
|
+
|
5
|
+
## Why? How?
|
6
|
+
|
7
|
+
Sometimes you want to generate just part of a file programatically. For example,
|
8
|
+
you might want to generate your `~/.ssh/config` from a list of hosts managed by
|
9
|
+
[Chef][chef]:
|
10
|
+
|
11
|
+
Host a
|
12
|
+
Hostname 123.123.123.1
|
13
|
+
User me
|
14
|
+
|
15
|
+
Host b
|
16
|
+
Hostname 123.123.123.2
|
17
|
+
User me
|
18
|
+
|
19
|
+
# GEN: knife sshgen
|
20
|
+
# ENDGEN
|
21
|
+
|
22
|
+
Now `frag` that file:
|
23
|
+
|
24
|
+
frag ~/.ssh/config
|
25
|
+
|
26
|
+
and the region delimited by the `GEN`..`ENDGEN` lines will be filled in with the
|
27
|
+
output from [knife sshgen][knife-sshgen]. The delimiter lines remain, so you can
|
28
|
+
re-`frag` anytime to bring it up to date.
|
29
|
+
|
30
|
+
Or maybe you want your `/etc/hosts` to set a list of local subdomains from a
|
31
|
+
database:
|
32
|
+
|
33
|
+
127.0.0.1 localhost
|
34
|
+
255.255.255.255 broadcasthost
|
35
|
+
::1 localhost
|
36
|
+
fe80::1%lo0 localhost
|
37
|
+
|
38
|
+
# GEN: mysql myapp -Bre 'select subdomain from sites | sed -e 's/.*/127.0.0.1 &.myapp.local/'
|
39
|
+
# ENDGEN
|
40
|
+
|
41
|
+
Pipelines work - woo!
|
42
|
+
|
43
|
+
Or maybe you're authoring this README and want to show all the options `frag`
|
44
|
+
takes:
|
45
|
+
|
46
|
+
<!-- GEN: echo; ruby -Ilib bin/frag --help | sed -e 's/^/ /'; echo -->
|
47
|
+
|
48
|
+
USAGE: bin/frag [options] file ...
|
49
|
+
-b, --begin DELIMITER
|
50
|
+
-e, --end DELIMITER
|
51
|
+
-l, --leader STRING
|
52
|
+
-t, --trailer STRING
|
53
|
+
-p, --backup-prefix PREFIX
|
54
|
+
-s, --backup-suffix SUFFIX
|
55
|
+
|
56
|
+
<!-- ENDGEN -->
|
57
|
+
|
58
|
+
(Check the source... ;-)
|
59
|
+
|
60
|
+
[chef]: http://www.opscode.com/chef
|
61
|
+
[knife-sshgen]: https://github.com/harvesthq/knife-plugins/blob/master/.chef/plugins/knife/sshgen.rb
|
62
|
+
|
63
|
+
## Too simple?
|
64
|
+
|
65
|
+
Make things complicated with these customizable options.
|
66
|
+
|
67
|
+
### Comment Syntax
|
68
|
+
|
69
|
+
By default, frag assumes the beginning and ending lines for each fragment start
|
70
|
+
with a '#' (followed by optional whitespace). Change that with`-l` or
|
71
|
+
`--leader`:
|
72
|
+
|
73
|
+
frag -l '--' magic.hs
|
74
|
+
|
75
|
+
If your comments need trailing characters too, there's `-t` or `--trailer`:
|
76
|
+
|
77
|
+
frag -l '/*' -t '*/' magic.cc
|
78
|
+
|
79
|
+
### Custom Delimiters
|
80
|
+
|
81
|
+
If you want to choose your own delimiters.
|
82
|
+
|
83
|
+
frag -b 'FRAGGED BY' -e 'FRAGGED' file.txt
|
84
|
+
|
85
|
+
Now your regions can look like:
|
86
|
+
|
87
|
+
# FRAGGED BY ...
|
88
|
+
...
|
89
|
+
# FRAGGED
|
90
|
+
|
91
|
+
### Backups
|
92
|
+
|
93
|
+
Back up the original file by providing a suffix:
|
94
|
+
|
95
|
+
frag -s '.backup' file.txt
|
96
|
+
|
97
|
+
Or dump all your backups into a directory with a prefix:
|
98
|
+
|
99
|
+
frag -p ~/.frag-backups file.txt
|
100
|
+
|
101
|
+
## Note on Patches/Pull Requests
|
102
|
+
|
103
|
+
* Bug reports: http://github.com/oggy/frag/issues
|
104
|
+
* Source: http://github.com/oggy/frag
|
105
|
+
* Patches: Fork on Github, send pull request.
|
106
|
+
* Ensure patch includes tests.
|
107
|
+
* Leave the version alone, or bump it in a separate commit.
|
108
|
+
|
109
|
+
## Copyright
|
110
|
+
|
111
|
+
Copyright (c) George Ogata. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'ritual'
|
data/bin/frag
ADDED
data/frag.gemspec
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.unshift File.expand_path('lib', File.dirname(__FILE__))
|
3
|
+
require 'frag/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.name = 'frag'
|
7
|
+
gem.version = Frag::VERSION
|
8
|
+
gem.authors = ['George Ogata']
|
9
|
+
gem.email = ['george.ogata@gmail.com']
|
10
|
+
gem.license = 'MIT'
|
11
|
+
gem.summary = "Generate regions of files from the output of shell commands."
|
12
|
+
gem.homepage = 'http://github.com/oggy/frag'
|
13
|
+
|
14
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
15
|
+
gem.files = `git ls-files`.split("\n")
|
16
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
|
+
|
18
|
+
gem.add_development_dependency 'ritual', '~> 0.4.1'
|
19
|
+
end
|
data/lib/frag/app.rb
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'optparse'
|
4
|
+
|
5
|
+
module Frag
|
6
|
+
class App
|
7
|
+
def initialize(args, input=STDIN, output=STDOUT, error=STDERR)
|
8
|
+
@input, @output, @error = input, output, error
|
9
|
+
@status = 0
|
10
|
+
|
11
|
+
beginning = 'GEN:'
|
12
|
+
ending = 'ENDGEN'
|
13
|
+
leader = '#'
|
14
|
+
trailer = ''
|
15
|
+
@backup_prefix = @backup_suffix = nil
|
16
|
+
|
17
|
+
parser = OptionParser.new do |parser|
|
18
|
+
parser.banner = "USAGE: #$0 [options] file ..."
|
19
|
+
|
20
|
+
parser.on '-b', '--begin DELIMITER' do |value|
|
21
|
+
beginning = Regexp.escape(value)
|
22
|
+
end
|
23
|
+
parser.on '-e', '--end DELIMITER' do |value|
|
24
|
+
ending = Regexp.escape(value)
|
25
|
+
end
|
26
|
+
parser.on '-l', '--leader STRING' do |value|
|
27
|
+
leader = Regexp.escape(value)
|
28
|
+
end
|
29
|
+
parser.on '-t', '--trailer STRING' do |value|
|
30
|
+
trailer = Regexp.escape(value)
|
31
|
+
end
|
32
|
+
parser.on '-p', '--backup-prefix PREFIX' do |value|
|
33
|
+
@backup_prefix = value
|
34
|
+
end
|
35
|
+
parser.on '-s', '--backup-suffix SUFFIX' do |value|
|
36
|
+
@backup_suffix = value
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
parser.parse!(args)
|
41
|
+
args.size > 0 or
|
42
|
+
return error "no files given"
|
43
|
+
|
44
|
+
@begin_line = Regexp.new(['^', leader, beginning, '(.*)', trailer, '$'].reject(&:empty?).join('\\s*'))
|
45
|
+
@end_line = Regexp.new(['^', leader, ending, trailer, '$'].reject(&:empty?).join('\\s*'))
|
46
|
+
@input_paths = args
|
47
|
+
end
|
48
|
+
|
49
|
+
def run
|
50
|
+
return @status if @status != 0
|
51
|
+
@input_paths.each do |input_path|
|
52
|
+
manage_files(input_path) do |input, output|
|
53
|
+
process(input, output)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
@status
|
57
|
+
end
|
58
|
+
|
59
|
+
attr_reader :status
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def error(message, status=1)
|
64
|
+
@error.puts "error: #{message}"
|
65
|
+
@status = status
|
66
|
+
false
|
67
|
+
end
|
68
|
+
|
69
|
+
def manage_files(input_path)
|
70
|
+
File.exist?(input_path) or
|
71
|
+
return error "file not found: #{input_path}"
|
72
|
+
File.file?(input_path) or
|
73
|
+
return error "not a file: #{input_path}"
|
74
|
+
File.readable?(input_path) or
|
75
|
+
return error "cannot open file: #{input_path}"
|
76
|
+
tempfile = nil
|
77
|
+
success = nil
|
78
|
+
open(input_path) do |input|
|
79
|
+
tempfile = Tempfile.open('frag') do |output|
|
80
|
+
yield input, output or
|
81
|
+
return
|
82
|
+
output
|
83
|
+
end
|
84
|
+
end
|
85
|
+
if @backup_prefix || @backup_suffix
|
86
|
+
backup_path = "#{@backup_prefix}#{File.expand_path(input_path)}#{@backup_suffix}"
|
87
|
+
FileUtils.mkdir_p File.dirname(backup_path)
|
88
|
+
FileUtils.cp input_path, backup_path
|
89
|
+
end
|
90
|
+
FileUtils.cp tempfile.path, input_path
|
91
|
+
end
|
92
|
+
|
93
|
+
def process(input, output)
|
94
|
+
region_start = nil
|
95
|
+
command = nil
|
96
|
+
while (line = input.gets)
|
97
|
+
unless region_start
|
98
|
+
output.puts line
|
99
|
+
end
|
100
|
+
case line
|
101
|
+
when @begin_line
|
102
|
+
region_start.nil? or
|
103
|
+
return error "#{input.lineno}: nested region"
|
104
|
+
command = $1
|
105
|
+
region_start = input.lineno
|
106
|
+
when @end_line
|
107
|
+
region_start or
|
108
|
+
return error "#{input.lineno}: unmatched begin delimiter"
|
109
|
+
output.puts `#{command}`
|
110
|
+
if !$?.success?
|
111
|
+
return error "#{region_start}: failed: (#{$?.exitstatus}) #{command}"
|
112
|
+
end
|
113
|
+
region_start = nil
|
114
|
+
output.puts line
|
115
|
+
end
|
116
|
+
end
|
117
|
+
if region_start
|
118
|
+
return error "#{region_start}: unmatched end delimiter"
|
119
|
+
end
|
120
|
+
true
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
data/lib/frag/version.rb
ADDED
data/lib/frag.rb
ADDED
data/test/test_helper.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
require 'minitest/spec'
|
3
|
+
require 'debugger'
|
4
|
+
|
5
|
+
ROOT = File.expand_path('..', File.dirname(__FILE__))
|
6
|
+
$:.unshift "#{ROOT}/lib"
|
7
|
+
require 'frag'
|
8
|
+
|
9
|
+
String.class_eval do
|
10
|
+
def demargin
|
11
|
+
gsub(/^ *\|/, '')
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
MiniTest::Spec.class_eval do
|
16
|
+
before do
|
17
|
+
FileUtils.mkdir_p "#{ROOT}/test/tmp"
|
18
|
+
@original_pwd = Dir.pwd
|
19
|
+
FileUtils.chdir "#{ROOT}/test/tmp"
|
20
|
+
end
|
21
|
+
|
22
|
+
after do
|
23
|
+
FileUtils.chdir @original_pwd
|
24
|
+
FileUtils.rm_rf "#{ROOT}/test/tmp"
|
25
|
+
end
|
26
|
+
|
27
|
+
def write_file(path, input)
|
28
|
+
open(path, 'w') { |f| f.print input }
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
require 'open3'
|
3
|
+
|
4
|
+
describe Frag do
|
5
|
+
def frag(*args)
|
6
|
+
ruby = RbConfig::CONFIG.values_at('bindir', 'ruby_install_name').join('/')
|
7
|
+
command = [ruby, '-I', "#{ROOT}/lib", "#{ROOT}/bin/frag", *args]
|
8
|
+
@output, @error, status = Open3.capture3(*command)
|
9
|
+
status
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "when a file is frag'd successfully" do
|
13
|
+
it "updates the file, and exits with zero status" do
|
14
|
+
write_file 'input', <<-EOS.demargin
|
15
|
+
|# GEN: echo new
|
16
|
+
|old
|
17
|
+
|# ENDGEN
|
18
|
+
EOS
|
19
|
+
frag('input').must_equal 0
|
20
|
+
File.read('input').must_equal <<-EOS.demargin
|
21
|
+
|# GEN: echo new
|
22
|
+
|new
|
23
|
+
|# ENDGEN
|
24
|
+
EOS
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "when options are used succesfully" do
|
29
|
+
it "updates the file, and exits with zero status" do
|
30
|
+
write_file 'input', <<-EOS.demargin
|
31
|
+
|# GEN: echo new
|
32
|
+
|old
|
33
|
+
|# ENDGEN
|
34
|
+
|// GEN: echo new
|
35
|
+
|old
|
36
|
+
|// ENDGEN
|
37
|
+
EOS
|
38
|
+
frag('-l', '//', 'input').must_equal 0
|
39
|
+
File.read('input').must_equal <<-EOS.demargin
|
40
|
+
|# GEN: echo new
|
41
|
+
|old
|
42
|
+
|# ENDGEN
|
43
|
+
|// GEN: echo new
|
44
|
+
|new
|
45
|
+
|// ENDGEN
|
46
|
+
EOS
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,372 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
require 'stringio'
|
3
|
+
require 'tempfile'
|
4
|
+
|
5
|
+
describe Frag::App do
|
6
|
+
let(:input) { StringIO.new }
|
7
|
+
let(:output) { StringIO.new }
|
8
|
+
let(:error) { StringIO.new }
|
9
|
+
|
10
|
+
def frag(*args)
|
11
|
+
Frag::App.new(args, input, output, error).run
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "when no options are used" do
|
15
|
+
it "populates the delimited region if it's empty" do
|
16
|
+
write_file 'input', <<-EOS.demargin
|
17
|
+
|# GEN: echo hi
|
18
|
+
|# ENDGEN
|
19
|
+
EOS
|
20
|
+
frag('input').must_equal 0
|
21
|
+
(output.string + error.string).must_equal ''
|
22
|
+
File.read('input').must_equal <<-EOS.demargin
|
23
|
+
|# GEN: echo hi
|
24
|
+
|hi
|
25
|
+
|# ENDGEN
|
26
|
+
EOS
|
27
|
+
end
|
28
|
+
|
29
|
+
it "replaces the delimited region if there's already something there" do
|
30
|
+
write_file 'input', <<-EOS.demargin
|
31
|
+
|# GEN: echo new
|
32
|
+
|old
|
33
|
+
|# ENDGEN
|
34
|
+
EOS
|
35
|
+
frag('input').must_equal 0
|
36
|
+
(output.string + error.string).must_equal ''
|
37
|
+
File.read('input').must_equal <<-EOS.demargin
|
38
|
+
|# GEN: echo new
|
39
|
+
|new
|
40
|
+
|# ENDGEN
|
41
|
+
EOS
|
42
|
+
end
|
43
|
+
|
44
|
+
it "appends a newline if the command output doesn't end in one" do
|
45
|
+
write_file 'input', <<-EOS.demargin
|
46
|
+
|# GEN: echo -n hi
|
47
|
+
|# ENDGEN
|
48
|
+
EOS
|
49
|
+
frag('input').must_equal 0
|
50
|
+
(output.string + error.string).must_equal ''
|
51
|
+
File.read('input').must_equal <<-EOS.demargin
|
52
|
+
|# GEN: echo -n hi
|
53
|
+
|hi
|
54
|
+
|# ENDGEN
|
55
|
+
EOS
|
56
|
+
end
|
57
|
+
|
58
|
+
it "processes multiple regions, and leave surrounding content alone" do
|
59
|
+
write_file 'input', <<-EOS.demargin
|
60
|
+
|before
|
61
|
+
|# GEN: echo one
|
62
|
+
|# ENDGEN
|
63
|
+
|middle
|
64
|
+
|# GEN: echo two
|
65
|
+
|# ENDGEN
|
66
|
+
|after
|
67
|
+
EOS
|
68
|
+
frag('input').must_equal 0
|
69
|
+
(output.string + error.string).must_equal ''
|
70
|
+
File.read('input').must_equal <<-EOS.demargin
|
71
|
+
|before
|
72
|
+
|# GEN: echo one
|
73
|
+
|one
|
74
|
+
|# ENDGEN
|
75
|
+
|middle
|
76
|
+
|# GEN: echo two
|
77
|
+
|two
|
78
|
+
|# ENDGEN
|
79
|
+
|after
|
80
|
+
EOS
|
81
|
+
end
|
82
|
+
|
83
|
+
it "can process multiple files" do
|
84
|
+
write_file 'one', <<-EOS.demargin
|
85
|
+
|# GEN: echo one
|
86
|
+
|# ENDGEN
|
87
|
+
EOS
|
88
|
+
write_file 'two', <<-EOS.demargin
|
89
|
+
|# GEN: echo two
|
90
|
+
|# ENDGEN
|
91
|
+
EOS
|
92
|
+
frag('one', 'two').must_equal 0
|
93
|
+
(output.string + error.string).must_equal ''
|
94
|
+
File.read('one').must_equal <<-EOS.demargin
|
95
|
+
|# GEN: echo one
|
96
|
+
|one
|
97
|
+
|# ENDGEN
|
98
|
+
EOS
|
99
|
+
File.read('two').must_equal <<-EOS.demargin
|
100
|
+
|# GEN: echo two
|
101
|
+
|two
|
102
|
+
|# ENDGEN
|
103
|
+
EOS
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "when the delimiter options are used" do
|
108
|
+
['-b', '--begin'].each do |option|
|
109
|
+
it "uses the beginning delimiter given by #{option}" do
|
110
|
+
write_file 'input', <<-EOS.demargin
|
111
|
+
|# BEGIN echo one
|
112
|
+
|# ENDGEN
|
113
|
+
EOS
|
114
|
+
frag(option, 'BEGIN', 'input').must_equal 0
|
115
|
+
(output.string + error.string).must_equal ''
|
116
|
+
File.read('input').must_equal <<-EOS.demargin
|
117
|
+
|# BEGIN echo one
|
118
|
+
|one
|
119
|
+
|# ENDGEN
|
120
|
+
EOS
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
['-e', '--end'].each do |option|
|
125
|
+
it "uses the ending delimiter given by #{option}" do
|
126
|
+
write_file 'input', <<-EOS.demargin
|
127
|
+
|# GEN: echo one
|
128
|
+
|# END
|
129
|
+
EOS
|
130
|
+
frag(option, 'END', 'input').must_equal 0
|
131
|
+
(output.string + error.string).must_equal ''
|
132
|
+
File.read('input').must_equal <<-EOS.demargin
|
133
|
+
|# GEN: echo one
|
134
|
+
|one
|
135
|
+
|# END
|
136
|
+
EOS
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
['-l', '--leader'].each do |option|
|
141
|
+
it "uses the delimiter leader given by #{option}" do
|
142
|
+
write_file 'input', <<-EOS.demargin
|
143
|
+
|// GEN: echo one
|
144
|
+
|// ENDGEN
|
145
|
+
EOS
|
146
|
+
frag(option, '//', 'input').must_equal 0
|
147
|
+
(output.string + error.string).must_equal ''
|
148
|
+
File.read('input').must_equal <<-EOS.demargin
|
149
|
+
|// GEN: echo one
|
150
|
+
|one
|
151
|
+
|// ENDGEN
|
152
|
+
EOS
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
['-t', '--trailer'].each do |option|
|
157
|
+
it "uses the delimiter trailer given by #{option}" do
|
158
|
+
write_file 'input', <<-EOS.demargin
|
159
|
+
|# GEN: echo one !!
|
160
|
+
|# ENDGEN !!
|
161
|
+
EOS
|
162
|
+
frag(option, '!!', 'input').must_equal 0
|
163
|
+
(output.string + error.string).must_equal ''
|
164
|
+
File.read('input').must_equal <<-EOS.demargin
|
165
|
+
|# GEN: echo one !!
|
166
|
+
|one
|
167
|
+
|# ENDGEN !!
|
168
|
+
EOS
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
it "supports using delimiter options together" do
|
173
|
+
write_file 'input', <<-EOS.demargin
|
174
|
+
|<!-- BEGIN echo one -->
|
175
|
+
|<!-- END -->
|
176
|
+
EOS
|
177
|
+
frag('-b', 'BEGIN', '-e', 'END', '-l', '<!--', '-t', '-->', 'input').must_equal 0
|
178
|
+
(output.string + error.string).must_equal ''
|
179
|
+
File.read('input').must_equal <<-EOS.demargin
|
180
|
+
|<!-- BEGIN echo one -->
|
181
|
+
|one
|
182
|
+
|<!-- END -->
|
183
|
+
EOS
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
describe "when the backup options are used" do
|
188
|
+
['-p', '--backup-prefix'].each do |option|
|
189
|
+
it "backs up the input file with the prefix given by #{option}" do
|
190
|
+
write_file 'input', <<-EOS.demargin
|
191
|
+
|# GEN: echo new
|
192
|
+
|old
|
193
|
+
|# ENDGEN
|
194
|
+
EOS
|
195
|
+
frag(option, 'path/to/backups', 'input').must_equal 0
|
196
|
+
(output.string + error.string).must_equal ''
|
197
|
+
File.read('input').must_equal <<-EOS.demargin
|
198
|
+
|# GEN: echo new
|
199
|
+
|new
|
200
|
+
|# ENDGEN
|
201
|
+
EOS
|
202
|
+
File.read("path/to/backups/#{File.expand_path('input')}").must_equal <<-EOS.demargin
|
203
|
+
|# GEN: echo new
|
204
|
+
|old
|
205
|
+
|# ENDGEN
|
206
|
+
EOS
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
['-s', '--backup-suffix'].each do |option|
|
211
|
+
it "backs up the input file with the suffix given by #{option}" do
|
212
|
+
write_file 'input', <<-EOS.demargin
|
213
|
+
|# GEN: echo new
|
214
|
+
|old
|
215
|
+
|# ENDGEN
|
216
|
+
EOS
|
217
|
+
frag(option, '.backup', 'input').must_equal 0
|
218
|
+
(output.string + error.string).must_equal ''
|
219
|
+
File.read('input').must_equal <<-EOS.demargin
|
220
|
+
|# GEN: echo new
|
221
|
+
|new
|
222
|
+
|# ENDGEN
|
223
|
+
EOS
|
224
|
+
File.read('input.backup').must_equal <<-EOS.demargin
|
225
|
+
|# GEN: echo new
|
226
|
+
|old
|
227
|
+
|# ENDGEN
|
228
|
+
EOS
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
it "supports using --backup-prefix and --backup-suffix together" do
|
233
|
+
write_file 'input', <<-EOS.demargin
|
234
|
+
|# GEN: echo new
|
235
|
+
|old
|
236
|
+
|# ENDGEN
|
237
|
+
EOS
|
238
|
+
frag('-p', 'path/to/backups', '-s', '.backup', 'input').must_equal 0
|
239
|
+
(output.string + error.string).must_equal ''
|
240
|
+
File.read('input').must_equal <<-EOS.demargin
|
241
|
+
|# GEN: echo new
|
242
|
+
|new
|
243
|
+
|# ENDGEN
|
244
|
+
EOS
|
245
|
+
File.read("path/to/backups/#{File.expand_path('input')}.backup").must_equal <<-EOS.demargin
|
246
|
+
|# GEN: echo new
|
247
|
+
|old
|
248
|
+
|# ENDGEN
|
249
|
+
EOS
|
250
|
+
end
|
251
|
+
|
252
|
+
it "does not back up files which produces errors" do
|
253
|
+
write_file 'a', <<-EOS.demargin
|
254
|
+
|# GEN: true
|
255
|
+
|# ENDGEN
|
256
|
+
EOS
|
257
|
+
write_file 'b', <<-EOS.demargin
|
258
|
+
|# GEN: false
|
259
|
+
|# ENDGEN
|
260
|
+
EOS
|
261
|
+
write_file 'c', <<-EOS.demargin
|
262
|
+
|# GEN: true
|
263
|
+
|# ENDGEN
|
264
|
+
EOS
|
265
|
+
frag('-s', '.backup', 'a', 'b', 'c').must_equal 1
|
266
|
+
File.exist?('a.backup').must_equal true
|
267
|
+
File.exist?('b.backup').must_equal false
|
268
|
+
File.exist?('c.backup').must_equal true
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
it "prints an error and leaves the input file unchanged if a command fails" do
|
273
|
+
write_file 'input', <<-EOS.demargin
|
274
|
+
|# GEN: echo new
|
275
|
+
|old
|
276
|
+
|# ENDGEN
|
277
|
+
|# GEN: false
|
278
|
+
|# ENDGEN
|
279
|
+
EOS
|
280
|
+
frag('input').must_equal 1
|
281
|
+
output.string.must_equal ''
|
282
|
+
error.string.must_match(/\b4:.*failed/)
|
283
|
+
File.read('input').must_equal <<-EOS.demargin
|
284
|
+
|# GEN: echo new
|
285
|
+
|old
|
286
|
+
|# ENDGEN
|
287
|
+
|# GEN: false
|
288
|
+
|# ENDGEN
|
289
|
+
EOS
|
290
|
+
end
|
291
|
+
|
292
|
+
it "prints an error if there's an unmatched beginning line" do
|
293
|
+
write_file 'input', <<-EOS.demargin
|
294
|
+
|# GEN: echo one
|
295
|
+
EOS
|
296
|
+
frag('input').must_equal 1
|
297
|
+
output.string.must_equal ''
|
298
|
+
error.string.must_match(/\b1:.*unmatched/)
|
299
|
+
end
|
300
|
+
|
301
|
+
it "prints an error if there's an unmatched ending line" do
|
302
|
+
write_file 'input', <<-EOS.demargin
|
303
|
+
|# ENDGEN
|
304
|
+
EOS
|
305
|
+
frag('input').must_equal 1
|
306
|
+
output.string.must_equal ''
|
307
|
+
error.string.must_match(/\b1:.*unmatched/)
|
308
|
+
end
|
309
|
+
|
310
|
+
it "continues processing other files if one of them produces an error" do
|
311
|
+
write_file 'a', <<-EOS.demargin
|
312
|
+
|# GEN: echo new
|
313
|
+
|old
|
314
|
+
|# ENDGEN
|
315
|
+
EOS
|
316
|
+
write_file 'b', <<-EOS.demargin
|
317
|
+
|# GEN: false
|
318
|
+
|old
|
319
|
+
|# ENDGEN
|
320
|
+
EOS
|
321
|
+
write_file 'c', <<-EOS.demargin
|
322
|
+
|# GEN: echo new
|
323
|
+
|old
|
324
|
+
|# ENDGEN
|
325
|
+
EOS
|
326
|
+
frag('a', 'b', 'c').must_equal 1
|
327
|
+
output.string.must_equal ''
|
328
|
+
File.read('a').must_equal <<-EOS.demargin
|
329
|
+
|# GEN: echo new
|
330
|
+
|new
|
331
|
+
|# ENDGEN
|
332
|
+
EOS
|
333
|
+
File.read('b').must_equal <<-EOS.demargin
|
334
|
+
|# GEN: false
|
335
|
+
|old
|
336
|
+
|# ENDGEN
|
337
|
+
EOS
|
338
|
+
File.read('c').must_equal <<-EOS.demargin
|
339
|
+
|# GEN: echo new
|
340
|
+
|new
|
341
|
+
|# ENDGEN
|
342
|
+
EOS
|
343
|
+
end
|
344
|
+
|
345
|
+
it "prints an error if an input file does not exist" do
|
346
|
+
frag('input').must_equal 1
|
347
|
+
output.string.must_equal ''
|
348
|
+
error.string.must_match /file not found.*input/
|
349
|
+
end
|
350
|
+
|
351
|
+
it "prints an error if an input file is not readable" do
|
352
|
+
write_file 'input', ''
|
353
|
+
File.chmod 0, 'input'
|
354
|
+
frag('input').must_equal 1
|
355
|
+
output.string.must_equal ''
|
356
|
+
error.string.must_match /cannot open file.*input/
|
357
|
+
end
|
358
|
+
|
359
|
+
it "prints an error if an input file is not a file" do
|
360
|
+
Dir.mkdir 'input'
|
361
|
+
frag('input').must_equal 1
|
362
|
+
output.string.must_equal ''
|
363
|
+
error.string.must_match /not a file.*input/
|
364
|
+
end
|
365
|
+
|
366
|
+
it "prints an error if an input file is a dangling symlink" do
|
367
|
+
File.symlink 'missing', 'input'
|
368
|
+
frag('input').must_equal 1
|
369
|
+
output.string.must_equal ''
|
370
|
+
error.string.must_match /file not found.*input/
|
371
|
+
end
|
372
|
+
end
|
metadata
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: frag
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- George Ogata
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-08-05 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: ritual
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.4.1
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.4.1
|
30
|
+
description:
|
31
|
+
email:
|
32
|
+
- george.ogata@gmail.com
|
33
|
+
executables:
|
34
|
+
- frag
|
35
|
+
extensions: []
|
36
|
+
extra_rdoc_files: []
|
37
|
+
files:
|
38
|
+
- .gitignore
|
39
|
+
- CHANGELOG
|
40
|
+
- Gemfile
|
41
|
+
- LICENSE
|
42
|
+
- README.markdown
|
43
|
+
- Rakefile
|
44
|
+
- bin/frag
|
45
|
+
- frag.gemspec
|
46
|
+
- lib/frag.rb
|
47
|
+
- lib/frag/app.rb
|
48
|
+
- lib/frag/version.rb
|
49
|
+
- test/test_helper.rb
|
50
|
+
- test/test_integration.rb
|
51
|
+
- test/unit/test_app.rb
|
52
|
+
homepage: http://github.com/oggy/frag
|
53
|
+
licenses:
|
54
|
+
- MIT
|
55
|
+
post_install_message:
|
56
|
+
rdoc_options: []
|
57
|
+
require_paths:
|
58
|
+
- lib
|
59
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ! '>='
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0'
|
65
|
+
segments:
|
66
|
+
- 0
|
67
|
+
hash: 863246519076329498
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ! '>='
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
segments:
|
75
|
+
- 0
|
76
|
+
hash: 863246519076329498
|
77
|
+
requirements: []
|
78
|
+
rubyforge_project:
|
79
|
+
rubygems_version: 1.8.24
|
80
|
+
signing_key:
|
81
|
+
specification_version: 3
|
82
|
+
summary: Generate regions of files from the output of shell commands.
|
83
|
+
test_files:
|
84
|
+
- test/test_helper.rb
|
85
|
+
- test/test_integration.rb
|
86
|
+
- test/unit/test_app.rb
|