k_doc 0.0.18 → 0.0.21

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KDoc
4
+ # Composite Design Pattern: https://refactoring.guru/design-patterns/composite
5
+ module ComposableComponents
6
+ # Parent allows upwards navigation to parent component
7
+ attr_reader :parent
8
+
9
+ # Components allow downwards navigation plus access to sub-components
10
+ attr_reader :components
11
+
12
+ def attach_parent(parent)
13
+ @parent = parent
14
+ end
15
+
16
+ def navigate_parent
17
+ parent.nil? ? self : parent
18
+ end
19
+
20
+ def root?
21
+ parent.nil?
22
+ end
23
+
24
+ # Implement as needed (Implement is not provided here because you may want to use hash or array and have additional logic)
25
+ # def reset_components
26
+ # end
27
+ # def add_component
28
+ # end
29
+ # def remove_component
30
+ # end
31
+ # def get_components
32
+ # end
33
+ # def has_component?
34
+ # end
35
+ # def execute
36
+ # end
37
+ end
38
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KDoc
4
+ # Data acts as a base data object containers
5
+ module Datum
6
+ include KDoc::Guarded
7
+
8
+ attr_reader :data
9
+
10
+ def initialize_data(opts)
11
+ @default_data_value = opts.delete(:default_data_value) if opts.key?(:default_data_value)
12
+ @data = opts.delete(:data) || opts.delete(:default_data) || default_data_value
13
+
14
+ return if data.is_a?(default_data_value.class)
15
+
16
+ warn("Incompatible data type - #{default_data_value.class} is incompatible with #{data.class}")
17
+ @data = default_data_value
18
+ end
19
+
20
+ def default_data_value
21
+ raise 'Implement default_data_value in container' unless @default_data_value
22
+ end
23
+
24
+ # Write data object
25
+ #
26
+ # @param [Object] value A compatible data object to be stored against .data property
27
+ # @param [Symbol] data_action The data_action to take when setting data, defaults to :replace
28
+ # @param [:replace] data_action :replace will replace the existing data instance with the incoming data value
29
+ # @param [:append] data_action :append will keep existing data and then new value data over the top
30
+ def set_data(value, data_action: :replace)
31
+ return warn("Incompatible data type - #{default_data_value.class} is incompatible with #{value.class}") unless value.is_a?(default_data_value.class)
32
+
33
+ case data_action
34
+ when :replace
35
+ @data = value
36
+ when :append
37
+ @data.merge!(value) if @data.is_a?(Hash)
38
+ @data += value if @data.is_a?(Array)
39
+ else
40
+ warn("Unknown data_action: #{data_action}")
41
+ end
42
+ end
43
+
44
+ def clear_data
45
+ @data.clear
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KDoc
4
+ # Guarded provides parameter waring and guarding
5
+ #
6
+ # TODO: this could be moved into KType or KGuard
7
+ module Guarded
8
+ def guard(message)
9
+ errors << OpenStruct.new(type: :guard, message: message)
10
+ end
11
+
12
+ def warn(message)
13
+ errors << OpenStruct.new(type: :warning, message: message)
14
+ end
15
+ alias warning warn
16
+
17
+ def errors
18
+ @errors ||= []
19
+ end
20
+
21
+ def error_messages
22
+ @errors.map(&:message)
23
+ end
24
+
25
+ def valid?
26
+ @errors.length.zero?
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KDoc
4
+ # A container acts a base data object for any data that requires tagging
5
+ # such as unique key, type and namespace.
6
+ module Taggable
7
+ include KLog::Logging
8
+
9
+ attr_reader :tag_options
10
+
11
+ # Tags are provided via an options hash, these tags are remove from the hash as they are read
12
+ #
13
+ # Any container can be uniquely identified via it's key, type, namespace and project_key tags
14
+ #
15
+ # @param [Hash] opts The options
16
+ # @option opts [String|Symbol] project Project Name
17
+ # @option opts [String|Symbol|Array] namespace Namespace or array if namespace is deep nested
18
+ # @option opts [String|Symbol] name Container Name
19
+ # @option opts [String|Symbol] type Type of the container, uses default_container_type if not set
20
+ def initialize_tag(opts)
21
+ @tag_options = opts
22
+ build_tag
23
+ end
24
+
25
+ def tag
26
+ return @tag if defined? @tag
27
+ end
28
+
29
+ # Name of the document (required)
30
+ #
31
+ # Examples: user, account, country
32
+ def key
33
+ @key ||= @tag_options.delete(:key) || SecureRandom.alphanumeric(4)
34
+ end
35
+
36
+ # Type of data
37
+ #
38
+ # Examples by data type
39
+ # :csv, :yaml, :json, :xml
40
+ #
41
+ # Examples by shape of the data in a DSL
42
+ # :entity, :microapp, blueprint
43
+ def type
44
+ @type ||= @tag_options.delete(:type) || default_container_type
45
+ end
46
+
47
+ # Project name
48
+ #
49
+ # Examples
50
+ # :app_name1, :app_name2, :library_name1
51
+ def project
52
+ @project ||= @tag_options.delete(:project) || ''
53
+ end
54
+
55
+ # Namespace(s) what namespace is this document under
56
+ #
57
+ # Example for single path
58
+ # :controllers, :models
59
+ #
60
+ # Example for deep path
61
+ # [:app, :controllers, :admin]
62
+ def namespace
63
+ return @namespace if defined? @namespace
64
+
65
+ ns = @tag_options.delete(:namespace) || []
66
+ @namespace = ns.is_a?(Array) ? ns : [ns]
67
+ end
68
+
69
+ # # Internal data object
70
+ # def data
71
+ # @data ||= @tag_options.delete(:data) || @tag_options.delete(:default_data) || default_data_value
72
+ # # Settings and Table on Model needed access to @data for modification, I don't think this should be a clone
73
+ # # never return the original data object, but at the same time
74
+ # # do not re-clone it every time this accessor is called.
75
+ # # @clone_data ||= @data.clone
76
+ # end
77
+
78
+ # Implement in container
79
+ # def default_container_type
80
+ # :container
81
+ # end
82
+
83
+ # def default_data_value
84
+ # {}
85
+ # end
86
+
87
+ protected
88
+
89
+ # rubocop:disable Metrics/AbcSize
90
+ def debug_container
91
+ log.kv 'tag' , tag , debug_pad_size
92
+ log.kv 'project' , project , debug_pad_size unless project.nil? || project.empty?
93
+ log.kv 'namespace', namespace , debug_pad_size unless namespace.nil? || namespace.empty?
94
+ log.kv 'key' , key , debug_pad_size
95
+ log.kv 'type' , type , debug_pad_size
96
+ # log.kv 'error' , error , debug_kv_pad_size
97
+ end
98
+ # rubocop:enable Metrics/AbcSize
99
+
100
+ def debug_pad_size
101
+ @debug_pad_size ||= @tag_options.delete(:debug_pad_size) || 15
102
+ end
103
+
104
+ private
105
+
106
+ def build_tag
107
+ values = []
108
+ values << project if project
109
+ values += namespace
110
+ values << type if type
111
+ values << key
112
+ values -= [nil, '']
113
+ @tag = values.join('_').to_sym
114
+ end
115
+ end
116
+ end
data/lib/k_doc/model.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  module KDoc
4
4
  # Model is a DSL for modeling general purpose data objects
5
5
  #
6
- # A mode can have
6
+ # A model can have
7
7
  # - 0 or more named setting groups each with their key/value pairs
8
8
  # - 0 or more named table groups each with their own columns and rows
9
9
  #
@@ -17,64 +17,26 @@ module KDoc
17
17
  # include KType::NamedFolder
18
18
  # include KType::LayeredFolder
19
19
 
20
- attr_reader :options
21
-
22
- # Create document
23
- #
24
- # @param [String|Symbol] name Name of the document
25
- # @param args[0] Type of the document, defaults to KDoc:: FakeOpinion.new.default_model_type if not set
26
- # @param default: Default value (using named params), as above
27
- def initialize(key = nil, **options, &block)
28
- super(key: key, type: options[:type] || KDoc.opinion.default_model_type) # , namespace: options[:namespace], project_key: options[:project_key])
29
- initialize_attributes(**options)
30
-
31
- @block = block if block_given?
20
+ def initialize(key = nil, **opts, &block)
21
+ super(**{ key: key }.merge(opts), &block)
32
22
  end
33
23
 
34
- # NOTE: Can this be moved out of the is object?
35
- def execute_block(run_actions: nil)
36
- return if @block.nil?
37
-
38
- # The DSL actions method will only run on run_actions: true
39
- @run_actions = run_actions
40
-
41
- instance_eval(&@block)
42
-
43
- on_action if run_actions && respond_to?(:on_action)
44
- # rescue KDoc::Error => e
45
- # puts('KDoc::Error in document')
46
- # puts "key #{unique_key}"
47
- # # puts "file #{KUtil.data.console_file_hyperlink(resource.file, resource.file)}"
48
- # puts(e.message)
49
- # @error = e
50
- # raise
51
- rescue StandardError => e
52
- log.error('Standard error in document')
53
- # puts "key #{unique_key}"
54
- # puts "file #{KUtil.data.console_file_hyperlink(resource.file, resource.file)}"
55
- log.error(e.message)
56
- @error = e
57
- # log.exception exception2
58
- raise
59
- ensure
60
- @run_actions = nil
61
- end
24
+ # Need to look at Director as an alternative to this technique
25
+ def settings(key = nil, **setting_opts, &block)
26
+ setting_opts ||= {}
62
27
 
63
- def settings(key = nil, **options, &block)
64
- options ||= {}
28
+ setting_opts = {}.merge(opts) # Container options
29
+ .merge(setting_opts) # Settings setting_opts
30
+ .merge(parent: self)
65
31
 
66
- opts = {}.merge(@options) # Data Options
67
- .merge(options) # Settings Options
68
- .merge(parent: self)
69
-
70
- settings_instance(@data, key, **opts, &block)
32
+ settings_instance(data, key, **setting_opts, &block)
71
33
  # settings.run_decorators(opts)
72
34
  end
73
35
 
74
- def table(key = :table, **options, &block)
36
+ def table(key = :table, **opts, &block)
75
37
  # NEED to add support for run_decorators I think
76
- options.merge(parent: self)
77
- table_instance(@data, key, **options, &block)
38
+ opts.merge(parent: self)
39
+ table_instance(data, key, **opts, &block)
78
40
  end
79
41
  alias rows table
80
42
 
@@ -84,6 +46,16 @@ module KDoc
84
46
  # KDoc::Builder::Shotstack.new(@data, key, &block)
85
47
  # end
86
48
 
49
+ # Need to move this down to container
50
+ # Need to use some sort of cache invalidation to know if the internal data has been altered
51
+ def odata
52
+ @odata ||= data_struct
53
+ end
54
+
55
+ def oraw
56
+ @oraw ||= raw_data_struct
57
+ end
58
+
87
59
  def data_struct
88
60
  KUtil.data.to_open_struct(data)
89
61
  end
@@ -93,9 +65,13 @@ module KDoc
93
65
  KUtil.data.to_open_struct(raw_data)
94
66
  end
95
67
 
68
+ def default_container_type
69
+ KDoc.opinion.default_model_type
70
+ end
71
+
96
72
  def get_node_type(node_name)
97
73
  node_name = KUtil.data.clean_symbol(node_name)
98
- node_data = @data[node_name]
74
+ node_data = data[node_name]
99
75
 
100
76
  raise KDoc::Error, "Node not found: #{node_name}" if node_data.nil?
101
77
 
@@ -139,47 +115,26 @@ module KDoc
139
115
 
140
116
  def debug_header
141
117
  log.heading self.class.name
142
- log.kv 'key' , key , 15
143
- log.kv 'type' , type , 15
144
- # log.kv 'namespace', namespace
145
- log.kv 'error' , error , 15
146
-
118
+ debug_container
147
119
  debug_header_keys
148
120
 
149
121
  log.line
150
122
  end
151
123
 
152
124
  def debug_header_keys
153
- options&.keys&.reject { |k| k == :namespace }&.each do |key|
154
- log.kv key, options[key]
125
+ opts&.keys&.reject { |k| k == :namespace }&.each do |key|
126
+ log.kv key, opts[key]
155
127
  end
156
128
  end
157
129
 
158
130
  private
159
131
 
160
- def initialize_attributes(**options)
161
- @options = options || {}
162
- # Is parent a part of model, or is it part of k_manager::document_taggable
163
- @parent = slice_option(:parent)
164
-
165
- # Most documents live within a hash, some tabular documents such as CSV will use an []
166
- @data = slice_option(:default_data) || {}
167
- end
168
-
169
- def settings_instance(data, key, **options, &block)
170
- KDoc.opinion.settings_class.new(data, key, **options, &block)
132
+ def settings_instance(data, key, **opts, &block)
133
+ KDoc.opinion.settings_class.new(data, key, **opts, &block)
171
134
  end
172
135
 
173
- def table_instance(data, key, **options, &block)
174
- KDoc.opinion.table_class.new(data, key, **options, &block)
175
- end
176
-
177
- def slice_option(key)
178
- return nil unless @options.key?(key)
179
-
180
- result = @options[key]
181
- @options.delete(key)
182
- result
136
+ def table_instance(data, key, **opts, &block)
137
+ KDoc.opinion.table_class.new(data, key, **opts, &block)
183
138
  end
184
139
  end
185
140
  end
@@ -0,0 +1,191 @@
1
+ # frozen_string_literal: true
2
+
3
+ # module KDoc
4
+ # # Model is a DSL for modeling general purpose data objects
5
+ # #
6
+ # # A model can have
7
+ # # - 0 or more named setting groups each with their key/value pairs
8
+ # # - 0 or more named table groups each with their own columns and rows
9
+ # #
10
+ # # A settings group without a name will default to name: :settings
11
+ # # A table group without a name will default to name: :table
12
+ # class Model < KDoc::Container
13
+ # include KLog::Logging
14
+
15
+ # # include KType::Error
16
+ # # include KType::ManagedState
17
+ # # include KType::NamedFolder
18
+ # # include KType::LayeredFolder
19
+
20
+ # attr_reader :options
21
+
22
+ # # Create document
23
+ # #
24
+ # # @param [String|Symbol] name Name of the document
25
+ # # @param args[0] Type of the document, defaults to KDoc:: FakeOpinion.new.default_model_type if not set
26
+ # # @param default: Default value (using named params), as above
27
+ # def initialize(key = nil, **options, &block)
28
+ # super(key: key, type: options[:type] || KDoc.opinion.default_model_type) # , namespace: options[:namespace], project_key: options[:project_key])
29
+ # initialize_attributes(**options)
30
+
31
+ # @block = block if block_given?
32
+ # end
33
+
34
+ # # NOTE: Can this be moved out of the is object?
35
+ # def execute_block(run_actions: nil)
36
+ # return if @block.nil?
37
+
38
+ # # The DSL actions method will only run on run_actions: true
39
+ # @run_actions = run_actions
40
+
41
+ # instance_eval(&@block)
42
+
43
+ # on_action if run_actions && respond_to?(:on_action)
44
+ # # rescue KDoc::Error => e
45
+ # # puts('KDoc::Error in document')
46
+ # # puts "key #{unique_key}"
47
+ # # # puts "file #{KUtil.data.console_file_hyperlink(resource.file, resource.file)}"
48
+ # # puts(e.message)
49
+ # # @error = e
50
+ # # raise
51
+ # rescue StandardError => e
52
+ # log.error('Standard error in document')
53
+ # # puts "key #{unique_key}"
54
+ # # puts "file #{KUtil.data.console_file_hyperlink(resource.file, resource.file)}"
55
+ # log.error(e.message)
56
+ # @error = e
57
+ # # log.exception exception2
58
+ # raise
59
+ # ensure
60
+ # @run_actions = nil
61
+ # end
62
+
63
+ # def settings(key = nil, **options, &block)
64
+ # options ||= {}
65
+
66
+ # opts = {}.merge(@options) # Data Options
67
+ # .merge(options) # Settings Options
68
+ # .merge(parent: self)
69
+
70
+ # settings_instance(@data, key, **opts, &block)
71
+ # # settings.run_decorators(opts)
72
+ # end
73
+
74
+ # def table(key = :table, **options, &block)
75
+ # # NEED to add support for run_decorators I think
76
+ # options.merge(parent: self)
77
+ # table_instance(@data, key, **options, &block)
78
+ # end
79
+ # alias rows table
80
+
81
+ # # Sweet add-on would be builders
82
+ # # def builder(key, &block)
83
+ # # # example
84
+ # # KDoc::Builder::Shotstack.new(@data, key, &block)
85
+ # # end
86
+
87
+ # # Need to move this down to container
88
+ # # Need to use some sort of cache invalidation to know if the internal data has been altered
89
+ # def odata
90
+ # @odata ||= data_struct
91
+ # end
92
+
93
+ # def oraw
94
+ # @oraw ||= raw_data_struct
95
+ # end
96
+
97
+ # def data_struct
98
+ # KUtil.data.to_open_struct(data)
99
+ # end
100
+ # # alias d data_struct
101
+
102
+ # def raw_data_struct
103
+ # KUtil.data.to_open_struct(raw_data)
104
+ # end
105
+
106
+ # def get_node_type(node_name)
107
+ # node_name = KUtil.data.clean_symbol(node_name)
108
+ # node_data = @data[node_name]
109
+
110
+ # raise KDoc::Error, "Node not found: #{node_name}" if node_data.nil?
111
+
112
+ # if node_data.keys.length == 2 && (node_data.key?('fields') && node_data.key?('rows'))
113
+ # :table
114
+ # else
115
+ # :settings
116
+ # end
117
+ # end
118
+
119
+ # # Removes any meta data eg. "fields" from a table and just returns the raw data
120
+ # # REFACTOR: IT MAY BE BEST TO MOVE raw_data into each of the node_types
121
+ # def raw_data
122
+ # # REFACT, what if this is CSV, meaning it is just an array?
123
+ # # add specs
124
+ # result = data
125
+
126
+ # result.each_key do |key|
127
+ # # ANTI: get_node_type uses @data while we are using @data.clone here
128
+ # result[key] = if get_node_type(key) == :table
129
+ # # Old format was to keep the rows and delete the fields
130
+ # # Now the format is to pull the row_value up to the key and remove rows and fields
131
+ # # result[key].delete('fields')
132
+ # result[key]['rows']
133
+ # else
134
+ # result[key]
135
+ # end
136
+ # end
137
+
138
+ # result
139
+ # end
140
+
141
+ # # Move this out to the logger function when it has been refactor
142
+ # def debug(include_header: false)
143
+ # debug_header if include_header
144
+
145
+ # # tp dsls.values, :k_key, :k_type, :state, :save_at, :last_at, :data, :last_data, :source, { :file => { :width => 150 } }
146
+ # # puts JSON.pretty_generate(data)
147
+ # log.o(raw_data_struct)
148
+ # end
149
+
150
+ # def debug_header
151
+ # log.heading self.class.name
152
+ # debug_container
153
+ # debug_header_keys
154
+
155
+ # log.line
156
+ # end
157
+
158
+ # def debug_header_keys
159
+ # options&.keys&.reject { |k| k == :namespace }&.each do |key|
160
+ # log.kv key, options[key]
161
+ # end
162
+ # end
163
+
164
+ # private
165
+
166
+ # def initialize_attributes(**options)
167
+ # @options = options || {}
168
+ # # Is parent a part of model, or is it part of k_manager::document_taggable (NOT SURE IF IT IS USED YET)
169
+ # @parent = slice_option(:parent)
170
+
171
+ # # Most documents live within a hash, some tabular documents such as CSV will use an []
172
+ # @data = slice_option(:default_data) || {}
173
+ # end
174
+
175
+ # def settings_instance(data, key, **options, &block)
176
+ # KDoc.opinion.settings_class.new(data, key, **options, &block)
177
+ # end
178
+
179
+ # def table_instance(data, key, **options, &block)
180
+ # KDoc.opinion.table_class.new(data, key, **options, &block)
181
+ # end
182
+
183
+ # def slice_option(key)
184
+ # return nil unless @options.key?(key)
185
+
186
+ # result = @options[key]
187
+ # @options.delete(key)
188
+ # result
189
+ # end
190
+ # end
191
+ # end
data/lib/k_doc/table.rb CHANGED
@@ -25,10 +25,7 @@ module KDoc
25
25
  #
26
26
  # The older format of an array is supported via a splat conversion
27
27
  def fields(*field_definitions)
28
- if field_definitions.length == 1 && field_definitions[0].is_a?(Array)
29
- log.warn('avoid supplying field definitions with array. *Splat fields is the preferred technique.')
30
- field_definitions = *field_definitions[0]
31
- end
28
+ field_definitions = *field_definitions[0] if field_definitions.length == 1 && field_definitions[0].is_a?(Array)
32
29
 
33
30
  fields = @data[@name]['fields']
34
31
 
data/lib/k_doc/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module KDoc
4
- VERSION = '0.0.18'
4
+ VERSION = '0.0.21'
5
5
  end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+
5
+ module KDoc
6
+ # YamlDoc is a DSL for modeling YAML data objects
7
+ class YamlDoc < KDoc::Container
8
+ attr_reader :file
9
+
10
+ # Create YAML document
11
+ #
12
+ # @param [String|Symbol] name Name of the document
13
+ # @param args[0] Type of the document, defaults to KDoc:: FakeOpinion.new.default_csv_type if not set
14
+ # @param default: Default value (using named params), as above
15
+ def initialize(key = nil, **opts, &block)
16
+ super(**{ key: key }.merge(opts))
17
+
18
+ initialize_file
19
+
20
+ @block = block if block_given?
21
+ end
22
+
23
+ # Load data from file
24
+ #
25
+ # @param [Symbol] load_action The load_action to take if data has already been loaded
26
+ # @param [:once] load_action :once will load the data from content source on first try only
27
+ # @param [:reload] load_action :reload will reload from content source each time
28
+ # @param [Symbol] data_action The data_action to take when setting data, defaults to :replace
29
+ # @param [:replace] data_action :replace will replace the existing data instance with the incoming data value
30
+ # @param [:append] data_action :append will keep existing data and then new value data over the top
31
+ def load(load_action: :once, data_action: :replace)
32
+ return if load_action == :once && loaded?
33
+
34
+ content = File.read(file)
35
+ hash = YAML.safe_load(content)
36
+
37
+ set_data(hash, data_action: data_action)
38
+
39
+ @loaded = true
40
+ end
41
+
42
+ def loaded?
43
+ @loaded
44
+ end
45
+
46
+ private
47
+
48
+ def initialize_file
49
+ @file ||= opts.delete(:file) || ''
50
+ @loaded = false
51
+ end
52
+
53
+ def default_data_value
54
+ @default_data_value ||= {}
55
+ end
56
+
57
+ def default_container_type
58
+ :yaml
59
+ end
60
+ end
61
+ end