cdo 1.2.7 → 1.3.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.
- data/gemspec +2 -2
- data/lib/cdo.rb +204 -279
- data/lib/cdo_lib.rb +415 -0
- data/test/test_cdo.rb +180 -181
- metadata +3 -3
- data/lib/cdo_oo.rb +0 -301
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cdo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2016-01-22 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: unifiedPlot
|
@@ -34,7 +34,7 @@ extensions: []
|
|
34
34
|
extra_rdoc_files: []
|
35
35
|
files:
|
36
36
|
- lib/cdo.rb
|
37
|
-
- lib/
|
37
|
+
- lib/cdo_lib.rb
|
38
38
|
- gemspec
|
39
39
|
- LICENSE
|
40
40
|
- test/test_cdo.rb
|
data/lib/cdo_oo.rb
DELETED
@@ -1,301 +0,0 @@
|
|
1
|
-
require 'pp'
|
2
|
-
require 'open3'
|
3
|
-
require 'logger'
|
4
|
-
require 'stringio'
|
5
|
-
|
6
|
-
class Cdo
|
7
|
-
OutputOperatorsPattern = /(diff|info|output|griddes|zaxisdes|show|ncode|ndate|nlevel|nmon|nvar|nyear|ntime|npar|gradsdes|pardes)/
|
8
|
-
|
9
|
-
attr_accessor :cdo, :returnCdf, :forceOutput, :env, :debug
|
10
|
-
attr_reader :operators, :filetypes
|
11
|
-
|
12
|
-
def initialize(cdo: 'cdo',
|
13
|
-
returnCdf: false,
|
14
|
-
returnFalseOnError: false,
|
15
|
-
forceOutput: true,
|
16
|
-
env: {},
|
17
|
-
debug: false)
|
18
|
-
|
19
|
-
# setup path to cdo executable
|
20
|
-
@cdo = ENV.has_key?('CDO') ? ENV['CDO'] : cdo
|
21
|
-
|
22
|
-
@operators = getOperators
|
23
|
-
@returnCdf = returnCdf
|
24
|
-
@forceOutput = forceOutput
|
25
|
-
@env = env
|
26
|
-
@debug = ENV.has_key?('DEBUG') ? true : debug
|
27
|
-
|
28
|
-
@filetypes = getFiletypes
|
29
|
-
@returnFalseOnError = returnFalseOnError
|
30
|
-
|
31
|
-
end
|
32
|
-
|
33
|
-
private # {{{
|
34
|
-
|
35
|
-
# split arguments into hash-like args and the rest
|
36
|
-
def Cdo.parseArgs(args)
|
37
|
-
operatorArgs = args.reject {|a| a.class == Hash}
|
38
|
-
opts = operatorArgs.empty? ? '' : ',' + operatorArgs.join(',')
|
39
|
-
io = args.find {|a| a.class == Hash}
|
40
|
-
io = {} if io.nil?
|
41
|
-
args.delete_if {|a| a.class == Hash}
|
42
|
-
# join input streams together if possible
|
43
|
-
io[:input] = io[:input].join(' ') if io[:input].respond_to?(:join)
|
44
|
-
|
45
|
-
return [io,opts]
|
46
|
-
end
|
47
|
-
|
48
|
-
# collect the complete list of possible operators
|
49
|
-
def getOperators
|
50
|
-
cmd = @cdo + ' 2>&1'
|
51
|
-
help = IO.popen(cmd).readlines.map {|l| l.chomp.lstrip}
|
52
|
-
if 5 >= help.size
|
53
|
-
warn "Operators could not get listed by running the CDO binary (#{@cdo})"
|
54
|
-
pp help if @debug
|
55
|
-
exit
|
56
|
-
end
|
57
|
-
|
58
|
-
@operators = help[(help.index("Operators:")+1)..help.index(help.find {|v| v =~ /CDO version/}) - 2].join(' ').split
|
59
|
-
end
|
60
|
-
|
61
|
-
# get supported IO filetypes form the binary
|
62
|
-
def getFiletypes
|
63
|
-
_, _, stderr, _ = Open3.popen3(@cdo + " -V")
|
64
|
-
supported = stderr.readlines.map(&:chomp)
|
65
|
-
|
66
|
-
supported.grep(/(Filetypes)/)[0].split(':')[1].split.map(&:downcase)
|
67
|
-
end
|
68
|
-
|
69
|
-
|
70
|
-
# Execute the given cdo call and return all outputs
|
71
|
-
def _call(cmd)
|
72
|
-
if (@debug)
|
73
|
-
puts '# DEBUG ====================================================================='
|
74
|
-
pp @env unless @env.empty?
|
75
|
-
puts 'CMD: '
|
76
|
-
puts cmd
|
77
|
-
puts '# DEBUG ====================================================================='
|
78
|
-
end
|
79
|
-
|
80
|
-
stdin, stdout, stderr, wait_thr = Open3.popen3(@env,cmd)
|
81
|
-
{
|
82
|
-
:stdout => stdout.read,
|
83
|
-
:stderr => stderr.read,
|
84
|
-
:returncode => wait_thr.value.exitstatus
|
85
|
-
}
|
86
|
-
end
|
87
|
-
|
88
|
-
# Error handling for the given command
|
89
|
-
def _hasError(cmd,retvals)
|
90
|
-
if @debug
|
91
|
-
puts("RETURNCODE: #{retvals[:returncode]}")
|
92
|
-
end
|
93
|
-
if ( 0 != retvals[:returncode] )
|
94
|
-
puts("Error in calling:")
|
95
|
-
puts(">>> "+cmd+"<<<")
|
96
|
-
puts(retvals[:stderr])
|
97
|
-
return true
|
98
|
-
else
|
99
|
-
return false
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
# command execution wrapper, which handles the possible return types
|
104
|
-
def _run(cmd,ofile='',options=nil,returnCdf=false,force=nil,returnArray=nil,returnMaArray=nil)
|
105
|
-
options = options.to_s
|
106
|
-
|
107
|
-
options << '-f nc' if options.empty? and ( \
|
108
|
-
( returnCdf ) or \
|
109
|
-
( not returnArray.nil? ) or \
|
110
|
-
( not returnMaArray.nil?) \
|
111
|
-
)
|
112
|
-
cmd = "#{@cdo} -O #{options} #{cmd} "
|
113
|
-
|
114
|
-
case ofile
|
115
|
-
when $stdout
|
116
|
-
retvals = _call(cmd)
|
117
|
-
@logger.info(cmd+"\n") if @log
|
118
|
-
unless _hasError(cmd,retvals)
|
119
|
-
return retvals[:stdout].split($/).map {|l| l.chomp.strip}
|
120
|
-
else
|
121
|
-
raise ArgumentError,"CDO did NOT run successfully!"
|
122
|
-
end
|
123
|
-
else
|
124
|
-
force = @forceOutput if force.nil?
|
125
|
-
if force or not File.exists?(ofile.to_s)
|
126
|
-
ofile = MyTempfile.path if ofile.nil?
|
127
|
-
cmd << "#{ofile}"
|
128
|
-
retvals = _call(cmd)
|
129
|
-
@logger.info(cmd+"\n") if @log
|
130
|
-
if _hasError(cmd,retvals)
|
131
|
-
raise ArgumentError,"CDO did NOT run successfully!"
|
132
|
-
end
|
133
|
-
else
|
134
|
-
warn "Use existing file '#{ofile}'" if @debug
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
if not returnArray.nil?
|
139
|
-
readArray(ofile,returnArray)
|
140
|
-
elsif not returnMaArray.nil?
|
141
|
-
readMaArray(ofile,returnMaArray)
|
142
|
-
elsif returnCdf or @returnCdf
|
143
|
-
readCdf(ofile)
|
144
|
-
else
|
145
|
-
ofile
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
# Implementation of operator calls using ruby's meta programming skills
|
150
|
-
#
|
151
|
-
# args is expected to look like
|
152
|
-
# [opt1,...,optN,:input => iStream,:output => oStream, :options => ' ']
|
153
|
-
# where iStream could be another CDO call (timmax(selname(Temp,U,V,ifile.nc))
|
154
|
-
def method_missing(sym, *args, &block)
|
155
|
-
puts "Operator #{sym.to_s} is called" if @debug
|
156
|
-
|
157
|
-
if @operators.include?(sym.to_s)
|
158
|
-
io, opts = Cdo.parseArgs(args)
|
159
|
-
if OutputOperatorsPattern.match(sym.to_s)
|
160
|
-
_run(" -#{sym.to_s}#{opts} #{io[:input]} ",$stdout)
|
161
|
-
else
|
162
|
-
_run(" -#{sym.to_s}#{opts} #{io[:input]} ",io[:output],io[:options],io[:returnCdf],io[:force],io[:returnArray],io[:returnMaArray])
|
163
|
-
end
|
164
|
-
else
|
165
|
-
return false if @returnFalseOnError
|
166
|
-
raise ArgumentError,"Operator #{sym.to_s} not found"
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
# load the netcdf bindings
|
171
|
-
def loadCdf
|
172
|
-
begin
|
173
|
-
require "numru/netcdf_miss"
|
174
|
-
rescue LoadError
|
175
|
-
warn "Could not load ruby's netcdf bindings. Please install it."
|
176
|
-
raise
|
177
|
-
end
|
178
|
-
end
|
179
|
-
|
180
|
-
# }}}
|
181
|
-
|
182
|
-
public # {{{
|
183
|
-
|
184
|
-
# show Cdo's built-in help for given operator
|
185
|
-
def help(operator=nil)
|
186
|
-
if operator.nil?
|
187
|
-
puts _call([@cdo,'-h'].join(' '))[:stderr]
|
188
|
-
else
|
189
|
-
operator = operator.to_s
|
190
|
-
puts _call([@cdo,'-h',operator].join(' ')).values_at(:stdout,:stderr)
|
191
|
-
end
|
192
|
-
end
|
193
|
-
|
194
|
-
# check if cdo backend is working
|
195
|
-
def check
|
196
|
-
return false unless system("#@cdo -h 1>/dev/null 2>&1")
|
197
|
-
|
198
|
-
retval = _call("#@cdo -V")
|
199
|
-
pp retval if @debug
|
200
|
-
|
201
|
-
return true
|
202
|
-
end
|
203
|
-
|
204
|
-
def version
|
205
|
-
IO.popen("#{@cdo} -V 2>&1").readlines.first.split(/version/i).last.strip.split(' ').first
|
206
|
-
end
|
207
|
-
|
208
|
-
# return cdf handle to given file readonly
|
209
|
-
def readCdf(iFile)
|
210
|
-
loadCdf
|
211
|
-
NumRu::NetCDF.open(iFile)
|
212
|
-
end
|
213
|
-
|
214
|
-
# return cdf handle opened in append more
|
215
|
-
def openCdf(iFile)
|
216
|
-
loadCdf
|
217
|
-
NumRu::NetCDF.open(iFile,'r+')
|
218
|
-
end
|
219
|
-
|
220
|
-
# return narray for given variable name
|
221
|
-
def readArray(iFile,varname)
|
222
|
-
filehandle = readCdf(iFile)
|
223
|
-
if filehandle.var_names.include?(varname)
|
224
|
-
# return the data array
|
225
|
-
filehandle.var(varname).get
|
226
|
-
else
|
227
|
-
raise ArgumentError, "Cannot find variable '#{varname}'"
|
228
|
-
end
|
229
|
-
end
|
230
|
-
|
231
|
-
# return a masked array for given variable name
|
232
|
-
def readMaArray(iFile,varname)
|
233
|
-
filehandle = readCdf(iFile)
|
234
|
-
if filehandle.var_names.include?(varname)
|
235
|
-
# return the data array
|
236
|
-
filehandle.var(varname).get_with_miss
|
237
|
-
else
|
238
|
-
raise ArgumentError,"Cannot find variable '#{varname}'"
|
239
|
-
end
|
240
|
-
end
|
241
|
-
|
242
|
-
# }}}
|
243
|
-
|
244
|
-
# Addional operators: {{{
|
245
|
-
|
246
|
-
# compute vertical boundary levels from full levels
|
247
|
-
def boundaryLevels(args)
|
248
|
-
ilevels = self.showlevel(:input => args[:input])[0].split.map(&:to_f)
|
249
|
-
bound_levels = Array.new(ilevels.size+1)
|
250
|
-
bound_levels[0] = 0
|
251
|
-
(1..ilevels.size).each {|i|
|
252
|
-
bound_levels[i] =bound_levels[i-1] + 2*(ilevels[i-1]-bound_levels[i-1])
|
253
|
-
}
|
254
|
-
bound_levels
|
255
|
-
end
|
256
|
-
|
257
|
-
# compute level thicknesses from given full levels
|
258
|
-
def thicknessOfLevels(args)
|
259
|
-
bound_levels = self.boundaryLevels(args)
|
260
|
-
delta_levels = []
|
261
|
-
bound_levels.each_with_index {|v,i|
|
262
|
-
next if 0 == i
|
263
|
-
delta_levels << v - bound_levels[i-1]
|
264
|
-
}
|
265
|
-
delta_levels
|
266
|
-
end
|
267
|
-
|
268
|
-
# }}}
|
269
|
-
|
270
|
-
end
|
271
|
-
#
|
272
|
-
# Helper module for easy temp file handling
|
273
|
-
module MyTempfile
|
274
|
-
require 'tempfile'
|
275
|
-
@@_tempfiles = []
|
276
|
-
@@persistent_tempfiles = false
|
277
|
-
@@N = 10000000
|
278
|
-
|
279
|
-
def MyTempfile.setPersist(value)
|
280
|
-
@@persistent_tempfiles = value
|
281
|
-
end
|
282
|
-
|
283
|
-
def MyTempfile.path
|
284
|
-
unless @@persistent_tempfiles
|
285
|
-
t = Tempfile.new(self.class.to_s)
|
286
|
-
@@_tempfiles << t
|
287
|
-
@@_tempfiles << t.path
|
288
|
-
t.path
|
289
|
-
else
|
290
|
-
t = "_"+rand(@@N).to_s
|
291
|
-
@@_tempfiles << t
|
292
|
-
t
|
293
|
-
end
|
294
|
-
end
|
295
|
-
|
296
|
-
def MyTempfile.showFiles
|
297
|
-
@@_tempfiles.each {|f| print(f+" ") if f.kind_of? String}
|
298
|
-
end
|
299
|
-
end
|
300
|
-
|
301
|
-
#vim:fdm=marker
|