command_exec 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 ADDED
@@ -0,0 +1,52 @@
1
+ .$*$
2
+ .*
3
+ *.*~
4
+ *.acn
5
+ *.acr
6
+ *.alg
7
+ *.aux
8
+ *.auxlock
9
+ *.bak
10
+ *.bbl
11
+ *.blg
12
+ .bundle
13
+ coverage/
14
+ doc/yardoc/
15
+ *.dvi
16
+ *.fdb_latexmk
17
+ *.fls
18
+ *.gem
19
+ !.git*
20
+ *.glg
21
+ *.glo
22
+ *.gls
23
+ *.htm
24
+ *.html
25
+ *.idx
26
+ *.ilg
27
+ *.ind
28
+ *.ist
29
+ *.loc
30
+ *.lof
31
+ *.log
32
+ *.log
33
+ *.lol
34
+ *.lot
35
+ *.nlo
36
+ *.nls
37
+ nohup*
38
+ *.old
39
+ *.out
40
+ pkg/
41
+ pkg/*
42
+ .rvmrc
43
+ *.s??
44
+ *.s*p
45
+ *.synctex.gz*
46
+ *.tex.meta
47
+ *.toc
48
+ .yardoc
49
+ tmp/
50
+ *.pdf
51
+ !doc/*/*.pdf
52
+ *.pid
data/Gemfile ADDED
@@ -0,0 +1,24 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in ruby-pdflatex.gemspec
4
+ gemspec
5
+
6
+ group :development do
7
+ gem 'cucumber'
8
+ gem 'fakefs'
9
+ gem 'fuubar'
10
+ gem 'github-markup'
11
+ gem 'guard'
12
+ gem 'guard-cucumber'
13
+ gem 'guard-rspec'
14
+ gem 'guard-yard'
15
+ gem 'libnotify'
16
+ gem 'pry'
17
+ gem 'rb-inotify'
18
+ gem 'rb-readline'
19
+ gem 'redcarpet' , '~>1.17.2'
20
+ gem 'rspec'
21
+ gem 'simplecov'
22
+ gem 'terminal_multiplexer'
23
+ gem 'yard'
24
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,101 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ command_exec (0.0.1)
5
+ POpen4
6
+ colored
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ POpen4 (0.1.4)
12
+ Platform (>= 0.4.0)
13
+ open4
14
+ Platform (0.4.0)
15
+ builder (3.0.0)
16
+ coderay (1.0.5)
17
+ colored (1.2)
18
+ cucumber (1.1.9)
19
+ builder (>= 2.1.2)
20
+ diff-lcs (>= 1.1.2)
21
+ gherkin (~> 2.9.0)
22
+ json (>= 1.4.6)
23
+ term-ansicolor (>= 1.0.6)
24
+ diff-lcs (1.1.3)
25
+ fakefs (0.4.0)
26
+ ffi (1.0.11)
27
+ fuubar (1.0.0)
28
+ rspec (~> 2.0)
29
+ rspec-instafail (~> 0.2.0)
30
+ ruby-progressbar (~> 0.0.10)
31
+ gherkin (2.9.1)
32
+ json (>= 1.4.6)
33
+ github-markup (0.7.1)
34
+ guard (1.0.1)
35
+ ffi (>= 0.5.0)
36
+ thor (~> 0.14.6)
37
+ guard-cucumber (0.7.5)
38
+ cucumber (>= 0.10)
39
+ guard (>= 0.8.3)
40
+ guard-rspec (0.6.0)
41
+ guard (>= 0.10.0)
42
+ guard-yard (1.0.2)
43
+ guard (>= 0.2.2)
44
+ yard (>= 0.7.0)
45
+ highline (1.6.11)
46
+ json (1.6.5)
47
+ libnotify (0.7.2)
48
+ method_source (0.7.1)
49
+ multi_json (1.1.0)
50
+ open4 (1.3.0)
51
+ pry (0.9.8.4)
52
+ coderay (~> 1.0.5)
53
+ method_source (~> 0.7.1)
54
+ slop (>= 2.4.4, < 3)
55
+ rb-inotify (0.8.8)
56
+ ffi (>= 0.5.0)
57
+ rb-readline (0.4.2)
58
+ redcarpet (1.17.2)
59
+ rspec (2.9.0)
60
+ rspec-core (~> 2.9.0)
61
+ rspec-expectations (~> 2.9.0)
62
+ rspec-mocks (~> 2.9.0)
63
+ rspec-core (2.9.0)
64
+ rspec-expectations (2.9.0)
65
+ diff-lcs (~> 1.1.3)
66
+ rspec-instafail (0.2.2)
67
+ rspec-mocks (2.9.0)
68
+ ruby-progressbar (0.0.10)
69
+ simplecov (0.6.1)
70
+ multi_json (~> 1.0)
71
+ simplecov-html (~> 0.5.3)
72
+ simplecov-html (0.5.3)
73
+ slop (2.4.4)
74
+ term-ansicolor (1.0.7)
75
+ terminal_multiplexer (1.0.2)
76
+ highline
77
+ thor (0.14.6)
78
+ yard (0.7.5)
79
+
80
+ PLATFORMS
81
+ ruby
82
+
83
+ DEPENDENCIES
84
+ command_exec!
85
+ cucumber
86
+ fakefs
87
+ fuubar
88
+ github-markup
89
+ guard
90
+ guard-cucumber
91
+ guard-rspec
92
+ guard-yard
93
+ libnotify
94
+ pry
95
+ rb-inotify
96
+ rb-readline
97
+ redcarpet (~> 1.17.2)
98
+ rspec
99
+ simplecov
100
+ terminal_multiplexer
101
+ yard
data/Guardfile ADDED
@@ -0,0 +1,9 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'rspec', :version => 2 do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
7
+ watch('spec/spec_helper.rb') { "spec" }
8
+ end
9
+
data/README.md ADDED
@@ -0,0 +1,6 @@
1
+ command-exec(1) -- execute shell commands with ease
2
+ ===================================================
3
+
4
+ ## DESCRIPTION
5
+
6
+
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "command_exec/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "command_exec"
7
+ s.version = CommandExec::VERSION
8
+ s.authors = ["Max Meyer"]
9
+ s.email = ["dev@fedux.org"]
10
+ s.homepage = ""
11
+ s.summary = %q{Helper gem to exectue arbitrary shell commands}
12
+ s.description = %q{This adds bells and whistles to ease shell command execution}
13
+
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
+ s.require_paths = ['lib']
18
+
19
+ # specify any dependencies here; for example:
20
+ s.add_runtime_dependency 'POpen4'
21
+ s.add_runtime_dependency 'colored'
22
+ end
@@ -0,0 +1,238 @@
1
+ # encoding: utf-8
2
+
3
+ require 'popen4'
4
+ require 'colored'
5
+ require 'logger'
6
+
7
+ # Classes concerning command execution
8
+ module CommandExec
9
+ # Run commands
10
+ class Command
11
+
12
+ attr_accessor :logfile, :options , :parameter, :error_keywords
13
+ attr_reader :result, :path, :working_directory
14
+
15
+ # Create a new command to execute
16
+ #
17
+ # @param [Symbol] name name of command
18
+ # @param [optional,Hash] opts options for the command
19
+ # @option opts [String] :options options for binary
20
+ # @option opts [String] :parameter parameter for binary
21
+ # @option opts [String] :error_keywords keyword indicating an error on stdout
22
+ # @option opts [String] :working_directory working directory where the process should run in
23
+ # @option opts [String] :logfile file path to log file of process
24
+ def initialize(name,opts={})
25
+
26
+ @name = name
27
+ @opts = {
28
+ :logger => Logger.new($stderr),
29
+ :options => '',
30
+ :parameter => '',
31
+ :error_keywords => [],
32
+ :working_directory => Dir.pwd,
33
+ :logfile => '',
34
+ :debug => false,
35
+ }.update opts
36
+
37
+ @logger = @opts[:logger]
38
+ @options = @opts[:options]
39
+ @parameter = @opts[:parameter]
40
+ @path = resolve_cmd_name(name)
41
+ @error_keywords = @opts[:error_keywords]
42
+ @logfile = @opts[:logfile]
43
+
44
+ @working_directory = @opts[:working_directory]
45
+ Dir.chdir(working_directory)
46
+
47
+ end
48
+
49
+ private
50
+
51
+ # Find utility path
52
+ #
53
+ # @param [Symbol] name Name of utility
54
+ # @return [Path] Returns the path to the binary of the binary
55
+ def resolve_cmd_name(name)
56
+ path=''
57
+ path = %x[which #{name} 2>/dev/null].chomp
58
+
59
+ if path.empty?
60
+ @logger.fatal("Command not found #{name}")
61
+ raise Exceptions::CommandNotFound
62
+ end
63
+
64
+ return path
65
+ end
66
+
67
+ # Build string to execute command
68
+ #
69
+ # @return [String] Returns to whole command with parameters and options
70
+ def build_cmd_string
71
+ cmd = ''
72
+ cmd += path
73
+ cmd += options.empty? ? "" : " #{options}"
74
+ cmd += parameter.empty? ? "" : " #{parameter}"
75
+
76
+ return cmd
77
+ end
78
+
79
+ # Checks for errors
80
+ #
81
+ # @raise [String]
82
+
83
+ public
84
+
85
+ # Output the textual representation of a command
86
+ #
87
+ # @return [String] command in text form
88
+ def to_txt
89
+ return build_cmd_string
90
+ end
91
+
92
+ # Run the program
93
+ #
94
+ def run
95
+
96
+ _stdout = ''
97
+ _stderr = ''
98
+
99
+ status = POpen4::popen4(build_cmd_string) do |stdout, stderr, stdin, pid|
100
+ _stdout = stdout.read.strip
101
+ _stderr = stderr.read.strip
102
+ end
103
+
104
+
105
+ error_in_stdout_found = error_in_string_found?(error_keywords,_stdout)
106
+ @result = run_successful?( status.success? , error_in_stdout_found )
107
+
108
+ if @result == false
109
+ msg = message(
110
+ @result,
111
+ help_output(
112
+ {
113
+ :error_in_exec => not(status.success?),
114
+ :error_in_stdout => error_in_stdout_found
115
+ }, {
116
+ :stdout => StringIO.new(_stdout),
117
+ :stderr => StringIO.new(_stderr),
118
+ :logfile => read_logfile(logfile),
119
+ }
120
+ )
121
+ )
122
+ else
123
+ msg = message(@result)
124
+ end
125
+
126
+ @logger.info "#{@name.to_s}: #{msg}"
127
+
128
+ return @result
129
+
130
+ end
131
+
132
+ # Read the content of the logfile
133
+ #
134
+ # @param [Path] file path to logfile
135
+ # @param [Integer num_of_lines the number of lines which should be read -- e.g. 30 lines = -30
136
+ def read_logfile(file, num_of_lines=-30)
137
+ content = StringIO.new
138
+
139
+ unless file.empty?
140
+ begin
141
+ content << File.readlines(logfile)[num_of_lines..-1].join("")
142
+ rescue Errno::ENOENT
143
+ @logger.warn "Warning: logfile not found!"
144
+ end
145
+ end
146
+
147
+ return content
148
+ end
149
+
150
+ # Decide if a program run was successful
151
+ #
152
+ # @return [Boolean] Returns the decision
153
+ def run_successful?(success,error_in_stdout)
154
+ if success == false or error_in_stdout == true
155
+ return false
156
+ else
157
+ return true
158
+ end
159
+ end
160
+
161
+ # Decide which output to return to the user
162
+ # to help him with debugging
163
+ #
164
+ # @return [Array] Returns lines of log/stdout/stderr
165
+ def help_output(error_indicators={},output={})
166
+ error_in_exec = error_indicators[:error_in_exec]
167
+ error_in_stdout = error_indicators[:error_in_stdout]
168
+
169
+ logfile = output[:logfile].string
170
+ stdout = output[:stdout].string
171
+ stderr = output[:stderr].string
172
+
173
+ result = []
174
+
175
+ if error_in_exec == true
176
+ result << '================== LOGFILE ================== '
177
+ result << logfile if logfile.empty? == false
178
+ result << '================== STDOUT ================== '
179
+ result << stdout if stdout.empty? == false
180
+ result << '================== STDERR ================== '
181
+ result << stderr if stderr.empty? == false
182
+ elsif error_in_stdout == true
183
+ result << '================== STDOUT ================== '
184
+ result << stdout
185
+ end
186
+
187
+ return result
188
+
189
+ end
190
+
191
+ # Find error in stdout
192
+ #
193
+ # @return [Boolean] Returns true if it finds an error
194
+ def error_in_string_found? (keywords=[], string )
195
+ return false if keywords.empty? or not keywords.is_a? Array
196
+ return false if string.nil? or not string.is_a? String
197
+
198
+ error_found = false
199
+ keywords.each do |word|
200
+ if string.include? word
201
+ error_found = true
202
+ break
203
+ end
204
+ end
205
+
206
+ return error_found
207
+
208
+ end
209
+
210
+ # Generate the message which is return to the user
211
+ #
212
+ # @param [Boolean] run_successful true if a positive message should be returned
213
+ # @param [Array] msg Message which should be returned
214
+ def message(run_successful, *msg)
215
+
216
+ message = []
217
+ if run_successful
218
+ message << 'OK'.green.bold
219
+ else
220
+ message << 'FAILED'.red.bold
221
+ message.concat msg.flatten
222
+ end
223
+
224
+ return message.join("\n")
225
+ end
226
+
227
+ # Constructur to initiate a new command and run it later
228
+ #
229
+ # @see #initialize
230
+ def Command.execute(name,opts={})
231
+ command = new(name,opts)
232
+ command.run
233
+
234
+ return command
235
+ end
236
+
237
+ end
238
+ end
@@ -0,0 +1,20 @@
1
+ # Main
2
+ module CommandExec
3
+ # Classed concerning pdflatex exceptions
4
+ module Exceptions
5
+ # Class used to indicate that a command
6
+ # could not be found in file system
7
+ class CommandNotFound < RuntimeError
8
+ end
9
+
10
+ # Class used to indicate that a command run
11
+ # ended with a failure
12
+ class ExecuteCommandFailed < RuntimeError
13
+ end
14
+
15
+ # Class used to indicate that a logfile
16
+ # could not be found in file system
17
+ class LogfileNotFound < RuntimeError
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,19 @@
1
+ module CommandExec
2
+ module SpecHelper
3
+ def capture_stderr(&block)
4
+ previous_stderr, $stderr = $stderr, StringIO.new
5
+ block.call
6
+ return $stderr.string
7
+ ensure
8
+ $stderr = previous_stderr
9
+ end
10
+
11
+ def capture_stdout(&block)
12
+ previous_stdout, $stdout = $stdout, StringIO.new
13
+ block.call
14
+ return $stdout.string
15
+ ensure
16
+ $stdout = previous_stdout
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,3 @@
1
+ module CommandExec
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,6 @@
1
+ require 'command_exec/version'
2
+ require 'command_exec/exceptions'
3
+ require 'command_exec/command'
4
+
5
+ module CommandExec
6
+ end
@@ -0,0 +1,100 @@
1
+ require 'spec_helper'
2
+
3
+ describe Command do
4
+ let(:logger) {Logger.new('/dev/null')}
5
+ #let(:logger) {Logger.new($stdout)}
6
+ let(:debug) {false}
7
+ #let(:debug) {true}
8
+ let(:command) { Command.new(:echo , :debug=>debug, :logger => logger, :parameter => "hello world" , :error_keywords => %q[abc def], :working_directory => '/tmp' ) }
9
+
10
+
11
+ it "has a path" do
12
+ command.path.should == '/bin/echo'
13
+ end
14
+
15
+ it "has parameter" do
16
+ command.parameter.should == 'hello world'
17
+ end
18
+
19
+ it "has options" do
20
+ command.options.should == ''
21
+ end
22
+
23
+ it "offers the possibility to change the working directory of the process" do
24
+ command.working_directory.should == '/tmp'
25
+ Dir.pwd.should == '/tmp'
26
+ end
27
+
28
+ it "has special keywords indicating errors in stdout" do
29
+ command.error_keywords.should == %q[abc def]
30
+ end
31
+
32
+ it "can be used to construct a command string, which can be executed" do
33
+ command = Command.new(:pdflatex, :debug => debug, :logger => logger, :parameter => "index.tex blub.tex", :options => "-a -b")
34
+ command.send(:build_cmd_string).should == "/usr/bin/pdflatex -a -b index.tex blub.tex"
35
+ end
36
+
37
+ it "runs programms" do
38
+ command.run
39
+ command.result.should == true
40
+ end
41
+
42
+ it "returns the textual rep of a command" do
43
+ command.to_txt.should == '/bin/echo hello world'
44
+ end
45
+
46
+ it "execute existing programs" do
47
+ command = Command.execute(:echo, :debug => debug, :logger => logger ,:parameter => "index.tex blub.tex", :options => "-- -a -b")
48
+ command.result.should == true
49
+ end
50
+
51
+ it "does not execute non-existing programs" do
52
+ command = Command.execute(:grep, :debug => debug, :logger => logger, :parameter => "index.tex blub.tex", :options => "-- -a -b")
53
+ command.result.should == false
54
+ end
55
+
56
+ it "checks if errors have happend during execution" do
57
+ lambda { Command.new(:echo1, :debug => debug, :logger => logger, :parameter => "index.tex blub.tex", :options => "-- -a -b") }.should raise_error CommandNotFound
58
+ end
59
+
60
+ it "decides which output should be returned to the user" do
61
+ logfile = StringIO.new
62
+ logfile << 'Error in ... found'
63
+
64
+ stderr = StringIO.new
65
+ stderr << 'Error found'
66
+
67
+ stdout = StringIO.new
68
+ stdout << 'Error found'
69
+
70
+ #result = command.send(:help_logger)({ :error_in_exec => true , :error_in_stdout => false} , { :logfile => logfile, :stderr => stderr , :stdout => stdout })
71
+ result = command.send(:help_output, { :error_in_exec => true , :error_in_stdout => false} , { :logfile => logfile, :stderr => stderr , :stdout => stdout })
72
+ result.should == ["================== LOGFILE ================== ", "Error in ... found", "================== STDOUT ================== ", "Error found", "================== STDERR ================== ", "Error found"]
73
+
74
+ result = command.send(:help_output, { :error_in_exec => false , :error_in_stdout => true} , { :logfile => logfile, :stderr => stderr , :stdout => stdout })
75
+ result.should == ["================== STDOUT ================== ", "Error found"]
76
+
77
+ result = command.send(:help_output, { :error_in_exec => true , :error_in_stdout => true} , { :logfile => logfile, :stderr => stderr , :stdout => stdout })
78
+ result.should == ["================== LOGFILE ================== ", "Error in ... found", "================== STDOUT ================== ", "Error found", "================== STDERR ================== ", "Error found"]
79
+
80
+
81
+ result = command.send(:help_output, { :error_in_exec => false , :error_in_stdout => false} , { :logfile => logfile, :stderr => stderr , :stdout => stdout })
82
+ result.should == []
83
+
84
+ end
85
+
86
+ it "finds errors in stdout" do
87
+ command.send(:error_in_string_found?, ['error'] , 'long string witherror inside' ).should == true
88
+ command.send(:error_in_string_found?, ['long', 'inside'] , 'long string witherror inside' ).should == true
89
+ command.send(:error_in_string_found?, ['error'] , 'long string with erro inside' ).should == false
90
+ end
91
+
92
+ it "output a message" do
93
+ command.send(:message, false, 'Hello_world').should == "\e[1m\e[31mFAILED\e[0m\e[0m\nHello_world"
94
+ command.send(:message, true, 'Hello_world').should == "\e[1m\e[32mOK\e[0m\e[0m"
95
+ command.send(:message, true ).should == "\e[1m\e[32mOK\e[0m\e[0m"
96
+ end
97
+
98
+
99
+
100
+ end
@@ -0,0 +1,19 @@
1
+ #encoding: utf-8
2
+ $LOAD_PATH << File.expand_path('../lib' , File.dirname(__FILE__))
3
+
4
+ require 'pry'
5
+ require 'stringio'
6
+
7
+ require 'simplecov'
8
+ SimpleCov.start
9
+
10
+ require 'command_exec'
11
+ require 'command_exec/spec_helper_module'
12
+
13
+ include CommandExec
14
+ include CommandExec::Exceptions
15
+
16
+ RSpec.configure do |c|
17
+ c.include CommandExec::SpecHelper
18
+ end
19
+
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: command_exec
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Max Meyer
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-03-21 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: POpen4
16
+ requirement: &21841820 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *21841820
25
+ - !ruby/object:Gem::Dependency
26
+ name: colored
27
+ requirement: &21889840 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *21889840
36
+ description: This adds bells and whistles to ease shell command execution
37
+ email:
38
+ - dev@fedux.org
39
+ executables: []
40
+ extensions: []
41
+ extra_rdoc_files: []
42
+ files:
43
+ - .gitignore
44
+ - Gemfile
45
+ - Gemfile.lock
46
+ - Guardfile
47
+ - README.md
48
+ - Rakefile
49
+ - command_exec.gemspec
50
+ - lib/command_exec.rb
51
+ - lib/command_exec/command.rb
52
+ - lib/command_exec/exceptions.rb
53
+ - lib/command_exec/spec_helper_module.rb
54
+ - lib/command_exec/version.rb
55
+ - spec/command/command_spec.rb
56
+ - spec/spec_helper.rb
57
+ homepage: ''
58
+ licenses: []
59
+ post_install_message:
60
+ rdoc_options: []
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ! '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ requirements: []
76
+ rubyforge_project:
77
+ rubygems_version: 1.8.17
78
+ signing_key:
79
+ specification_version: 3
80
+ summary: Helper gem to exectue arbitrary shell commands
81
+ test_files:
82
+ - spec/command/command_spec.rb
83
+ - spec/spec_helper.rb
84
+ has_rdoc: