coque 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 344cd271557b02f3d3b01fc25a524fe44a2ecf4a
4
+ data.tar.gz: 74a4a0f32297f6c2e7368fab1c89a3317e52168d
5
+ SHA512:
6
+ metadata.gz: 4561c894a43b60ef8baaa903e1ac732473927deff5dc9fc9d3c8ce7c3bdba1aa361d783830f20762546574e82baeb252c0d5d8139ed569ffe84b8fa839e32068
7
+ data.tar.gz: e3a6a90e1a67e085a4aab4c5261340599ac9941bdd86da01c645830cb051d2c85fac54fc038308eaba8598cb5b061e9f79de84b9c808561ae39a08774dd4bea9
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.0
5
+ before_install: gem install bundler -v 1.16.1
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at horace.d.williams@gmail.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,45 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ coque (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ ansi (1.5.0)
10
+ builder (3.2.3)
11
+ coderay (1.1.2)
12
+ docile (1.3.1)
13
+ json (2.1.0)
14
+ method_source (0.9.0)
15
+ minitest (5.11.3)
16
+ minitest-reporters (1.2.0)
17
+ ansi
18
+ builder
19
+ minitest (>= 5.0)
20
+ ruby-progressbar
21
+ pry (0.11.2)
22
+ coderay (~> 1.1.0)
23
+ method_source (~> 0.9.0)
24
+ rake (12.3.1)
25
+ ruby-progressbar (1.9.0)
26
+ simplecov (0.16.1)
27
+ docile (~> 1.1)
28
+ json (>= 1.8, < 3)
29
+ simplecov-html (~> 0.10.0)
30
+ simplecov-html (0.10.2)
31
+
32
+ PLATFORMS
33
+ ruby
34
+
35
+ DEPENDENCIES
36
+ bundler (~> 1.16)
37
+ coque!
38
+ minitest (~> 5.11)
39
+ minitest-reporters
40
+ pry
41
+ rake (~> 12.3)
42
+ simplecov
43
+
44
+ BUNDLED WITH
45
+ 1.16.1
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Horace Williams
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.
data/README.md ADDED
@@ -0,0 +1,135 @@
1
+ # Coque
2
+
3
+ Create, manage, and interop with shell pipelines from Ruby. Like [Plumbum](https://plumbum.readthedocs.io/en/latest/), for Ruby, with native (Ruby) code streaming integration.
4
+
5
+ ## Installation
6
+
7
+ Add to your gemfile:
8
+
9
+ ```ruby
10
+ gem 'coque'
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ Create Coque commands:
16
+
17
+ ```rb
18
+ cmd = Coque["echo", "hi"]
19
+ # => <Coque::Sh ["echo", "hi"]>
20
+ ```
21
+
22
+ And run them:
23
+
24
+ ```rb
25
+ res = cmd.run
26
+ # => #<Coque::Result:0x007feb5930e408 @out=#<IO:fd 13>, @pid=58688>
27
+ res.to_a
28
+ # => ["hi"]
29
+ ```
30
+
31
+ Or pipe them:
32
+
33
+ ```rb
34
+ pipeline = cmd | Coque["wc", "-c"]
35
+ # => #<Coque::Pipeline:0x007feb598730b0 @commands=[<Coque::Sh ["echo", "hi"]>, <Coque::Sh ["wc", "-c"]>]>
36
+ pipeline.run.to_a
37
+ # => ["3"]
38
+ ```
39
+
40
+ Coque can also create "Rb" commands, which integrate Ruby code with streaming, line-wise processing of other commands:
41
+
42
+ ```
43
+ c1 = Coque["printf", '"a\nb\nc\n"']
44
+ c2 = Coque.rb { |line| puts line.upcase }
45
+ (c1 | c2).run.to_a
46
+ # => ["A", "B", "C"]
47
+ ```
48
+
49
+ Rb commands can also take "pre" and "post" blocks
50
+
51
+ ```
52
+ dict = Coque["cat", "/usr/share/dict/words"]
53
+ rb_wc = Coque.rb { @lines += 1 }.pre { @lines = 0 }.post { puts @lines }
54
+
55
+ (dict | rb_wc).run.to_a
56
+ # => ["235886"]
57
+ ```
58
+
59
+ Commands can have Stdin, Stdout, and Stderr redirected
60
+
61
+ ```rb
62
+ (Coque["echo", "hi"] > "/tmp/hi.txt").run.wait
63
+ File.read("/tmp/hi.txt")
64
+ # => "hi\n"
65
+
66
+ (Coque["head", "-n", "4"] < "/usr/share/dict/words").run.to_a
67
+ # => ["A", "a", "aa", "aal"]
68
+
69
+ (Coque["cat", "/doesntexist.txt"] >= "/tmp/error.txt").run.wait
70
+ File.read("/tmp/error.txt")
71
+ # => "cat: /doesntexist.txt: No such file or directory\n"
72
+ ```
73
+
74
+ Coque commands can also be derived from a `Coque::Context`:
75
+
76
+ ```rb
77
+ c = Coque.context
78
+ c["pwd"].run.to_a
79
+ # => ["/Users/worace/code/coque"]
80
+
81
+ Coque.context.chdir("/tmp")["pwd"].run.to_a
82
+ # => ["/private/tmp"]
83
+
84
+ Coque.context.setenv("my_key": "pizza")["echo", "$my_key"].run.to_a
85
+ # => ["pizza"]
86
+ ```
87
+
88
+ ### Streaming Performance
89
+
90
+ Should be little overhead compared with the equivalent pipeline from a standard shell.
91
+
92
+ From zsh:
93
+
94
+ ```
95
+ head -c 100000000 /dev/urandom | pv | wc -c
96
+ 95.4MiB 0:00:06 [14.1MiB/s] [ <=> ]
97
+ 100000000
98
+ ```
99
+
100
+ With coque:
101
+
102
+ ```rb
103
+ p = Coque["head", "-c", "100000000", "/dev/urandom"] | Coque["pv"] | Coque["wc", "-c"]
104
+ p.run.wait
105
+ 95.4MiB 0:00:06 [14.6MiB/s] [ <=> ]
106
+ ```
107
+
108
+ ## Development
109
+
110
+ * Setup local environment with standard `bundle`
111
+ * Run tests with `rake`
112
+ * See code coverage output in `coverage/`
113
+ * Start a pry console with `bin/console`
114
+ * Install current dev version with `rake install`
115
+ * Use `rake release` to release after bumping `lib/coque/version.rb`
116
+ * New issues welcome
117
+
118
+ ## Further Reading / Prior Art
119
+
120
+ The concept and API for this library was heavily inspired by Python's excellent [Plumbum](https://plumbum.readthedocs.io/en/latest/) library.
121
+
122
+ I relied on many resources to understand Ruby's great facilities for Process creation and manipulation. Some highlights include:
123
+
124
+ * Avdi Grimm's _A dozen (or so) ways to start sub-processes in Ruby_ ([Part 1](https://devver.wordpress.com/2009/06/30/a-dozen-or-so-ways-to-start-sub-processes-in-ruby-part-1/), [Part 2](https://devver.wordpress.com/2009/07/13/a-dozen-or-so-ways-to-start-sub-processes-in-ruby-part-2/), [Part 3](https://devver.wordpress.com/2009/10/12/ruby-subprocesses-part_3/))
125
+ * Ryan Tomayko's [I like Unicorn because it's Unix](https://tomayko.com/blog/2009/unicorn-is-unix)
126
+ * Jesse Storimer's [Working With Unix Processes](https://www.jstorimer.com/products/working-with-unix-processes)
127
+ * Brandon Wamboldt's blog series: [How bash redirection works](https://brandonwamboldt.ca/how-bash-redirection-works-under-the-hood-1512/), [How Linux pipes work](https://brandonwamboldt.ca/how-linux-pipes-work-under-the-hood-1518/), and [Understanding how Linux creates processes](https://brandonwamboldt.ca/how-linux-creates-processes-1528/)
128
+
129
+ ## License
130
+
131
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
132
+
133
+ ## Code of Conduct
134
+
135
+ Everyone interacting in the Coque project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[worace]/coque/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ task :default => :test
data/bin/console ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "coque"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ require "pry"
11
+ Pry.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/coque.gemspec ADDED
@@ -0,0 +1,39 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "coque/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "coque"
8
+ spec.version = Coque::VERSION
9
+ spec.authors = ["Horace Williams"]
10
+ spec.email = ["horace@worace.works"]
11
+
12
+ spec.summary = %q{Shell command utilities with easy integration with Ruby code.}
13
+ spec.description = %q{Like plumbum...for Ruby...with native Ruby streaming interop.}
14
+ spec.homepage = "http://github.com/worace/coque"
15
+ spec.license = "MIT"
16
+
17
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
18
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
19
+ if spec.respond_to?(:metadata)
20
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
21
+ else
22
+ raise "RubyGems 2.0 or newer is required to protect against " \
23
+ "public gem pushes."
24
+ end
25
+
26
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
27
+ f.match(%r{^(test|spec|features)/})
28
+ end
29
+ spec.bindir = "exe"
30
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
31
+ spec.require_paths = ["lib"]
32
+
33
+ spec.add_development_dependency "bundler", "~> 1.16"
34
+ spec.add_development_dependency "rake", "~> 12.3"
35
+ spec.add_development_dependency "minitest", "~> 5.11"
36
+ spec.add_development_dependency "minitest-reporters"
37
+ spec.add_development_dependency "pry"
38
+ spec.add_development_dependency "simplecov"
39
+ end
data/drake.rb ADDED
@@ -0,0 +1,113 @@
1
+ require "open3"
2
+ require "pry"
3
+
4
+ class Pipeline
5
+ attr_reader :commands
6
+ def initialize(commands = [])
7
+ @commands = commands
8
+ end
9
+
10
+ def to_s
11
+ "Pipeline of #{commands.join("|")}"
12
+ end
13
+
14
+ def |(other)
15
+ case other
16
+ when Pipeline
17
+ Pipeline.new(commands + other.commands)
18
+ when Cmd
19
+ Pipeline.new(commands + [other])
20
+ when Crb
21
+ Pipeline.new(commands + [other])
22
+ end
23
+ end
24
+
25
+ def run
26
+ commands = self.commands
27
+ pids = []
28
+
29
+ in_read, in_write = IO.pipe
30
+ out_read = nil
31
+ out_write = nil
32
+
33
+ while commands.any? do
34
+ in_write.close
35
+ out_read, out_write = IO.pipe
36
+
37
+ cmd = commands.shift
38
+ puts "Spawn command: #{cmd}"
39
+ pids << cmd.run(in_read, out_write)
40
+
41
+ in_read = out_read
42
+ in_write = out_write
43
+ end
44
+ out_write.close
45
+ [pids, out_read]
46
+ end
47
+ end
48
+
49
+ class Cmd
50
+ attr_reader :args
51
+ def initialize(args)
52
+ @args = args
53
+ end
54
+
55
+ def to_s
56
+ "Cmd to run: `#{args.inspect}`"
57
+ end
58
+
59
+ def self.[](*args)
60
+ puts args.inspect
61
+ Cmd.new(args)
62
+ end
63
+
64
+ def command
65
+ args.join(" ")
66
+ end
67
+
68
+ def run(stdin, stdout)
69
+ spawn(args.join(" "), in: stdin, stdin => stdin, out: stdout, stdout => stdout)
70
+ end
71
+
72
+ def |(other)
73
+ case other
74
+ when Cmd
75
+ Pipeline.new([self, other])
76
+ when Crb
77
+ Pipeline.new([self, other])
78
+ when Pipeline
79
+ Pipeline.new([self] + other.commands)
80
+ end
81
+ end
82
+ end
83
+
84
+ class Crb
85
+ def initialize(&block)
86
+ @block = block
87
+ end
88
+
89
+ def run(stdin, stdout)
90
+ fork do
91
+ STDOUT.reopen(stdout)
92
+ stdin.each_line(&@block)
93
+ end
94
+ end
95
+
96
+ def |(other)
97
+ case other
98
+ when Cmd
99
+ Pipeline.new([self, other])
100
+ when Crb
101
+ Pipeline.new([self, other])
102
+ when Pipeline
103
+ Pipeline.new([self] + other.commands)
104
+ end
105
+ end
106
+ end
107
+
108
+
109
+ c = Cmd['cat', '/usr/share/dict/words'] | Cmd['head'] | Crb.new { |line| puts "crb - #{line}" }
110
+ puts "pipeline: #{c}"
111
+ pids, out = c.run
112
+ puts "Spawned pids: #{pids}"
113
+ out.each_line { |l| puts l }