MuranoCLI 2.0.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 (151) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +28 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +21 -0
  5. data/Gemfile +27 -0
  6. data/LICENSE.txt +19 -0
  7. data/MuranoCLI.gemspec +50 -0
  8. data/MuranoCLI.iss +50 -0
  9. data/README.markdown +208 -0
  10. data/Rakefile +188 -0
  11. data/TODO.taskpaper +122 -0
  12. data/bin/mr +8 -0
  13. data/bin/murano +84 -0
  14. data/docs/demo.md +109 -0
  15. data/lib/MrMurano/Account.rb +211 -0
  16. data/lib/MrMurano/Config-Migrate.rb +47 -0
  17. data/lib/MrMurano/Config.rb +286 -0
  18. data/lib/MrMurano/Mock.rb +63 -0
  19. data/lib/MrMurano/Product-1P-Device.rb +145 -0
  20. data/lib/MrMurano/Product-Resources.rb +195 -0
  21. data/lib/MrMurano/Product.rb +358 -0
  22. data/lib/MrMurano/ProjectFile.rb +349 -0
  23. data/lib/MrMurano/Solution-Cors.rb +46 -0
  24. data/lib/MrMurano/Solution-Endpoint.rb +177 -0
  25. data/lib/MrMurano/Solution-File.rb +150 -0
  26. data/lib/MrMurano/Solution-ServiceConfig.rb +140 -0
  27. data/lib/MrMurano/Solution-Services.rb +326 -0
  28. data/lib/MrMurano/Solution-Users.rb +129 -0
  29. data/lib/MrMurano/Solution.rb +59 -0
  30. data/lib/MrMurano/SubCmdGroupContext.rb +49 -0
  31. data/lib/MrMurano/SyncUpDown.rb +565 -0
  32. data/lib/MrMurano/commands/assign.rb +57 -0
  33. data/lib/MrMurano/commands/businessList.rb +45 -0
  34. data/lib/MrMurano/commands/completion.rb +152 -0
  35. data/lib/MrMurano/commands/config.rb +67 -0
  36. data/lib/MrMurano/commands/content.rb +130 -0
  37. data/lib/MrMurano/commands/cors.rb +30 -0
  38. data/lib/MrMurano/commands/domain.rb +17 -0
  39. data/lib/MrMurano/commands/gb.rb +33 -0
  40. data/lib/MrMurano/commands/init.rb +138 -0
  41. data/lib/MrMurano/commands/keystore.rb +157 -0
  42. data/lib/MrMurano/commands/logs.rb +78 -0
  43. data/lib/MrMurano/commands/mock.rb +63 -0
  44. data/lib/MrMurano/commands/password.rb +88 -0
  45. data/lib/MrMurano/commands/postgresql.rb +41 -0
  46. data/lib/MrMurano/commands/product.rb +14 -0
  47. data/lib/MrMurano/commands/productCreate.rb +39 -0
  48. data/lib/MrMurano/commands/productDelete.rb +33 -0
  49. data/lib/MrMurano/commands/productDevice.rb +84 -0
  50. data/lib/MrMurano/commands/productDeviceIdCmds.rb +86 -0
  51. data/lib/MrMurano/commands/productList.rb +45 -0
  52. data/lib/MrMurano/commands/productWrite.rb +27 -0
  53. data/lib/MrMurano/commands/show.rb +80 -0
  54. data/lib/MrMurano/commands/solution.rb +14 -0
  55. data/lib/MrMurano/commands/solutionCreate.rb +39 -0
  56. data/lib/MrMurano/commands/solutionDelete.rb +34 -0
  57. data/lib/MrMurano/commands/solutionList.rb +45 -0
  58. data/lib/MrMurano/commands/status.rb +92 -0
  59. data/lib/MrMurano/commands/sync.rb +60 -0
  60. data/lib/MrMurano/commands/timeseries.rb +115 -0
  61. data/lib/MrMurano/commands/tsdb.rb +271 -0
  62. data/lib/MrMurano/commands/usage.rb +23 -0
  63. data/lib/MrMurano/commands/zshcomplete.erb +112 -0
  64. data/lib/MrMurano/commands.rb +32 -0
  65. data/lib/MrMurano/hash.rb +20 -0
  66. data/lib/MrMurano/http.rb +153 -0
  67. data/lib/MrMurano/makePretty.rb +75 -0
  68. data/lib/MrMurano/schema/pf-v1.0.0.yaml +114 -0
  69. data/lib/MrMurano/schema/sf-v0.2.0.yaml +77 -0
  70. data/lib/MrMurano/schema/sf-v0.3.0.yaml +78 -0
  71. data/lib/MrMurano/template/mock.erb +9 -0
  72. data/lib/MrMurano/template/projectFile.murano.erb +81 -0
  73. data/lib/MrMurano/verbosing.rb +99 -0
  74. data/lib/MrMurano/version.rb +4 -0
  75. data/lib/MrMurano.rb +20 -0
  76. data/spec/Account-Passwords_spec.rb +242 -0
  77. data/spec/Account_spec.rb +272 -0
  78. data/spec/ConfigFile_spec.rb +50 -0
  79. data/spec/ConfigMigrate_spec.rb +89 -0
  80. data/spec/Config_spec.rb +409 -0
  81. data/spec/Http_spec.rb +204 -0
  82. data/spec/MakePretties_spec.rb +118 -0
  83. data/spec/Mock_spec.rb +53 -0
  84. data/spec/ProductBase_spec.rb +113 -0
  85. data/spec/ProductContent_spec.rb +162 -0
  86. data/spec/ProductResources_spec.rb +329 -0
  87. data/spec/Product_1P_Device_spec.rb +202 -0
  88. data/spec/Product_1P_RPC_spec.rb +175 -0
  89. data/spec/Product_spec.rb +153 -0
  90. data/spec/ProjectFile_spec.rb +324 -0
  91. data/spec/Solution-Cors_spec.rb +164 -0
  92. data/spec/Solution-Endpoint_spec.rb +581 -0
  93. data/spec/Solution-File_spec.rb +212 -0
  94. data/spec/Solution-ServiceConfig_spec.rb +202 -0
  95. data/spec/Solution-ServiceDevice_spec.rb +176 -0
  96. data/spec/Solution-ServiceEventHandler_spec.rb +385 -0
  97. data/spec/Solution-ServiceModules_spec.rb +465 -0
  98. data/spec/Solution-UsersRoles_spec.rb +207 -0
  99. data/spec/Solution_spec.rb +92 -0
  100. data/spec/SyncRoot_spec.rb +83 -0
  101. data/spec/SyncUpDown_spec.rb +495 -0
  102. data/spec/Verbosing_spec.rb +279 -0
  103. data/spec/_workspace.rb +27 -0
  104. data/spec/cmd_assign_spec.rb +51 -0
  105. data/spec/cmd_business_spec.rb +59 -0
  106. data/spec/cmd_common.rb +72 -0
  107. data/spec/cmd_config_spec.rb +68 -0
  108. data/spec/cmd_content_spec.rb +71 -0
  109. data/spec/cmd_cors_spec.rb +50 -0
  110. data/spec/cmd_device_spec.rb +96 -0
  111. data/spec/cmd_domain_spec.rb +32 -0
  112. data/spec/cmd_init_spec.rb +30 -0
  113. data/spec/cmd_keystore_spec.rb +97 -0
  114. data/spec/cmd_password_spec.rb +62 -0
  115. data/spec/cmd_status_spec.rb +239 -0
  116. data/spec/cmd_syncdown_spec.rb +86 -0
  117. data/spec/cmd_syncup_spec.rb +62 -0
  118. data/spec/cmd_usage_spec.rb +36 -0
  119. data/spec/fixtures/.mrmuranorc +9 -0
  120. data/spec/fixtures/ProjectFiles/invalid.yaml +9 -0
  121. data/spec/fixtures/ProjectFiles/only_meta.yaml +24 -0
  122. data/spec/fixtures/ProjectFiles/with_routes.yaml +27 -0
  123. data/spec/fixtures/SolutionFiles/0.2.0.json +20 -0
  124. data/spec/fixtures/SolutionFiles/0.2.0_invalid.json +18 -0
  125. data/spec/fixtures/SolutionFiles/0.2.json +21 -0
  126. data/spec/fixtures/SolutionFiles/0.3.0.json +20 -0
  127. data/spec/fixtures/SolutionFiles/0.3.0_invalid.json +19 -0
  128. data/spec/fixtures/SolutionFiles/0.3.json +20 -0
  129. data/spec/fixtures/SolutionFiles/basic.json +20 -0
  130. data/spec/fixtures/SolutionFiles/secret.json +6 -0
  131. data/spec/fixtures/configfile +9 -0
  132. data/spec/fixtures/dumped_config +42 -0
  133. data/spec/fixtures/mrmuranorc_deleted_bob +8 -0
  134. data/spec/fixtures/mrmuranorc_tool_bob +3 -0
  135. data/spec/fixtures/product_spec_files/example.exoline.spec.yaml +116 -0
  136. data/spec/fixtures/product_spec_files/example.murano.spec.yaml +14 -0
  137. data/spec/fixtures/product_spec_files/gwe.exoline.spec.yaml +21 -0
  138. data/spec/fixtures/product_spec_files/gwe.murano.spec.yaml +16 -0
  139. data/spec/fixtures/product_spec_files/lightbulb-no-state.yaml +11 -0
  140. data/spec/fixtures/product_spec_files/lightbulb.yaml +14 -0
  141. data/spec/fixtures/roles-three.yaml +11 -0
  142. data/spec/fixtures/syncable_content/assets/icon.png +0 -0
  143. data/spec/fixtures/syncable_content/assets/index.html +0 -0
  144. data/spec/fixtures/syncable_content/assets/js/script.js +0 -0
  145. data/spec/fixtures/syncable_content/modules/table_util.lua +58 -0
  146. data/spec/fixtures/syncable_content/routes/manyRoutes.lua +11 -0
  147. data/spec/fixtures/syncable_content/routes/singleRoute.lua +5 -0
  148. data/spec/fixtures/syncable_content/services/devdata.lua +18 -0
  149. data/spec/fixtures/syncable_content/services/timers.lua +4 -0
  150. data/spec/spec_helper.rb +119 -0
  151. metadata +498 -0
@@ -0,0 +1,349 @@
1
+ require 'yaml'
2
+ require 'json-schema'
3
+ require 'pathname'
4
+ require 'MrMurano/verbosing'
5
+ require 'MrMurano/Config'
6
+ require 'MrMurano/hash'
7
+
8
+ module MrMurano
9
+ ##
10
+ # A Project File that describes details about a project that is synced into
11
+ # Murano
12
+ class ProjectFile
13
+ include Verbose
14
+
15
+ # Methods that are common to various internal structs.
16
+ module PrjStructCommonMethods
17
+ ## Load data from Hash into self
18
+ #
19
+ # Also makes sure that :include and :exclude are arrays.
20
+ #
21
+ # @param obj [Hash] Data to load in
22
+ def load(obj)
23
+ self.members.reject{|key| [:legacy].include? key}.each do |key|
24
+ self[key] = obj[key] if obj.has_key? key
25
+ end
26
+ self.members.select{|k| [:include, :exclude].include? k}.each do |key|
27
+ self[key] = [self[key]] unless self[key].kind_of? Array
28
+ end
29
+ end
30
+
31
+ ## Returns a sparse hash of the data in self
32
+ # @return [Hash] Just the non-nil members of this
33
+ def save
34
+ ret={}
35
+ self.members.reject{|key| [:legacy].include? key}.each do |key|
36
+ ret[key] = self[key] unless self[key].nil?
37
+ end
38
+ ret
39
+ end
40
+ end
41
+
42
+ # The contents of this is explictily not just a nest of hashes and arrays.
43
+ # To keep expectations in check, there is a set number of known keys.
44
+ # This should also help by keeping the file format seperate from the internal
45
+ # lookups. Hopefully, this will avoid (or at least minimize) changes to the
46
+ # file format affecting all kinds of code.
47
+ PrjMeta = Struct.new(:name, :summary, :description, :authors, :version, :source, :dependencies) do
48
+ include PrjStructCommonMethods
49
+ end
50
+
51
+ PrjFiles = Struct.new(:location, :include, :exclude, :default_page) do
52
+ include PrjStructCommonMethods
53
+ end
54
+
55
+ PrjModules = Struct.new(:location, :include, :exclude) do
56
+ include PrjStructCommonMethods
57
+ end
58
+
59
+ PrjEndpoints = Struct.new(:location, :include, :exclude, :cors) do
60
+ include PrjStructCommonMethods
61
+ end
62
+
63
+ PrjEventHandlers = Struct.new(:location, :include, :exclude, :legacy) do
64
+ include PrjStructCommonMethods
65
+ end
66
+
67
+ PrjResources = Struct.new(:location, :include, :exclude) do
68
+ include PrjStructCommonMethods
69
+ end
70
+
71
+ PrfFile = Struct.new(:info, :assets, :modules, :routes, :services, :resources) do
72
+ ## Returns a sparse hash of the data in self
73
+ # @return [Hash] Just the non-empty members of this
74
+ def save
75
+ ret={}
76
+ self.members.each do |key|
77
+ value = self[key].save
78
+ ret[key] = value unless value.empty?
79
+ end
80
+ ret
81
+ end
82
+ def get_binding
83
+ binding()
84
+ end
85
+ end
86
+
87
+ def initialize()
88
+ @prjFile = nil
89
+ tname = $cfg['location.base'].basename.to_s.gsub(/[^A-Za-z0-9]/, '')
90
+ @data = PrfFile.new(
91
+ PrjMeta.new(
92
+ tname,
93
+ "One line summary of #{tname}",
94
+ "In depth description of #{tname}\n\nWith lots of details.",
95
+ [$cfg['user.name']],
96
+ "1.0.0",
97
+ nil,
98
+ nil
99
+ ),
100
+ PrjFiles.new,
101
+ PrjModules.new,
102
+ PrjEndpoints.new,
103
+ PrjEventHandlers.new,
104
+ PrjResources.new,
105
+ )
106
+ end
107
+
108
+ # Get the current Project file
109
+ # @return [Pathname] PAth to current project file.
110
+ def project_file
111
+ @prjFile
112
+ end
113
+ # Get a binding to the data for building the example ProjectFile
114
+ def data_binding
115
+ @data.get_binding
116
+ end
117
+
118
+ # Get a value for a key.
119
+ # Keys are 'section.key'
120
+ def get(key)
121
+ raise "Empty key" if key.empty?
122
+ section, ikey = key.split('.')
123
+ raise "Missing dot" if ikey.nil? and section == key
124
+ raise "Missing key" if ikey.nil? and section != key
125
+ ret = @data[section.to_sym][ikey.to_sym]
126
+ return default_value_for(key) if ret.nil?
127
+ return ret
128
+ end
129
+ alias [] get
130
+
131
+ # Get the default value for a key.
132
+ #
133
+ # Keys are 'section.key'
134
+ #
135
+ # All of these are currently stored in $cfg, but under different names.
136
+ def default_value_for(key)
137
+ keymap = {
138
+ 'assets.location' => 'location.files',
139
+ 'assets.include' => 'files.searchFor',
140
+ 'assets.exclude' => 'files.ignoring',
141
+ 'assets.default_page' => 'files.default_page',
142
+ 'modules.location' => 'location.modules',
143
+ 'modules.include' => 'modules.searchFor',
144
+ 'modules.exclude' => 'modules.ignoring',
145
+ 'routes.location' => 'location.endpoints',
146
+ 'routes.include' => 'endpoints.searchFor',
147
+ 'routes.exclude' => 'endpoints.ignoring',
148
+ 'routes.cors' => 'location.cors',
149
+ 'services.location' => 'location.eventhandlers',
150
+ 'services.include' => 'eventhandler.searchFor',
151
+ 'services.exclude' => 'eventhandler.ignoring',
152
+ 'resources.location' => 'location.specs',
153
+ 'resources.include' => 'product.spec',
154
+ 'resources.exclude' => 'product.ignoring',
155
+ }.freeze
156
+ needSplit = %r{.*\.(searchFor|ignoring)$}.freeze
157
+ return nil unless keymap.has_key? key
158
+ # *.{include,exclude} want arrays returned. But what they map to is
159
+ # strings.
160
+ cfg_key = keymap[key]
161
+ ret = ($cfg[cfg_key] or '')
162
+ ret = ret.split() if cfg_key =~ needSplit
163
+ ret
164
+ end
165
+
166
+ # Set a value for a key.
167
+ # Keys are 'section.key'
168
+ def set(key, value)
169
+ section, ikey = key.split('.')
170
+ begin
171
+ sec = @data[section.to_sym]
172
+ sec[ikey.to_sym] = value
173
+ rescue NameError => e
174
+ debug ">>=>> #{e}"
175
+ end
176
+ end
177
+ alias []= set
178
+
179
+ ## Save the Project File.
180
+ #
181
+ # This ALWAYS saves in the latest format only.
182
+ def save(ios=$stdout)
183
+ dt = @data.save
184
+ dt = Hash.transform_keys_to_strings(dt).to_yaml
185
+ if ios.nil?
186
+ dt
187
+ else
188
+ ios.write dt
189
+ end
190
+ end
191
+
192
+ ##
193
+ # Load the project file in the project directory.
194
+ def load
195
+ possible = Dir[
196
+ ($cfg['location.base'] + '*.murano').to_s,
197
+ ($cfg['location.base'] + 'Solutionfile.json').to_s,
198
+ ]
199
+ debug "Possible Project files: #{possible}"
200
+ return 0 if possible.empty? # this is ok.
201
+
202
+ warning "Multiple possible Project files! #{possible}" if possible.count > 1
203
+ @prjFile = Pathname.new(possible.first)
204
+
205
+ data = nil
206
+ begin
207
+ data = YAML.load_file(@prjFile.to_s)
208
+ rescue Exception => e
209
+ error "Load error; #{e}"
210
+ pp e
211
+ return -3
212
+ end
213
+ if data.nil? then
214
+ error "Failed to load #{@prjFile}"
215
+ return -2
216
+ end
217
+ unless data.kind_of?(Hash) then
218
+ error "Bad format in #{@prjFile}"
219
+ return -4
220
+ end
221
+
222
+ data = Hash.transform_keys_to_symbols(data)
223
+
224
+ # get format version; little different for older format.
225
+ if @prjFile.basename.to_s == "Solutionfile.json" then
226
+ fmtvers = (data[:version] or '0.2.0')
227
+ else
228
+ fmtvers = (data[:formatversion] or '1.0.0')
229
+ end
230
+
231
+ methodname = "load_#{fmtvers.gsub(/\./, '_')}".to_sym
232
+ debug "Will try to #{methodname}"
233
+ if respond_to? methodname then
234
+ errorlist = __send__(methodname, data)
235
+ unless errorlist.empty? then
236
+ error %{Project file #{@prjFile} not valid.}
237
+ errorlist.each{|er| error er}
238
+ return -5
239
+ end
240
+ else
241
+ error "Cannot load Project file of version #{fmtvers}"
242
+ end
243
+ end
244
+
245
+ # Only set destination if source has key
246
+ # @param src [Hash,Struct] Source of value to copy
247
+ # @param skey [String,Symbol] Key in source to check and read
248
+ # @param dest [Hash,Struct] Destination to save value in
249
+ # @param dkey [String,Symbol] Key in destination to save to
250
+ def ifset(src, skey, dest, dkey)
251
+ dest[dkey] = src[skey] if src.has_key? skey
252
+ end
253
+
254
+ # Load data in the 1.0.0 format.
255
+ # @param data [Hash] the data to load
256
+ # @return [Array] An array of validation errors in the data
257
+ def load_1_0_0(data)
258
+ schemaPath = Pathname.new(::File.dirname(__FILE__)) + 'schema/pf-v1.0.0.yaml'
259
+ schema = YAML.load_file(schemaPath.to_s)
260
+ v = JSON::Validator.fully_validate(schema, data)
261
+ return v unless v.empty?
262
+
263
+ @data.each_pair do |key, str|
264
+ str.load(data[key]) if data.has_key? key
265
+ end
266
+
267
+ []
268
+ end
269
+ alias load_1_0 load_1_0_0
270
+ alias load_1 load_1_0_0
271
+
272
+ # Load data in the 0.2.0 format.
273
+ # @param data [Hash] the data to load
274
+ # @return [Array] An array of validation errors in the data
275
+ def load_0_2_0(data)
276
+ schemaPath = Pathname.new(::File.dirname(__FILE__)) + 'schema/sf-v0.2.0.yaml'
277
+ schema = YAML.load_file(schemaPath.to_s)
278
+ v = JSON::Validator.fully_validate(schema, data)
279
+ return v unless v.empty?
280
+
281
+ ifset(data, :default_page, @data[:assets], :default_page)
282
+ ifset(data, :file_dir, @data[:assets], :location)
283
+
284
+ @data[:routes].location = '.'
285
+ @data[:routes][:include] = [data[:custom_api]] if data.has_key? :custom_api
286
+ ifset(data, :cors, @data[:routes], :cors)
287
+
288
+ if data.has_key? :modules then
289
+ @data[:modules].location = '.'
290
+ @data[:modules][:include] = data[:modules].values
291
+ end
292
+
293
+ if data.has_key? :event_handler then
294
+ @data[:services].location = '.'
295
+ evd = data[:event_handler].values.map{|e| e.values}.flatten
296
+ @data[:services].include = evd
297
+ @data.services.legacy = store_legacy_service_handlers(data[:event_handler])
298
+ end
299
+ []
300
+ end
301
+ alias load_0_2 load_0_2_0
302
+
303
+ def store_legacy_service_handlers(services)
304
+ ret = {}
305
+ services.each do |service, events|
306
+ events.each do |event, path|
307
+ ret[path] = [service, event]
308
+ end
309
+ end
310
+ ret
311
+ end
312
+
313
+ # Load data in the 0.3.0 format.
314
+ # @param data [Hash] the data to load
315
+ # @return [Array] An array of validation errors in the data
316
+ def load_0_3_0(data)
317
+ schemaPath = Pathname.new(::File.dirname(__FILE__)) + 'schema/sf-v0.3.0.yaml'
318
+ schema = YAML.load_file(schemaPath.to_s)
319
+ v = JSON::Validator.fully_validate(schema, data)
320
+ return v unless v.empty?
321
+
322
+ ifset(data, :default_page, @data[:assets], :default_page)
323
+ ifset(data, :assets, @data[:assets], :location)
324
+
325
+ @data[:routes].location = '.'
326
+ @data[:routes][:include] = [data[:routes]] if data.has_key? :routes
327
+ ifset(data, :cors, @data[:routes], :cors)
328
+
329
+ if data.has_key? :modules then
330
+ @data[:modules].location = '.'
331
+ @data[:modules][:include] = data[:modules].values
332
+ end
333
+
334
+ if data.has_key? :services then
335
+ @data[:services].location = '.'
336
+ evd = data[:services].values.map{|e| e.values}.flatten
337
+ @data[:services].include = evd
338
+ @data.services.legacy = store_legacy_service_handlers(data[:services])
339
+ end
340
+
341
+ []
342
+ end
343
+ alias load_0_3 load_0_3_0
344
+ alias load_0 load_0_3_0
345
+ end
346
+
347
+
348
+ end
349
+ # vim: set ai et sw=2 ts=2 :
@@ -0,0 +1,46 @@
1
+ require 'yaml'
2
+ require 'json'
3
+ require 'MrMurano/hash'
4
+ require 'MrMurano/Solution'
5
+
6
+ module MrMurano
7
+ class Cors < SolutionBase
8
+ def initialize
9
+ super
10
+ @uriparts << 'cors'
11
+ @project_section = :cors
12
+ end
13
+
14
+ def fetch(id=nil, &block)
15
+ ret = get()
16
+ if ret.kind_of?(Hash) and ret.has_key?(:cors) then
17
+ # XXX cors is a JSON encoded string. That seems weird. keep an eye on this.
18
+ data = JSON.parse(ret[:cors], @json_opts)
19
+ else
20
+ data = ret
21
+ end
22
+ if block_given? then
23
+ yield Hash.transform_keys_to_strings(data).to_yaml
24
+ else
25
+ data
26
+ end
27
+ end
28
+
29
+ ##
30
+ # Upload CORS
31
+ # @param file [String,Nil] File path to upload other than defaults
32
+ def upload(file=nil)
33
+ unless file.nil? then
34
+ data = YAML.load_file(file)
35
+ else
36
+ data = $project['routes.cors']
37
+ # If it is just a string, then is a file to load.
38
+ data = YAML.load_file(data) if data.kind_of? String
39
+ end
40
+ put('', data)
41
+ end
42
+
43
+ end
44
+ end
45
+
46
+ # vim: set ai et sw=2 ts=2 :
@@ -0,0 +1,177 @@
1
+ require 'uri'
2
+ require 'net/http'
3
+ require 'json'
4
+ require 'pp'
5
+ require 'MrMurano/Solution'
6
+
7
+ module MrMurano
8
+ # …/endpoint
9
+ class Endpoint < SolutionBase
10
+ def initialize
11
+ super
12
+ @uriparts << 'endpoint'
13
+ @project_section = :routes
14
+
15
+ @match_header = /--#ENDPOINT (?<method>\S+) (?<path>\S+)( (?<ctype>.*))?/
16
+ end
17
+
18
+ ##
19
+ # This gets all data about all endpoints
20
+ def list
21
+ get().map do |item|
22
+ if item[:content_type].nil? or item[:content_type].empty? then
23
+ item[:content_type] = 'application/json'
24
+ end
25
+ # XXX should this update the script header?
26
+ item
27
+ end
28
+ end
29
+
30
+ def fetch(id)
31
+ ret = get('/' + id.to_s)
32
+ ret[:content_type] = 'application/json' if ret[:content_type].empty?
33
+
34
+ script = ret[:script].lines.map{|l|l.chomp}
35
+
36
+ aheader = (script.first or "")
37
+
38
+ rh = ['--#ENDPOINT', ret[:method].upcase, ret[:path]]
39
+ rh << ret[:content_type] if ret[:content_type] != 'application/json'
40
+ rheader = rh.join(' ')
41
+
42
+ # if header is missing add it.
43
+ # If header is wrong, replace it.
44
+
45
+ md = @match_header.match(aheader)
46
+ if md.nil? then
47
+ # header missing.
48
+ script.unshift rheader
49
+ elsif md[:method] != ret[:method] or
50
+ md[:path] != ret[:path] or
51
+ md[:ctype] != ret[:content_type] then
52
+ # header is wrong.
53
+ script[0] = rheader
54
+ end
55
+ # otherwise current header is good.
56
+
57
+ script = script.join("\n") + "\n"
58
+ if block_given? then
59
+ yield script
60
+ else
61
+ script
62
+ end
63
+ end
64
+
65
+ ##
66
+ # Upload endpoint
67
+ # :local path to file to push
68
+ # :remote hash of method and endpoint path
69
+ # @param modify Bool: True if item exists already and this is changing it
70
+ def upload(local, remote, modify)
71
+ local = Pathname.new(local) unless local.kind_of? Pathname
72
+ raise "no file" unless local.exist?
73
+
74
+ # we assume these are small enough to slurp.
75
+ unless remote.has_key? :script then
76
+ script = local.read
77
+ remote[:script] = script
78
+ end
79
+ limitkeys = [:method, :path, :script, :content_type, @itemkey]
80
+ remote = remote.select{|k,v| limitkeys.include? k }
81
+ # post('', remote)
82
+ if remote.has_key? @itemkey then
83
+ put('/' + remote[@itemkey], remote) do |request, http|
84
+ response = http.request(request)
85
+ case response
86
+ when Net::HTTPSuccess
87
+ #return JSON.parse(response.body)
88
+ when Net::HTTPNotFound
89
+ verbose "\tDoesn't exist, creating"
90
+ post('/', remote)
91
+ else
92
+ showHttpError(request, response)
93
+ end
94
+ end
95
+ else
96
+ verbose "\tNo itemkey, creating"
97
+ post('/', remote)
98
+ end
99
+ end
100
+
101
+ ##
102
+ # Delete an endpoint
103
+ def remove(id)
104
+ delete('/' + id.to_s)
105
+ end
106
+
107
+ def tolocalname(item, key)
108
+ name = ''
109
+ name << item[:path].split('/').reject{|i|i.empty?}.join('-')
110
+ name << '.'
111
+ name << item[:method].downcase
112
+ name << '.lua'
113
+ end
114
+
115
+ def toRemoteItem(from, path)
116
+ # Path could be have multiple endpoints inside, so a loop.
117
+ items = []
118
+ path = Pathname.new(path) unless path.kind_of? Pathname
119
+ cur = nil
120
+ lineno=0
121
+ path.readlines().each do |line|
122
+ md = @match_header.match(line)
123
+ if not md.nil? then
124
+ # header line.
125
+ cur[:line_end] = lineno unless cur.nil?
126
+ items << cur unless cur.nil?
127
+ cur = {:method=>md[:method],
128
+ :path=>md[:path],
129
+ :content_type=> (md[:ctype] or 'application/json'),
130
+ :local_path=>path,
131
+ :line=>lineno,
132
+ :script=>line}
133
+ elsif not cur.nil? and not cur[:script].nil? then
134
+ cur[:script] << line
135
+ end
136
+ lineno += 1
137
+ end
138
+ cur[:line_end] = lineno unless cur.nil?
139
+ items << cur unless cur.nil?
140
+ items
141
+ end
142
+
143
+ def match(item, pattern)
144
+ # Pattern is: #{method}#{path glob}
145
+ pattern_pattern = /^#(?<method>[^#]*)#(?<path>.*)/i
146
+ md = pattern_pattern.match(pattern)
147
+ return false if md.nil?
148
+ debug "match pattern: '#{md[:method]}' '#{md[:path]}'"
149
+
150
+ unless md[:method].empty? then
151
+ return false unless item[:method].downcase == md[:method].downcase
152
+ end
153
+
154
+ return true if md[:path].empty?
155
+
156
+ ::File.fnmatch(md[:path],item[:path])
157
+ end
158
+
159
+ def synckey(item)
160
+ "#{item[:method].upcase}_#{item[:path]}"
161
+ end
162
+
163
+ def docmp(itemA, itemB)
164
+ if itemA[:script].nil? and itemA[:local_path] then
165
+ itemA[:script] = itemA[:local_path].read
166
+ end
167
+ if itemB[:script].nil? and itemB[:local_path] then
168
+ itemB[:script] = itemB[:local_path].read
169
+ end
170
+ return (itemA[:script] != itemB[:script] or itemA[:content_type] != itemB[:content_type])
171
+ end
172
+
173
+ end
174
+
175
+ SyncRoot.add('endpoints', Endpoint, 'A', %{Endpoints}, true)
176
+ end
177
+ # vim: set ai et sw=2 ts=2 :