k_doc 0.0.18 → 0.0.21

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.
@@ -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