noms-command 0.5.0

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.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +191 -0
  5. data/README.rst +376 -0
  6. data/ROADMAP.rst +127 -0
  7. data/Rakefile +49 -0
  8. data/TODO.rst +7 -0
  9. data/bin/noms2 +20 -0
  10. data/fixture/dnc.rb +120 -0
  11. data/fixture/identity +5 -0
  12. data/fixture/public/dnc.json +22 -0
  13. data/fixture/public/echo.json +7 -0
  14. data/fixture/public/files/data.json +12 -0
  15. data/fixture/public/files/foo.json +2 -0
  16. data/fixture/public/lib/dnc.js +81 -0
  17. data/fixture/public/lib/noms-args.js +13 -0
  18. data/fixture/public/lib/showopt.js +18 -0
  19. data/fixture/public/location.json +8 -0
  20. data/fixture/public/showopt.json +15 -0
  21. data/fixture/rig2json +21 -0
  22. data/lib/noms/command/application.rb +204 -0
  23. data/lib/noms/command/auth/identity.rb +62 -0
  24. data/lib/noms/command/auth.rb +117 -0
  25. data/lib/noms/command/base.rb +22 -0
  26. data/lib/noms/command/document.rb +59 -0
  27. data/lib/noms/command/error.rb +11 -0
  28. data/lib/noms/command/formatter.rb +178 -0
  29. data/lib/noms/command/urinion/data.rb +63 -0
  30. data/lib/noms/command/urinion.rb +29 -0
  31. data/lib/noms/command/useragent.rb +134 -0
  32. data/lib/noms/command/version.rb +7 -0
  33. data/lib/noms/command/window.rb +95 -0
  34. data/lib/noms/command/xmlhttprequest.rb +181 -0
  35. data/lib/noms/command.rb +107 -0
  36. data/noms-command.gemspec +30 -0
  37. data/spec/01noms-command_spec.rb +30 -0
  38. data/spec/02noms-command.sh +31 -0
  39. data/spec/03application_spec.rb +47 -0
  40. data/spec/04application_spec.rb +61 -0
  41. data/spec/05formatter_spec.rb +195 -0
  42. data/spec/06urinion_data.rb +20 -0
  43. data/spec/07js_spec.rb +87 -0
  44. data/spec/08xhr_spec.rb +209 -0
  45. data/spec/09bookmarks_spec.rb +60 -0
  46. data/spec/10auth_spec.rb +33 -0
  47. data/spec/spec_helper.rb +40 -0
  48. metadata +228 -0
@@ -0,0 +1,63 @@
1
+ #!ruby
2
+
3
+ require 'base64'
4
+
5
+ class NOMS
6
+
7
+ end
8
+
9
+ class NOMS::Command
10
+
11
+ end
12
+
13
+ class NOMS::Command::URInion
14
+
15
+ end
16
+
17
+ class NOMS::Command::URInion::Data
18
+
19
+ attr_accessor :scheme, :host, :port, :fragment, :query, :data, :path,
20
+ :data_encoding, :mime_type, :user, :password, :raw_data,
21
+ :character_set
22
+
23
+ def self.parse(urltext)
24
+ self.new(urltext)
25
+ end
26
+
27
+ def initialize(urltext)
28
+ @host = nil
29
+ @port = nil
30
+ @fragment = nil
31
+ @user = nil
32
+ @path = nil
33
+ @password = nil
34
+ @character_set = nil
35
+
36
+ @scheme, rest = urltext.split(':', 2)
37
+ raise NOMS::Command::Error.new "URL is not data: (scheme = #{@scheme})" unless @scheme == 'data'
38
+ meta, @raw_data = rest.split(',', 2)
39
+ fields = meta.split(';')
40
+ if fields[-1] == 'base64'
41
+ @data_encoding = fields.pop
42
+ end
43
+
44
+ unless fields[0].nil? or fields[0].empty?
45
+ @mime_type = fields.shift
46
+ end
47
+
48
+ fields.each do |field|
49
+ if m = /charset=(.*)/.match(field)
50
+ @character_set = m[1]
51
+ end
52
+ end
53
+
54
+ case @data_encoding
55
+ when 'base64'
56
+ @data = Base64.urlsafe_decode64
57
+ else
58
+ @data = @raw_data
59
+ end
60
+
61
+ end
62
+
63
+ end
@@ -0,0 +1,29 @@
1
+ #!ruby
2
+
3
+ require 'uri'
4
+ require 'noms/command/urinion/data'
5
+
6
+ class NOMS
7
+
8
+ end
9
+
10
+ class NOMS::Command
11
+
12
+ end
13
+
14
+ class NOMS::Command::URInion
15
+
16
+ def self.parse(url)
17
+ if url.respond_to? :scheme
18
+ url
19
+ else
20
+ case url
21
+ when /^data/
22
+ NOMS::Command::URInion::Data.parse(url)
23
+ else
24
+ URI.parse(url)
25
+ end
26
+ end
27
+ end
28
+
29
+ end
@@ -0,0 +1,134 @@
1
+ #!ruby
2
+
3
+ require 'noms/command/version'
4
+
5
+ require 'openssl'
6
+ require 'httpclient'
7
+ require 'uri'
8
+ require 'highline/import'
9
+
10
+ require 'noms/command/auth'
11
+ require 'noms/command/base'
12
+
13
+ class NOMS
14
+
15
+ end
16
+
17
+ class NOMS::Command
18
+
19
+ end
20
+
21
+ class NOMS::Command::UserAgent < NOMS::Command::Base
22
+
23
+ def initialize(origin, attrs={})
24
+ @origin = origin
25
+ @client = HTTPClient.new :agent_name => "noms/#{NOMS::Command::VERSION}"
26
+ # TODO Replace with TOFU implementation
27
+ @client.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
28
+ @redirect_checks = [ ]
29
+ @log = attrs[:logger] || default_logger
30
+
31
+ @log.debug "(UserAgent) specified identities = #{attrs[:specified_identities]}"
32
+ @auth = NOMS::Command::Auth.new(:logger => @log,
33
+ :specified_identities => (attrs[:specified_identities] || []))
34
+ # TODO: Set cookie jar to something origin-specific
35
+ # TODO: Set user-agent to something nomsy
36
+ # caching
37
+ @client.redirect_uri_callback = lambda do |uri, res|
38
+ raise NOMS::Command::Error.new "Bad redirect URL #{url}" unless check_redirect(uri)
39
+ @client.default_redirect_uri_callback(uri, res)
40
+ end
41
+ end
42
+
43
+ def auth
44
+ @auth
45
+ end
46
+
47
+ def check_redirect(url)
48
+ @log.debug "Running #{@redirect_checks.size} redirect checks on #{url}" unless @redirect_checks.empty?
49
+ @redirect_checks.all? { |check| check.call(url) }
50
+ end
51
+
52
+ def origin=(new_origin)
53
+ @log.debug "Setting my origin to #{new_origin}"
54
+ @origin = new_origin
55
+ end
56
+
57
+ def absolute_url(url)
58
+ @log.debug "Calculating absolute url of #{url} in context of #{@origin}"
59
+ begin
60
+ url = URI.parse url unless url.respond_to? :scheme
61
+ url = URI.join(@origin, url) unless url.absolute?
62
+ url
63
+ rescue StandardError => e
64
+ raise NOMS::Command::Error.new "Error parsing URL #{url} in context of #{@origin} (#{e.class}): #{e.message}"
65
+ end
66
+ end
67
+
68
+ def request(method, url, data=nil, headers={}, tries=10, identity=nil)
69
+ req_url = absolute_url(url)
70
+ @log.debug "#{method} #{req_url}" + (headers.empty? ? '' : headers.inspect)
71
+ response = @client.request(method.to_s.upcase, req_url, '', data, headers)
72
+ @log.debug "-> #{response.status} #{response.reason} (#{response.content.size} bytes of #{response.contenttype})"
73
+ @log.debug JSON.pretty_generate(response.headers)
74
+ case response.status
75
+ when 401
76
+ @log.debug " handling unauthorized"
77
+ if identity
78
+ @log.debug " we have an identity #{identity} but are trying again"
79
+ if tries > 0
80
+ @log.debug "loading authentication identity for #{url}"
81
+ identity = @auth.load(url, response)
82
+ @client.set_auth(identity['domain'], identity['username'], identity['password'])
83
+ response, req_url = self.request(method, url, data, headers, tries - 1, identity)
84
+ end
85
+ else
86
+ identity = @auth.load(url, response)
87
+ @client.set_auth(identity['domain'], identity['username'], identity['password'])
88
+ response, req_url = self.request(method, url, data, headers, 2, identity)
89
+ end
90
+ when 302, 301
91
+ new_url = response.header['location'].first
92
+ if check_redirect new_url
93
+ @log.debug "redirect to #{new_url}"
94
+ raise NOMS::Command::Error.new "Can't follow redirect to #{new_url}: too many redirects" if tries <= 0
95
+ response, req_url = self.request(method, new_url, data, headers, tries - 1)
96
+ end
97
+ end
98
+
99
+ if identity and response.ok?
100
+ @log.debug "Login succeeded, saving #{identity}"
101
+ identity.save
102
+ end
103
+
104
+ @log.debug "<- #{response.status} #{response.reason} <- #{req_url}"
105
+ [response, req_url]
106
+ end
107
+
108
+ def get(url, headers={})
109
+ request('GET', url, nil, headers)
110
+ end
111
+
112
+ # Wait for all asynchronous requests to complete.
113
+ # A stub while these are simulated
114
+ def wait(on=nil)
115
+ []
116
+ end
117
+
118
+ def add_redirect_check(&block)
119
+ @log.debug "Adding #{block} to redirect checks"
120
+ @redirect_checks << block
121
+ end
122
+
123
+ def clear_redirect_checks
124
+ @log.debug "Clearing redirect checks"
125
+ @redirect_checks = [ ]
126
+ end
127
+
128
+ def pop_redirect_check
129
+ unless @redirect_checks.empty?
130
+ @log.debug "Popping redirect check: #{@redirect_checks[-1]}"
131
+ @redirect_checks.pop
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,7 @@
1
+ class NOMS
2
+
3
+ end
4
+
5
+ class NOMS::Command
6
+ VERSION = "0.5.0"
7
+ end
@@ -0,0 +1,95 @@
1
+ #!ruby
2
+
3
+ require 'noms/command/version'
4
+
5
+ require 'highline/import'
6
+
7
+ require 'noms/command/base'
8
+
9
+ class NOMS
10
+
11
+ end
12
+
13
+ class NOMS::Command
14
+
15
+ end
16
+
17
+ class NOMS::Command::Window < NOMS::Command::Base
18
+
19
+ attr_accessor :document, :name, :origin
20
+ attr_reader :console, :origin, :location
21
+
22
+ def initialize(invoker=$0, origin=nil, opt={})
23
+ @document = nil
24
+ @origin = origin
25
+ @name = invoker
26
+ @log = opt[:logger] || default_logger
27
+ @console = NOMS::Command::Window::Console.new(@log)
28
+ @location = NOMS::Command::Window::Location.new(self)
29
+ end
30
+
31
+ def isatty()
32
+ $stdout.tty?
33
+ end
34
+
35
+ def alert(msg)
36
+ @log.error msg
37
+ end
38
+
39
+ def prompt(prompt_text, echo=true)
40
+ echo = true if echo.nil?
41
+ @log.debug "prompt(#{prompt_text.inspect}, #{echo.inspect})"
42
+ v = ask(prompt_text) { |p| p.echo = false unless echo }
43
+ @log.debug "-> #{v}"
44
+ v
45
+ end
46
+
47
+ end
48
+
49
+ class NOMS::Command::Window::Console < NOMS::Command::Base
50
+
51
+ def initialize(logger=nil)
52
+ @log = logger || default_logger
53
+ end
54
+
55
+ # Some implementations have a kind of format string. I don't
56
+ def log(*items)
57
+ @log.debug(items.map { |s| _sanitize(s) }.join(', '))
58
+ end
59
+
60
+ def _sanitize(s)
61
+ s.respond_to?(:to_str) ? s : s.to_json
62
+ end
63
+
64
+ end
65
+
66
+ class NOMS::Command::Window::Location
67
+
68
+ { :protocol => :scheme,
69
+ :origin => :to_s,
70
+ :pathname => :path,
71
+ :href => :to_s,
72
+ :hostname => :host,
73
+ :hash => :fragment,
74
+ :search => :query,
75
+ :port => :port
76
+ }.each do |loc_attr, uri_attr|
77
+ define_method(loc_attr) do
78
+ self.field uri_attr
79
+ end
80
+ end
81
+
82
+ def initialize(window)
83
+ @window = window
84
+ # Setup simple equivalences
85
+ end
86
+
87
+ def field(name)
88
+ @window.origin.send(name) if @window.origin.respond_to? name
89
+ end
90
+
91
+ def host
92
+ [self.hostname, self.port].join(':')
93
+ end
94
+
95
+ end
@@ -0,0 +1,181 @@
1
+ #!/usr/bin/env
2
+
3
+ require 'noms/command/urinion'
4
+ require 'noms/command/useragent'
5
+ require 'noms/command/error'
6
+
7
+ require 'httpclient'
8
+
9
+ class NOMS
10
+
11
+ end
12
+
13
+ class NOMS::Command
14
+
15
+ end
16
+
17
+ # Implements a subset of the XMLHttpRequest interface. This
18
+ # class is exposed as a constructor function in the Javascript
19
+ # VM. It uses NOMS::Command::UserAgent for HTTP conversations,
20
+ # adding checks for the same-origin policy.
21
+ class NOMS::Command::XMLHttpRequest
22
+
23
+ OPENED = 1
24
+ HEADERS_RECEIVED = 2
25
+ LOADING = 3
26
+ DONE = 4
27
+
28
+ @@origin = nil
29
+ @@ua = nil
30
+
31
+ def self.origin
32
+ @@origin
33
+ end
34
+
35
+ def self.origin=(origin)
36
+ @@origin = NOMS::Command::URInion.parse(origin)
37
+ end
38
+
39
+ def self.useragent
40
+ @@ua
41
+ end
42
+
43
+ # Set the class attribute for the UserAgent; ideally
44
+ # this is shared among everything in one *noms*
45
+ # invocation.
46
+ def self.useragent=(ua)
47
+ @@ua = ua
48
+ end
49
+
50
+ attr_accessor :responseText, :headers
51
+
52
+ def initialize()
53
+ @origin = @@origin
54
+ @ua = @@ua || NOMS::Command::UserAgent.new(@origin)
55
+ @headers = { }
56
+ @readyState = 0
57
+ end
58
+
59
+ def useragent
60
+ @ua
61
+ end
62
+
63
+ # Checks whether the URL has the same origin
64
+ # as the "origin" URL
65
+ def same_origin?(other)
66
+ other = NOMS::Command::URInion.parse(other)
67
+ return false unless @origin.scheme == other.scheme
68
+ return false unless @origin.host == other.host
69
+ return false unless @origin.port == other.port
70
+ return true
71
+ end
72
+
73
+ def open(method, url, async=true, user=nil, password=nil)
74
+ raise NOMS::Command::Error.new "origin of #{url} doesn't match application origin (#{@origin})" unless
75
+ same_origin? @ua.absolute_url(url)
76
+ # Should we run onreadystatechange when resetting this? Not doing it for now.
77
+ @readyState = 0
78
+ @responseText = ''
79
+ @response = nil
80
+ @method = method
81
+ @url = url
82
+ @async = async
83
+ @user = user
84
+ @password = password
85
+ end
86
+
87
+ def onreadystatechange
88
+ @onreadystatechange
89
+ end
90
+
91
+ def onreadystatechange=(callback)
92
+ @onreadystatechange = callback
93
+ end
94
+
95
+ def readyState
96
+ @readyState
97
+ end
98
+
99
+ def readyState=(value)
100
+ @readyState = value
101
+ unless @onreadystatechange.nil?
102
+ @onreadystatechange.methodcall self
103
+ end
104
+ @readyState
105
+ end
106
+
107
+ def setRequestHeader(header, value)
108
+ @headers[header] = value
109
+ end
110
+
111
+ def OPENED
112
+ OPENED
113
+ end
114
+
115
+ def HEADERS_RECEIVED
116
+ HEADERS_RECEIVED
117
+ end
118
+
119
+ def LOADING
120
+ LOADING
121
+ end
122
+
123
+ def DONE
124
+ DONE
125
+ end
126
+
127
+ # This problematic implementations attempts to "guess"
128
+ # when Object#send is intended, and when the Javascript-
129
+ # style xhr.send() is desired. The one ambiguous situation
130
+ # it can't recover from is when there is one string argument,
131
+ # this is a valid invocation of XMLHttpRequest#send as well
132
+ # as Object#send, and we settle on XMLHttpRequest#send.
133
+ def send(*vary)
134
+ if vary.size == 0 or (vary.size == 1 and ! vary[0].is_a? Symbol)
135
+ do_send(*vary)
136
+ else
137
+ super
138
+ end
139
+ end
140
+
141
+ # NOMS::Command::UserAgent doesn't do async
142
+ # calls (yet) since httpclient doesn't do
143
+ # anything special with them and you can
144
+ # only busy-wait on them. So they're "simulated",
145
+ # and by "simulated" I mean "performed synchronously".
146
+ def do_send(data=nil)
147
+ # @async ignored
148
+ @ua.add_redirect_check do |url|
149
+ self.same_origin? url
150
+ end
151
+ @response, landing_url = @ua.request(@method, @url, data, @headers)
152
+ # We don't need the 'landing' URL
153
+ @ua.pop_redirect_check
154
+ self.readyState = OPENED
155
+ self.readyState = HEADERS_RECEIVED
156
+ self.readyState = LOADING
157
+ @responseText = @response.content
158
+ self.readyState = DONE
159
+ end
160
+
161
+ def status
162
+ @response.status.to_i unless @response.nil?
163
+ end
164
+
165
+ def statusText
166
+ @response.status + ' ' + @response.reason unless @response.nil?
167
+ end
168
+
169
+ def getResponseHeader(header)
170
+ @response.header[header.downcase] unless @response.nil?
171
+ end
172
+
173
+ def getAllResponseHeaders
174
+ lambda { || @response.headers.map { |h, v| "#{h}: #{v}" }.join("\n") + "\n" }
175
+ end
176
+
177
+ def abort()
178
+ lambda { || @readyState = 0 }
179
+ end
180
+
181
+ end
@@ -0,0 +1,107 @@
1
+ #!ruby
2
+
3
+ require 'noms/command/version'
4
+
5
+ require 'trollop'
6
+ require 'logger'
7
+
8
+ require 'noms/command/window'
9
+ require 'noms/command/application'
10
+ require 'noms/command/formatter'
11
+
12
+ class NOMS
13
+
14
+ end
15
+
16
+ class NOMS::Command
17
+
18
+ def self.run(argv)
19
+ runner = self.new(argv)
20
+ runner.run
21
+ end
22
+
23
+ def initialize(argv)
24
+ @argv = argv
25
+ @log = Logger.new($stderr)
26
+ @log.formatter = lambda { |sev, timestamp, prog, msg| msg[-1].chr == "\n" ? msg : msg + "\n" }
27
+ end
28
+
29
+ def run
30
+ # Find my own configuration for bookmarks and stuff
31
+ # Interpret either environment variables or (maybe) initial options
32
+ parser = Trollop::Parser.new do
33
+ version NOMS::Command::VERSION
34
+ banner <<-USAGE.gsub(/^\s{16}/,'')
35
+ Usage:
36
+ noms [noms-options] { bookmark | url } [options] [arguments]
37
+ noms-options:
38
+ USAGE
39
+ opt :identity, "Identity file", :type => :string, :multi => true
40
+ opt :verbose, "Enable verbose output"
41
+ opt :list, "List bookmarks"
42
+ opt :bookmarks, "Bookmark file location (can be specified multiple times)",
43
+ :type => :string,
44
+ :multi => true
45
+ opt :nodefault_bookmarks, "Don't consult default bookmarks files",
46
+ :short => '-X',
47
+ :long => '--nodefault-bookmarks'
48
+ opt :debug, "Enable debug output"
49
+ stop_on_unknown
50
+ end
51
+
52
+ @opt = Trollop::with_standard_exception_handling parser do
53
+ parser.parse(@argv)
54
+ end
55
+
56
+ Trollop::with_standard_exception_handling parser do
57
+ raise Trollop::HelpNeeded if @argv.empty? and ! @opt[:list]
58
+ end
59
+
60
+ @opt[:debug] = true if ENV['NOMS_DEBUG'] and ! ENV['NOMS_DEBUG'].empty?
61
+
62
+ default_bookmarks =
63
+ [ File.join(ENV['HOME'], '.noms/bookmarks.json'),
64
+ '/usr/local/etc/noms/bookmarks.json',
65
+ '/etc/noms/bookmarks.json'].select { |f| File.exist? f }
66
+
67
+ @opt[:bookmarks].concat default_bookmarks unless @opt[:nodefault_bookmarks]
68
+
69
+ @log.level = Logger::WARN
70
+ @log.level = Logger::INFO if @opt[:verbose]
71
+ @log.level = Logger::DEBUG if @opt[:debug]
72
+
73
+ @bookmark = @opt[:bookmarks].map do |file|
74
+ begin
75
+ File.open(file, 'r') { |fh| JSON.load fh }
76
+ rescue JSON::ParserError => j
77
+ @log.warn "Couldn't load bookmarks from invalid JSON file #{file.inspect}"
78
+ @log.debug "JSON error: #{file.inspect}:#{j.message}"
79
+ nil
80
+ rescue StandardError => e
81
+ @log.warn "Couldn't open #{file.inspect} (#{e.class}): #{e.message}"
82
+ @log.debug "Error opening #{file.inspect}: #{e.backtrace.join("\n")}"
83
+ nil
84
+ end
85
+ end.compact.reverse.inject({}) { |h1, h2| h1.merge h2 }
86
+
87
+ if @opt[:list]
88
+ puts @bookmark.map { |pair| '%-15s -> %s' % pair }
89
+ return 0
90
+ end
91
+
92
+ begin
93
+ origin = @bookmark[@argv[0].split('/').first] || @argv[0]
94
+ app = NOMS::Command::Application.new(origin, @argv, :logger => @log,
95
+ :specified_identities => @opt[:identity])
96
+ app.fetch! # Retrieve page
97
+ app.render! # Run scripts
98
+ out = app.display
99
+ puts out unless out.empty?
100
+ app.exitcode # Return exitcode
101
+ rescue NOMS::Command::Error => e
102
+ @log.error "noms error: #{e.message}"
103
+ 255
104
+ end
105
+ end
106
+
107
+ end
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'noms/command/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "noms-command"
8
+ spec.version = NOMS::Command::VERSION
9
+ spec.authors = ["Jeremy Brinkley"]
10
+ spec.email = ["jbrinkley@evernote.com"]
11
+ spec.summary = %q{Interpreter for server-defined command-line interfaces}
12
+ spec.homepage = "http://github.com/en-jbrinkley/noms-command"
13
+ spec.license = "Apache-2"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_runtime_dependency "therubyracer"
21
+ spec.add_runtime_dependency "mime-types"
22
+ spec.add_runtime_dependency "httpclient"
23
+ spec.add_runtime_dependency "json"
24
+ spec.add_runtime_dependency "trollop"
25
+ spec.add_runtime_dependency "highline"
26
+
27
+ spec.add_development_dependency "bundler", "~> 1.7"
28
+ spec.add_development_dependency "rake", "~> 10.0"
29
+ spec.add_development_dependency "sinatra"
30
+ end
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env rspec
2
+
3
+ require 'spec_helper'
4
+
5
+ require 'noms/command'
6
+
7
+ describe NOMS::Command do
8
+
9
+ before(:all) do
10
+ setup_fixture 'test'
11
+ File.open('test/foo.txt', 'w') do |fh|
12
+ fh << <<-TEXT.gsub(/^\s+/,'')
13
+ 1: Test output
14
+ 2: from foo.txt
15
+ TEXT
16
+ end
17
+ end
18
+
19
+ after(:all) { teardown_fixture 'test' }
20
+
21
+ describe '.run' do
22
+ context 'with one file argument' do
23
+ it 'shows the file contents' do
24
+ file = 'file://' + File.join(Dir.pwd, 'test', 'foo.txt')
25
+ expect { NOMS::Command.run([file]) }.to output(/from foo.txt/).to_stdout
26
+ end
27
+ end
28
+ end
29
+
30
+ end