netlinx-workspace 0.3.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +206 -6
  3. data/bin/netlinx-workspace +27 -0
  4. data/doc/NetLinx.html +18 -7
  5. data/doc/NetLinx/Compile.html +15 -4
  6. data/doc/NetLinx/Compile/Extension.html +15 -4
  7. data/doc/NetLinx/Compile/Extension/APW.html +8 -8
  8. data/doc/NetLinx/Project.html +334 -90
  9. data/doc/NetLinx/Rake.html +128 -0
  10. data/doc/NetLinx/Rake/Workspace.html +128 -0
  11. data/doc/NetLinx/Rake/Workspace/CreateWorkspaceConfig.html +387 -0
  12. data/doc/NetLinx/Rake/Workspace/GenerateAPW.html +371 -0
  13. data/doc/NetLinx/System.html +1341 -217
  14. data/doc/NetLinx/SystemFile.html +454 -51
  15. data/doc/NetLinx/Workspace.html +434 -69
  16. data/doc/NetLinx/Workspace/YAML.html +398 -0
  17. data/doc/_index.html +66 -4
  18. data/doc/class_list.html +6 -2
  19. data/doc/file.README.html +218 -10
  20. data/doc/file.license.html +4 -4
  21. data/doc/file_list.html +5 -1
  22. data/doc/frames.html +1 -1
  23. data/doc/index.html +218 -10
  24. data/doc/js/full_list.js +4 -1
  25. data/doc/method_list.html +171 -23
  26. data/doc/top-level-namespace.html +3 -3
  27. data/lib/netlinx/compile/extension/apw.rb +3 -0
  28. data/lib/netlinx/rake/workspace.rb +14 -0
  29. data/lib/netlinx/rake/workspace/create_workspace_config.rb +49 -0
  30. data/lib/netlinx/rake/workspace/generate_apw.rb +41 -0
  31. data/lib/netlinx/workspace.rb +58 -14
  32. data/lib/netlinx/workspace/project.rb +107 -0
  33. data/lib/netlinx/workspace/system.rb +226 -0
  34. data/lib/netlinx/workspace/system_file.rb +98 -0
  35. data/lib/netlinx/workspace/yaml.rb +217 -0
  36. data/license.txt +1 -1
  37. metadata +53 -28
  38. data/lib/netlinx-workspace.rb +0 -1
  39. data/lib/netlinx/project.rb +0 -83
  40. data/lib/netlinx/system.rb +0 -122
  41. data/lib/netlinx/system_file.rb +0 -36
@@ -0,0 +1,41 @@
1
+ require 'rake'
2
+ require 'rake/tasklib'
3
+
4
+ module NetLinx
5
+ module Rake
6
+ module Workspace
7
+
8
+ # Generate .apw workspace file from yaml config.
9
+ class GenerateAPW < ::Rake::TaskLib
10
+
11
+ attr_accessor :name
12
+
13
+ def initialize name = :generate_apw
14
+ @name = name
15
+ yield self if block_given?
16
+
17
+ desc "Generate .apw workspace file from yaml config."
18
+
19
+ task(name) do
20
+ require 'netlinx/workspace'
21
+
22
+ workspace_file = 'workspace.config.yaml'
23
+
24
+ unless File.exists? workspace_file
25
+ puts "File not found: #{workspace_file}"
26
+ next
27
+ end
28
+
29
+ NetLinx::Workspace::YAML.parse_file(workspace_file).tap do |workspace|
30
+ return unless workspace.name
31
+ File.open("#{workspace.name.strip}.apw", 'w') do |f|
32
+ f.write workspace.to_xml
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ end
39
+ end
40
+ end
41
+ end
@@ -1,8 +1,11 @@
1
+ # Gem convenience includes.
2
+ require_relative 'workspace/yaml'
3
+
4
+ # File includes.
1
5
  require 'rexml/document'
2
- require 'netlinx/project'
6
+ require_relative 'workspace/project'
3
7
 
4
8
  module NetLinx
5
-
6
9
  # A NetLinx Studio workspace.
7
10
  # Collection of projects.
8
11
  # Workspace -> Project -> System
@@ -26,17 +29,19 @@ module NetLinx
26
29
  nil
27
30
  end
28
31
 
29
- def initialize(**kvargs)
30
- @name = kvargs.fetch :name, ''
31
- @description = kvargs.fetch :description, ''
32
+ # @option kwargs [String] :name ('') Workspace name.
33
+ # @option kwargs [String] :description ('')
34
+ def initialize **kwargs
35
+ @name = kwargs.fetch :name, ''
36
+ @description = kwargs.fetch :description, ''
32
37
  @projects = []
33
38
 
34
- @file = kvargs.fetch :file, nil
39
+ @file = kwargs.fetch :file, nil
35
40
  load_workspace @file if @file
36
41
  end
37
42
 
38
43
  # Alias to add a project.
39
- def <<(project)
44
+ def << project
40
45
  @projects << project
41
46
  project.workspace = self
42
47
  end
@@ -51,8 +56,8 @@ module NetLinx
51
56
  File.dirname @file if @file
52
57
  end
53
58
 
54
- # Returns true if the workspace contains the specified file.
55
- def include?(file)
59
+ # @return true if the workspace contains the specified file.
60
+ def include? file
56
61
  included = false
57
62
 
58
63
  projects.each do |project|
@@ -74,10 +79,45 @@ module NetLinx
74
79
  compiler_results
75
80
  end
76
81
 
82
+ # @return [REXML::Element] an XML element representing this workspace.
83
+ def to_xml_element
84
+ REXML::Element.new('Workspace').tap do |workspace|
85
+ workspace.attributes['CurrentVersion'] = '4.0'
86
+
87
+ workspace.add_element('Identifier').tap { |e| e.text = name }
88
+ workspace.add_element('CreateVersion').tap { |e| e.text = '4.0' }
89
+ workspace.add_element('Comments').tap { |e| e.text = description }
90
+
91
+ @projects.each { |project| workspace << project.to_xml_element }
92
+ end
93
+ end
94
+
95
+ # @return [String] an XML string representing this workspace.
96
+ #
97
+ # @todo REXML bug forces :indent to be -1 or else erroneous line feeds are added.
98
+ # https://bugs.ruby-lang.org/issues/10864
99
+ def to_xml indent: -1
100
+ str = '<?xml version="1.0" encoding="UTF-8"?>' + "\n"
101
+
102
+ REXML::Document.new.tap do |doc|
103
+ doc << to_xml_element
104
+ doc.write output: str, indent: indent
105
+ end
106
+
107
+ str + "\n"
108
+ end
109
+
110
+ # Generate a {NetLinx::Workspace} from an XML string.
111
+ # @return self
112
+ def parse_xml string
113
+ parse_xml_element REXML::Document.new(string)
114
+ self
115
+ end
116
+
77
117
  private
78
118
 
79
119
  # Load the workspace from a given NetLinx Studio .apw file.
80
- def load_workspace(file)
120
+ def load_workspace file
81
121
  raise LoadError, "File does not exist at:\n#{file}" unless File.exists? file
82
122
 
83
123
  doc = nil
@@ -85,12 +125,16 @@ module NetLinx
85
125
  doc = REXML::Document.new f
86
126
  end
87
127
 
128
+ parse_xml_element doc
129
+ end
130
+
131
+ def parse_xml_element root
88
132
  # Load workspace params.
89
- @name = doc.elements['/Workspace/Identifier'].text.strip
90
- @description = doc.elements['/Workspace/Comments'].text
91
-
133
+ @name = root.elements['/Workspace/Identifier'].text.strip || ''
134
+ @description = root.elements['/Workspace/Comments'].text || ''
135
+
92
136
  # Load projects.
93
- doc.each_element '/Workspace/Project' do |e|
137
+ root.each_element '/Workspace/Project' do |e|
94
138
  project = NetLinx::Project.new \
95
139
  element: e,
96
140
  workspace: self
@@ -0,0 +1,107 @@
1
+ require 'rexml/document'
2
+ require_relative 'system'
3
+
4
+ module NetLinx
5
+ # A collection of NetLinx systems.
6
+ # Workspace -> Project -> System
7
+ class Project
8
+ # A reference to the project's parent workspace.
9
+ attr_accessor :workspace
10
+ attr_accessor :name
11
+ attr_accessor :description
12
+ attr_accessor :dealer
13
+ attr_accessor :designer
14
+ attr_accessor :sales_order
15
+ attr_accessor :purchase_order
16
+ attr_accessor :systems
17
+
18
+ # @option kwargs [NetLinx::Workspace] :workspace This system's parent workspace node.
19
+ # @option kwargs [String] :name ('') Project name.
20
+ # @option kwargs [String] :description ('')
21
+ # @option kwargs [String] :dealer ('')
22
+ # @option kwargs [String] :designer ('')
23
+ # @option kwargs [String] :sales_order ('')
24
+ # @option kwargs [String] :purchase_order ('')
25
+ def initialize **kwargs
26
+ @workspace = kwargs.fetch :workspace, nil
27
+
28
+ @name = kwargs.fetch :name, ''
29
+ @description = kwargs.fetch :description, ''
30
+ @dealer = kwargs.fetch :dealer, ''
31
+ @designer = kwargs.fetch :designer, ''
32
+ @sales_order = kwargs.fetch :sales_order, ''
33
+ @purchase_order = kwargs.fetch :purchase_order, ''
34
+
35
+ @systems = []
36
+
37
+ project_element = kwargs.fetch :element, nil
38
+ parse_xml_element project_element if project_element
39
+ end
40
+
41
+ # Alias to add a system.
42
+ def << system
43
+ @systems << system
44
+ system.project = self
45
+ end
46
+
47
+ # @return the project name.
48
+ def to_s
49
+ @name
50
+ end
51
+
52
+ # @return true if the project contains the specified file.
53
+ def include? file
54
+ included = false
55
+
56
+ systems.each do |system|
57
+ included = system.include? file
58
+ break if included
59
+ end
60
+
61
+ included
62
+ end
63
+
64
+ # Compile all systems in this project.
65
+ def compile
66
+ compiler_results = []
67
+ @systems.each {|system| compiler_results << (system.compile).first}
68
+ compiler_results
69
+ end
70
+
71
+ # @return [REXML::Element] an XML element representing this project.
72
+ def to_xml_element
73
+ REXML::Element.new('Project').tap do |project|
74
+ project.add_element('Identifier').tap { |e| e.text = name }
75
+ project.add_element('Designer').tap { |e| e.text = designer }
76
+ project.add_element('DealerID').tap { |e| e.text = dealer }
77
+ project.add_element('SalesOrder').tap { |e| e.text = sales_order }
78
+ project.add_element('PurchaseOrder').tap { |e| e.text = purchase_order }
79
+ project.add_element('Comments').tap { |e| e.text = description }
80
+
81
+ @systems.each { |system| project << system.to_xml_element }
82
+ end
83
+ end
84
+
85
+ private
86
+
87
+ def parse_xml_element project
88
+ # Load project params.
89
+ @name = project.elements['Identifier'].text.strip || ''
90
+ @designer = project.elements['Designer'].text || ''
91
+ @dealer = project.elements['DealerID'].text || ''
92
+ @sales_order = project.elements['SalesOrder'].text || ''
93
+ @purchase_order = project.elements['PurchaseOrder'].text || ''
94
+ @description = project.elements['Comments'].text || ''
95
+
96
+ # Load systems.
97
+ project.each_element 'System' do |e|
98
+ system = NetLinx::System.new \
99
+ element: e,
100
+ project: self
101
+
102
+ @systems << system
103
+ end
104
+ end
105
+
106
+ end
107
+ end
@@ -0,0 +1,226 @@
1
+ require 'rexml/document'
2
+ require_relative 'system_file'
3
+
4
+ module NetLinx
5
+ # A collection of resources loaded onto a NetLinx master.
6
+ # Workspace -> Project -> System
7
+ class System
8
+ # A reference to the system's parent project.
9
+ attr_accessor :project
10
+ attr_accessor :files
11
+
12
+ attr_accessor :name
13
+ attr_accessor :active
14
+ attr_accessor :id
15
+ attr_accessor :description
16
+
17
+ attr_accessor :ip_address
18
+ attr_accessor :ip_port
19
+ attr_accessor :ensure_availability
20
+
21
+ attr_accessor :com_port
22
+ attr_accessor :baud_rate
23
+ attr_accessor :data_bits
24
+ attr_accessor :parity
25
+ attr_accessor :stop_bits
26
+ attr_accessor :flow_control
27
+
28
+ # @option kwargs [NetLinx::Project] :project This system's parent project node.
29
+ # @option kwargs [String] :name ('') System name.
30
+ # @option kwargs [String] :description ('')
31
+ # @option kwargs [Boolean] :active (false) True if this is the active system
32
+ # in the workspace.
33
+ # @option kwargs [Integer] :id (0) Master controller system ID.
34
+ # 0 connects to any master at the given communication settings.
35
+ # Or in other words, 0 prevents disconnection from a master
36
+ # with a different ID.
37
+ #
38
+ # @option kwargs [String] :ip_address ('0.0.0.0')
39
+ # @option kwargs [String] :ip_port (1319) ICSLan port.
40
+ # @option kwargs [String] :ensure_availability (true) Ping the master
41
+ # controller to ensure availability before connecting.
42
+ #
43
+ # @option kwargs [Symbol] :com_port (:com1)
44
+ # @option kwargs [Integer] :baud_rate (38400)
45
+ # @option kwargs [Integer] :data_bits (8)
46
+ # @option kwargs [:none,:even,:odd,:mark,:space] :parity (:none)
47
+ # @option kwargs [Integer] :stop_bits (1)
48
+ # @option kwargs [:none] :flow_control (:none)
49
+ def initialize **kwargs
50
+ @project = kwargs.fetch :project, nil
51
+
52
+ @name = kwargs.fetch :name, ''
53
+ @id = kwargs.fetch :id, 0
54
+ @active = kwargs.fetch :active, false
55
+ @description = kwargs.fetch :description, ''
56
+
57
+ @ip_address = kwargs.fetch :ip_address, '0.0.0.0'
58
+ @ip_port = kwargs.fetch :ip_port, 1319
59
+ @ensure_availability = kwargs.fetch :ensure_availability, true
60
+
61
+ @com_port = kwargs.fetch :com_port, :com1
62
+ @baud_rate = kwargs.fetch :baud_rate, 38400
63
+ @data_bits = kwargs.fetch :data_bits, 8
64
+ @parity = kwargs.fetch :parity, :none
65
+ @stop_bits = kwargs.fetch :stop_bits, 1
66
+ @flow_control = kwargs.fetch :flow_control, :none
67
+
68
+ @files = []
69
+
70
+ @compiler_target_files = []
71
+ @compiler_include_paths = []
72
+ @compiler_module_paths = []
73
+ @compiler_library_paths = []
74
+
75
+ system_element = kwargs.fetch :element, nil
76
+ parse_xml_element system_element if system_element
77
+ end
78
+
79
+ # Alias to add a file.
80
+ def << file
81
+ @files << file
82
+ file.system = self
83
+ end
84
+
85
+ # @return the system name.
86
+ def to_s
87
+ @name
88
+ end
89
+
90
+ # @return [REXML::Element] an XML element representing this system.
91
+ def to_xml_element
92
+ REXML::Element.new('System').tap do |system|
93
+ system.attributes['IsActive'] = active
94
+ system.attributes['Platform'] = 'Netlinx'
95
+ system.attributes['Transport'] = 'Serial'
96
+ system.attributes['TransportEx'] =
97
+ (ip_address == '0.0.0.0') ? 'Serial' : 'TCPIP'
98
+
99
+ system.add_element('Identifier').tap { |e| e.text = name }
100
+ system.add_element('SysID').tap { |e| e.text = id }
101
+ system.add_element('Comments').tap { |e| e.text = description }
102
+
103
+ # These don't seem to change in NetLinx Studio 4.0; possibly 3.x legacy.
104
+ # The 'Ex' suffixes are used.
105
+ system.add_element('TransTCPIP').tap { |e| e.text = '0.0.0.0' }
106
+ system.add_element('TransSerial').tap { |e| e.text = 'COM1,38400,8,None,1,None' }
107
+
108
+ # TODO: Generate communication settings.
109
+ system.add_element('TransTCPIPEx').tap { |e|
110
+ e.text = "#{ip_address}|#{ip_port}|#{ensure_availability ? 1 : 0}|||"
111
+ }
112
+ system.add_element('TransSerialEx').tap { |e|
113
+ e.text = "#{com_port.upcase}|#{baud_rate}|#{data_bits}|#{parity.capitalize}|#{stop_bits}|||"
114
+ }
115
+ system.add_element('TransUSBEx').tap { |e| e.text = '|||||' }
116
+ system.add_element('TransVNMEx').tap { |e| e.text = '||' }
117
+
118
+ system.add_element('UserName').tap { |e| e.text = '' }
119
+ system.add_element('Password').tap { |e| e.text = '' }
120
+
121
+ @files.each { |file| system << file.to_xml_element }
122
+ end
123
+ end
124
+
125
+ # @see Test::NetLinx::Compilable.
126
+ def compiler_target_files
127
+ @files
128
+ .select {|f| f.type == :master}
129
+ .map {|f| File.expand_path \
130
+ f.path.gsub('\\', '/'),
131
+ f.system.project.workspace.path
132
+ }.uniq
133
+ end
134
+
135
+ # @see Test::NetLinx::Compilable.
136
+ def compiler_include_paths
137
+ @files
138
+ .select {|f| f.type == :include}
139
+ .map {|f| File.expand_path \
140
+ File.dirname(f.path.gsub('\\', '/')),
141
+ f.system.project.workspace.path
142
+ }.uniq
143
+ end
144
+
145
+ # @see Test::NetLinx::Compilable.
146
+ def compiler_module_paths
147
+ @files
148
+ .select {|f| f.type == :module || f.type == :tko || f.type == :duet}
149
+ .map {|f| File.expand_path \
150
+ File.dirname(f.path.gsub('\\', '/')),
151
+ f.system.project.workspace.path
152
+ }.uniq
153
+ end
154
+
155
+ # @see Test::NetLinx::Compilable.
156
+ def compiler_library_paths
157
+ []
158
+ end
159
+
160
+ # @return [Boolean] true if the project contains the specified file.
161
+ def include? file
162
+ included = false
163
+
164
+ @files.each do |f|
165
+ name_included = f.name.downcase.eql? file.downcase
166
+
167
+ # TODO: This should probably be relative to the workspace path,
168
+ # which can be found by traversing @project, @workspace.
169
+ path_included = file.gsub(/\\/, '/').include? f.path.gsub(/\\/, '/')
170
+
171
+ included = name_included || path_included
172
+ break if included
173
+ end
174
+
175
+ included
176
+ end
177
+
178
+ # Compile this system.
179
+ def compile
180
+ # The compiler dependency is only needed if this method is called.
181
+ require 'netlinx/compiler'
182
+
183
+ compiler = NetLinx::Compiler.new
184
+ compiler.compile self
185
+ end
186
+
187
+ private
188
+
189
+ def parse_xml_element system
190
+ # Load system params.
191
+ @name = system.elements['Identifier'].text.strip || ''
192
+ @active = (system.attributes['IsActive'].strip == 'true') ? true : false
193
+ @id = system.elements['SysID'].text.strip.to_i || 0
194
+ @description = system.elements['Comments'].text || ''
195
+
196
+ if system.elements['TransTCPIPEx'] # Workspace v4.0
197
+ tcpip = (system.elements['TransTCPIPEx'].text || '').split('|')
198
+
199
+ @ip_address = tcpip[0] || '0.0.0.0'
200
+ @ip_port = (tcpip[1] || 1319).to_i
201
+ @ensure_availability = (tcpip[2] == '0') ? false : true
202
+ end
203
+
204
+ if system.elements['TransSerialEx'] # Workspace v4.0
205
+ serial = (system.elements['TransSerialEx'].text || '').split('|')
206
+
207
+ @com_port = (serial[0] || :com1).to_s.downcase.to_sym
208
+ @baud_rate = (serial[1] || 38400).to_i
209
+ @data_bits = (serial[2] || 8).to_i
210
+ @parity = (serial[3] || :none).to_s.downcase.to_sym
211
+ @stop_bits = (serial[4] || 1).to_i
212
+ @flow_control = (serial[5] || :none).to_s.downcase.to_sym
213
+ end
214
+
215
+ # Create system files.
216
+ system.each_element 'File' do |e|
217
+ system_file = NetLinx::SystemFile.new \
218
+ element: e,
219
+ system: self
220
+
221
+ @files << system_file
222
+ end
223
+ end
224
+
225
+ end
226
+ end