cdo 1.2.5 → 1.2.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/gemspec +3 -3
  2. data/lib/cdo_oo.rb +301 -0
  3. metadata +17 -12
  4. checksums.yaml +0 -7
data/gemspec CHANGED
@@ -3,9 +3,9 @@ $:.unshift File.join(File.dirname(__FILE__),"..","lib")
3
3
 
4
4
  spec = Gem::Specification.new do |s|
5
5
  s.name = "cdo"
6
- s.version = '1.2.5'
6
+ s.version = '1.2.7'
7
7
  s.platform = Gem::Platform::RUBY
8
- s.files = ["lib/cdo.rb"] + ["gemspec","LICENSE"]
8
+ s.files = ["lib/cdo.rb","lib/cdo_oo.rb"] + ["gemspec","LICENSE"]
9
9
  s.test_file = "test/test_cdo.rb"
10
10
  s.description = "Easy access to the Climate Data operators"
11
11
  s.summary = "Easy access to the Climate Data operators"
@@ -13,7 +13,7 @@ spec = Gem::Specification.new do |s|
13
13
  s.email = "stark.dreamdetective@gmail.com"
14
14
  s.homepage = "https://code.zmaw.de/projects/cdo/wiki/Cdo%7Brbpy%7D"
15
15
  s.license = "GPLv2"
16
- s.required_ruby_version = ">= 2.0"
16
+ s.required_ruby_version = ">= 1.9"
17
17
  s.add_development_dependency('unifiedPlot')
18
18
  end
19
19
 
@@ -0,0 +1,301 @@
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
metadata CHANGED
@@ -1,27 +1,30 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cdo
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.5
4
+ version: 1.2.7
5
+ prerelease:
5
6
  platform: ruby
6
7
  authors:
7
8
  - Ralf Mueller
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2015-06-02 00:00:00.000000000 Z
12
+ date: 2015-11-19 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: unifiedPlot
15
16
  requirement: !ruby/object:Gem::Requirement
17
+ none: false
16
18
  requirements:
17
- - - ">="
19
+ - - ! '>='
18
20
  - !ruby/object:Gem::Version
19
21
  version: '0'
20
22
  type: :development
21
23
  prerelease: false
22
24
  version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
23
26
  requirements:
24
- - - ">="
27
+ - - ! '>='
25
28
  - !ruby/object:Gem::Version
26
29
  version: '0'
27
30
  description: Easy access to the Climate Data operators
@@ -30,33 +33,35 @@ executables: []
30
33
  extensions: []
31
34
  extra_rdoc_files: []
32
35
  files:
33
- - LICENSE
34
- - gemspec
35
36
  - lib/cdo.rb
37
+ - lib/cdo_oo.rb
38
+ - gemspec
39
+ - LICENSE
36
40
  - test/test_cdo.rb
37
41
  homepage: https://code.zmaw.de/projects/cdo/wiki/Cdo%7Brbpy%7D
38
42
  licenses:
39
43
  - GPLv2
40
- metadata: {}
41
44
  post_install_message:
42
45
  rdoc_options: []
43
46
  require_paths:
44
47
  - lib
45
48
  required_ruby_version: !ruby/object:Gem::Requirement
49
+ none: false
46
50
  requirements:
47
- - - ">="
51
+ - - ! '>='
48
52
  - !ruby/object:Gem::Version
49
- version: '2.0'
53
+ version: '1.9'
50
54
  required_rubygems_version: !ruby/object:Gem::Requirement
55
+ none: false
51
56
  requirements:
52
- - - ">="
57
+ - - ! '>='
53
58
  - !ruby/object:Gem::Version
54
59
  version: '0'
55
60
  requirements: []
56
61
  rubyforge_project:
57
- rubygems_version: 2.4.5
62
+ rubygems_version: 1.8.23.2
58
63
  signing_key:
59
- specification_version: 4
64
+ specification_version: 3
60
65
  summary: Easy access to the Climate Data operators
61
66
  test_files:
62
67
  - test/test_cdo.rb
checksums.yaml DELETED
@@ -1,7 +0,0 @@
1
- ---
2
- SHA1:
3
- metadata.gz: 0596d2635271ae69ea1ea758d38f8481c3f04e13
4
- data.tar.gz: 3ca1b3a218e5797071abaa01174fc68fb00f8095
5
- SHA512:
6
- metadata.gz: 4c50745b63ef44962785a3379f1680fbde15fc270b7c7314c6e712bdc92bb8d7e0c0442437e9efa44dee73965c6d20be5fed17b1bfa22ca554ea0a2cf28bfe6d
7
- data.tar.gz: f1b5fed0c1909a7ad84ff504b24c76232c78d1d72f741ec76dc73c2b7c4098aae2852942a75f2595c8fef47cfadf5a33244cc484837a2a3288b63d639488a07c