archsight 0.1.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +24 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/CONTRIBUTING.md +186 -0
- data/Dockerfile +39 -0
- data/LICENSE.txt +201 -0
- data/README.md +170 -0
- data/SECURITY.md +27 -0
- data/exe/archsight +9 -0
- data/lib/archsight/annotations/aggregators.rb +109 -0
- data/lib/archsight/annotations/annotation.rb +168 -0
- data/lib/archsight/annotations/architecture_annotations.rb +59 -0
- data/lib/archsight/annotations/backup_annotations.rb +21 -0
- data/lib/archsight/annotations/computed.rb +264 -0
- data/lib/archsight/annotations/email_recipient.rb +35 -0
- data/lib/archsight/annotations/generated_annotations.rb +17 -0
- data/lib/archsight/annotations/git_annotations.rb +21 -0
- data/lib/archsight/annotations/relation_resolver.rb +160 -0
- data/lib/archsight/cli.rb +120 -0
- data/lib/archsight/configuration.rb +36 -0
- data/lib/archsight/database.rb +183 -0
- data/lib/archsight/documentation.rb +171 -0
- data/lib/archsight/graph.rb +113 -0
- data/lib/archsight/helpers.rb +210 -0
- data/lib/archsight/linter.rb +77 -0
- data/lib/archsight/mcp/analyze_resource_tool.rb +222 -0
- data/lib/archsight/mcp/base.rb +48 -0
- data/lib/archsight/mcp/query_tool.rb +113 -0
- data/lib/archsight/mcp/resource_doc_tool.rb +87 -0
- data/lib/archsight/mcp.rb +6 -0
- data/lib/archsight/query/ast.rb +279 -0
- data/lib/archsight/query/errors.rb +39 -0
- data/lib/archsight/query/evaluator.rb +707 -0
- data/lib/archsight/query/lexer.rb +289 -0
- data/lib/archsight/query/parser.rb +506 -0
- data/lib/archsight/query.rb +68 -0
- data/lib/archsight/renderer.rb +134 -0
- data/lib/archsight/resources/application_component.rb +346 -0
- data/lib/archsight/resources/application_interface.rb +54 -0
- data/lib/archsight/resources/application_service.rb +222 -0
- data/lib/archsight/resources/base.rb +300 -0
- data/lib/archsight/resources/business_actor.rb +195 -0
- data/lib/archsight/resources/business_constraint.rb +32 -0
- data/lib/archsight/resources/business_process.rb +37 -0
- data/lib/archsight/resources/business_product.rb +206 -0
- data/lib/archsight/resources/business_requirement.rb +56 -0
- data/lib/archsight/resources/compliance_evidence.rb +42 -0
- data/lib/archsight/resources/data_object.rb +49 -0
- data/lib/archsight/resources/motivation_goal.rb +37 -0
- data/lib/archsight/resources/motivation_outcome.rb +33 -0
- data/lib/archsight/resources/motivation_stakeholder.rb +38 -0
- data/lib/archsight/resources/strategy_capability.rb +38 -0
- data/lib/archsight/resources/technology_artifact.rb +154 -0
- data/lib/archsight/resources/technology_interface.rb +34 -0
- data/lib/archsight/resources/technology_node.rb +42 -0
- data/lib/archsight/resources/technology_service.rb +35 -0
- data/lib/archsight/resources/technology_system_software.rb +37 -0
- data/lib/archsight/resources/view.rb +51 -0
- data/lib/archsight/resources.rb +49 -0
- data/lib/archsight/template.rb +49 -0
- data/lib/archsight/version.rb +5 -0
- data/lib/archsight/web/application.rb +290 -0
- data/lib/archsight/web/doc/archimate.md +215 -0
- data/lib/archsight/web/doc/computed_annotations.md +316 -0
- data/lib/archsight/web/doc/icons.md +303 -0
- data/lib/archsight/web/doc/index.md.erb +74 -0
- data/lib/archsight/web/doc/modeling.md +200 -0
- data/lib/archsight/web/doc/search.md +227 -0
- data/lib/archsight/web/doc/togaf.md +255 -0
- data/lib/archsight/web/doc/tool.md +90 -0
- data/lib/archsight/web/public/css/artifact.css +985 -0
- data/lib/archsight/web/public/css/base.css +201 -0
- data/lib/archsight/web/public/css/graph.css +106 -0
- data/lib/archsight/web/public/css/highlight.min.css +10 -0
- data/lib/archsight/web/public/css/iconoir.css +22 -0
- data/lib/archsight/web/public/css/instance.css +329 -0
- data/lib/archsight/web/public/css/layout.css +421 -0
- data/lib/archsight/web/public/css/mermaid-layers.css +188 -0
- data/lib/archsight/web/public/css/pico.min.css +4 -0
- data/lib/archsight/web/public/favicon.ico +0 -0
- data/lib/archsight/web/public/img/archimate.png +0 -0
- data/lib/archsight/web/public/img/togaf-high-level.png +0 -0
- data/lib/archsight/web/public/js/graph-zoom.js +18 -0
- data/lib/archsight/web/public/js/highlight.min.js +3899 -0
- data/lib/archsight/web/public/js/htmx.min.js +1 -0
- data/lib/archsight/web/public/js/mermaid-init.js +88 -0
- data/lib/archsight/web/public/js/mermaid.min.js +2811 -0
- data/lib/archsight/web/public/js/sparkline.js +42 -0
- data/lib/archsight/web/public/js/svg-pan-zoom.min.js +3 -0
- data/lib/archsight/web/public/js/svg-zoom-controls.js +93 -0
- data/lib/archsight/web/views/index.haml +12 -0
- data/lib/archsight/web/views/partials/artifact/_activity.haml +55 -0
- data/lib/archsight/web/views/partials/artifact/_agentic.haml +25 -0
- data/lib/archsight/web/views/partials/artifact/_deployment.haml +29 -0
- data/lib/archsight/web/views/partials/artifact/_git_info.haml +16 -0
- data/lib/archsight/web/views/partials/artifact/_language_stats.haml +53 -0
- data/lib/archsight/web/views/partials/artifact/_links.haml +24 -0
- data/lib/archsight/web/views/partials/artifact/_project_estimate.haml +26 -0
- data/lib/archsight/web/views/partials/artifact/_repositories.haml +55 -0
- data/lib/archsight/web/views/partials/artifact/_team.haml +83 -0
- data/lib/archsight/web/views/partials/artifact/_workflow.haml +69 -0
- data/lib/archsight/web/views/partials/components/_activity.haml +37 -0
- data/lib/archsight/web/views/partials/components/_git.haml +17 -0
- data/lib/archsight/web/views/partials/components/_jira.haml +18 -0
- data/lib/archsight/web/views/partials/components/_languages.haml +29 -0
- data/lib/archsight/web/views/partials/components/_owner.haml +15 -0
- data/lib/archsight/web/views/partials/components/_repositories.haml +37 -0
- data/lib/archsight/web/views/partials/components/_status.haml +23 -0
- data/lib/archsight/web/views/partials/instance/_detail.haml +99 -0
- data/lib/archsight/web/views/partials/instance/_graph.haml +6 -0
- data/lib/archsight/web/views/partials/instance/_list.haml +84 -0
- data/lib/archsight/web/views/partials/instance/_relations.haml +43 -0
- data/lib/archsight/web/views/partials/instance/_requirements.haml +41 -0
- data/lib/archsight/web/views/partials/instance/_view_detail.haml +57 -0
- data/lib/archsight/web/views/partials/layout/_content.haml +40 -0
- data/lib/archsight/web/views/partials/layout/_error.haml +22 -0
- data/lib/archsight/web/views/partials/layout/_head.haml +24 -0
- data/lib/archsight/web/views/partials/layout/_navigation.haml +20 -0
- data/lib/archsight/web/views/partials/layout/_sidebar.haml +27 -0
- data/lib/archsight/web/views/search.haml +53 -0
- data/lib/archsight.rb +17 -0
- metadata +311 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
class Archsight::MCP::QueryTool < FastMcp::Tool
|
|
6
|
+
tool_name "query"
|
|
7
|
+
|
|
8
|
+
description <<~DESC.gsub("\n", " ").strip
|
|
9
|
+
Search and filter architecture resources using a powerful query language.
|
|
10
|
+
|
|
11
|
+
QUERY SYNTAX:
|
|
12
|
+
|
|
13
|
+
• Name search: 'kubernetes' (shortcut for name =~ "kubernetes"), 'name == "ExactName"', 'name =~ "pattern"'
|
|
14
|
+
|
|
15
|
+
• Kind filter prefix: 'TechnologyArtifact: ...' restricts search to that resource type
|
|
16
|
+
|
|
17
|
+
• Annotation filters: 'activity/status == "active"', 'scc/language/Go/loc > 5000'
|
|
18
|
+
Operators: == (equals), != (not equals), =~ (regex match), >, <, >=, <=
|
|
19
|
+
|
|
20
|
+
• Relation queries:
|
|
21
|
+
-> Kind (has outgoing relation to kind), -> "Name" (to specific instance)
|
|
22
|
+
<- Kind (has incoming relation from kind), <- "Name" (from specific instance)
|
|
23
|
+
~> Kind (transitively reaches), <~ Kind (transitively reached by)
|
|
24
|
+
-> none (no outgoing relations), <- none (no incoming relations / orphan detection)
|
|
25
|
+
|
|
26
|
+
• Sub-query targets: $(expression) - dynamic relation matching
|
|
27
|
+
-> $(expr) (relates to any resource matching expr)
|
|
28
|
+
~> $(expr) (transitively reaches any matching resource)
|
|
29
|
+
<- $(expr) (incoming from any matching resource)
|
|
30
|
+
|
|
31
|
+
• Logical operators: AND/and/&, OR/or/|, NOT/not/!, parentheses for grouping
|
|
32
|
+
|
|
33
|
+
EXAMPLES:
|
|
34
|
+
'kubernetes' - resources with "kubernetes" in name
|
|
35
|
+
'TechnologyArtifact: activity/status == "active"' - active TechnologyArtifacts
|
|
36
|
+
'-> ApplicationInterface & repository/artifacts == "container"' - containerized services exposing APIs
|
|
37
|
+
'<- none' - resources not referenced by anything (potential orphans)
|
|
38
|
+
'-> none & <- none' - true orphans with no relations at all
|
|
39
|
+
'~> $(dcd-mf-dcxpress)' - transitively reaches instance matching "dcd-mf-dcxpress"
|
|
40
|
+
'<- $(TechnologyArtifact: activity/status == "active")' - referenced by active artifacts
|
|
41
|
+
DESC
|
|
42
|
+
arguments do
|
|
43
|
+
required(:query).filled(:string).description(
|
|
44
|
+
"Query string using the query language syntax. " \
|
|
45
|
+
'Examples: \'kubernetes\', \'TechnologyArtifact: activity/status == "active"\', ' \
|
|
46
|
+
"'-> ApplicationInterface', '<- none'"
|
|
47
|
+
)
|
|
48
|
+
optional(:output).filled(:string).description(
|
|
49
|
+
"Output format: " \
|
|
50
|
+
"'complete' = full resource details including annotations and relations (default), " \
|
|
51
|
+
"'brief' = minimal output with just kind and name for each resource, " \
|
|
52
|
+
"'count' = only totals grouped by kind, no individual resources returned"
|
|
53
|
+
)
|
|
54
|
+
optional(:limit).filled(:integer).description(
|
|
55
|
+
"Maximum results to return (1-500). Use with offset for pagination. Ignored when output='count'."
|
|
56
|
+
)
|
|
57
|
+
optional(:offset).filled(:integer).description(
|
|
58
|
+
"Number of results to skip. Use with limit for pagination (e.g., offset=50, limit=50 for page 2). " \
|
|
59
|
+
"Ignored when output='count'."
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def call(query:, output: "complete", limit: 50, offset: 0)
|
|
64
|
+
db = Archsight::MCP.db
|
|
65
|
+
|
|
66
|
+
begin
|
|
67
|
+
parsed_query = Archsight::Query.parse(query)
|
|
68
|
+
results = parsed_query.filter(db)
|
|
69
|
+
total = results.length
|
|
70
|
+
|
|
71
|
+
# Omit kind from output when filtering by a specific kind (it's redundant)
|
|
72
|
+
omit_kind = !parsed_query.kind_filter.nil?
|
|
73
|
+
|
|
74
|
+
# Count-only mode: return totals grouped by kind
|
|
75
|
+
if output == "count"
|
|
76
|
+
by_kind = results.group_by { |r| r.class.to_s.split("::").last }
|
|
77
|
+
.transform_values(&:length)
|
|
78
|
+
|
|
79
|
+
result = {
|
|
80
|
+
query: query,
|
|
81
|
+
total: total,
|
|
82
|
+
by_kind: by_kind
|
|
83
|
+
}
|
|
84
|
+
else
|
|
85
|
+
# Complete or brief mode: return paginated resources
|
|
86
|
+
paginated = results.sort_by(&:name).drop(offset).take(limit)
|
|
87
|
+
resources = case output
|
|
88
|
+
when "brief"
|
|
89
|
+
paginated.map { |r| Archsight::MCP.brief_summary(r, omit_kind: omit_kind) }
|
|
90
|
+
else # "complete"
|
|
91
|
+
paginated.map { |r| Archsight::MCP.complete_summary(r, omit_kind: omit_kind) }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
result = {
|
|
95
|
+
query: query,
|
|
96
|
+
total: total,
|
|
97
|
+
limit: limit,
|
|
98
|
+
offset: offset,
|
|
99
|
+
count: paginated.length,
|
|
100
|
+
resources: resources
|
|
101
|
+
}
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
JSON.pretty_generate(result)
|
|
105
|
+
rescue Archsight::Query::QueryError => e
|
|
106
|
+
JSON.pretty_generate({
|
|
107
|
+
error: "Query error",
|
|
108
|
+
message: e.message,
|
|
109
|
+
query: query
|
|
110
|
+
})
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
class Archsight::MCP::ResourceDocTool < FastMcp::Tool
|
|
6
|
+
tool_name "resource_doc"
|
|
7
|
+
|
|
8
|
+
description <<~DESC.gsub("\n", " ").strip
|
|
9
|
+
Get documentation for architecture resource kinds.
|
|
10
|
+
|
|
11
|
+
TWO MODES OF OPERATION:
|
|
12
|
+
|
|
13
|
+
1. LIST MODE (no kind specified): Returns a list of all available resource kinds with their descriptions.
|
|
14
|
+
Useful for discovering what types of resources exist in the architecture model.
|
|
15
|
+
Example: call with no parameters
|
|
16
|
+
|
|
17
|
+
2. DOCUMENTATION MODE (kind specified): Returns detailed documentation for a specific resource kind.
|
|
18
|
+
Includes description, available annotations, relations, and a YAML example template.
|
|
19
|
+
Example: kind="TechnologyArtifact"
|
|
20
|
+
|
|
21
|
+
COMMON RESOURCE KINDS:
|
|
22
|
+
• TechnologyArtifact - Code repositories, artifacts, and technology components
|
|
23
|
+
• ApplicationComponent - Services and application building blocks
|
|
24
|
+
• ApplicationInterface - APIs and interfaces exposed by components
|
|
25
|
+
• ApplicationService - Business services provided by applications
|
|
26
|
+
• BusinessRequirement - Compliance controls and business requirements
|
|
27
|
+
• ComplianceEvidence - Evidence linking artifacts to compliance requirements
|
|
28
|
+
DESC
|
|
29
|
+
arguments do
|
|
30
|
+
optional(:kind).filled(:string).description(
|
|
31
|
+
'Resource kind to get documentation for (e.g., "TechnologyArtifact", "ApplicationComponent"). ' \
|
|
32
|
+
"If not specified, returns a list of all available resource kinds."
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def call(kind: nil)
|
|
37
|
+
if kind.nil? || kind.empty?
|
|
38
|
+
list_resource_kinds
|
|
39
|
+
else
|
|
40
|
+
get_resource_documentation(kind)
|
|
41
|
+
end
|
|
42
|
+
rescue StandardError => e
|
|
43
|
+
error_response(e.message, e.class.name)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def error_response(message, error_type = "Error")
|
|
49
|
+
JSON.pretty_generate({
|
|
50
|
+
error: error_type,
|
|
51
|
+
message: message
|
|
52
|
+
})
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def list_resource_kinds
|
|
56
|
+
kinds = []
|
|
57
|
+
|
|
58
|
+
Archsight::Resources.each do |kind_symbol|
|
|
59
|
+
klass = Archsight::Resources.const_get(kind_symbol)
|
|
60
|
+
kinds << {
|
|
61
|
+
kind: kind_symbol.to_s,
|
|
62
|
+
description: klass.description || "No description available",
|
|
63
|
+
annotation_count: klass.annotations.count,
|
|
64
|
+
relation_count: klass.relations.count
|
|
65
|
+
}
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
result = {
|
|
69
|
+
total: kinds.length,
|
|
70
|
+
resource_kinds: kinds
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
JSON.pretty_generate(result)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def get_resource_documentation(kind)
|
|
77
|
+
klass = Archsight::Resources[kind]
|
|
78
|
+
return error_response("Unknown resource kind: #{kind}") unless klass
|
|
79
|
+
|
|
80
|
+
content = Archsight::Documentation.generate(kind)
|
|
81
|
+
|
|
82
|
+
JSON.pretty_generate({
|
|
83
|
+
kind: kind,
|
|
84
|
+
documentation: content
|
|
85
|
+
})
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# AST module contains all Abstract Syntax Tree node types for the query language
|
|
4
|
+
module Archsight::Query::AST
|
|
5
|
+
# Base class for all AST nodes
|
|
6
|
+
class Node
|
|
7
|
+
def accept(visitor)
|
|
8
|
+
raise NotImplementedError
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Root query node with optional kind filter
|
|
13
|
+
class QueryNode < Node
|
|
14
|
+
attr_reader :kind_filter, :expression
|
|
15
|
+
|
|
16
|
+
def initialize(kind_filter, expression)
|
|
17
|
+
@kind_filter = kind_filter # String or nil
|
|
18
|
+
@expression = expression # Expression node
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def accept(visitor)
|
|
22
|
+
visitor.visit_query(self)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Binary logical operation (AND, OR)
|
|
27
|
+
class BinaryOp < Node
|
|
28
|
+
attr_reader :operator, :left, :right
|
|
29
|
+
|
|
30
|
+
def initialize(operator, left, right)
|
|
31
|
+
@operator = operator # :and, :or
|
|
32
|
+
@left = left
|
|
33
|
+
@right = right
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def accept(visitor)
|
|
37
|
+
visitor.visit_binary_op(self)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Unary NOT operation
|
|
42
|
+
class NotOp < Node
|
|
43
|
+
attr_reader :operand
|
|
44
|
+
|
|
45
|
+
def initialize(operand)
|
|
46
|
+
@operand = operand
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def accept(visitor)
|
|
50
|
+
visitor.visit_not_op(self)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Annotation condition: annotation_path op value
|
|
55
|
+
class AnnotationCondition < Node
|
|
56
|
+
attr_reader :path, :operator, :value
|
|
57
|
+
|
|
58
|
+
def initialize(path, operator, value)
|
|
59
|
+
@path = path # String (e.g., "activity/status")
|
|
60
|
+
@operator = operator # String (==, !=, =~, >, <, >=, <=)
|
|
61
|
+
@value = value # StringValue, NumberValue, or RegexValue
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def accept(visitor)
|
|
65
|
+
visitor.visit_annotation_condition(self)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Annotation existence condition: annotation_path? (checks if annotation exists)
|
|
70
|
+
class AnnotationExistsCondition < Node
|
|
71
|
+
attr_reader :path
|
|
72
|
+
|
|
73
|
+
def initialize(path)
|
|
74
|
+
@path = path # String (e.g., "activity/status")
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def accept(visitor)
|
|
78
|
+
visitor.visit_annotation_exists_condition(self)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Annotation "in" condition: annotation_path in (value1, value2, ...)
|
|
83
|
+
class AnnotationInCondition < Node
|
|
84
|
+
attr_reader :path, :values
|
|
85
|
+
|
|
86
|
+
def initialize(path, values)
|
|
87
|
+
@path = path # String (e.g., "repository/artifacts")
|
|
88
|
+
@values = values # Array of StringValue
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def accept(visitor)
|
|
92
|
+
visitor.visit_annotation_in_condition(self)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Kind condition: kind == "SomeKind" or kind =~ "pattern"
|
|
97
|
+
class KindCondition < Node
|
|
98
|
+
attr_reader :operator, :value
|
|
99
|
+
|
|
100
|
+
def initialize(operator, value)
|
|
101
|
+
@operator = operator # String (==, =~)
|
|
102
|
+
@value = value # StringValue or RegexValue
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def accept(visitor)
|
|
106
|
+
visitor.visit_kind_condition(self)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Kind "in" condition: kind in ("Kind1", "Kind2", ...)
|
|
111
|
+
class KindInCondition < Node
|
|
112
|
+
attr_reader :values
|
|
113
|
+
|
|
114
|
+
def initialize(values)
|
|
115
|
+
@values = values # Array of StringValue
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def accept(visitor)
|
|
119
|
+
visitor.visit_kind_in_condition(self)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Name condition: name == "value" or name =~ "pattern"
|
|
124
|
+
class NameCondition < Node
|
|
125
|
+
attr_reader :operator, :value
|
|
126
|
+
|
|
127
|
+
def initialize(operator, value)
|
|
128
|
+
@operator = operator # String (==, !=, =~)
|
|
129
|
+
@value = value # StringValue or RegexValue
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def accept(visitor)
|
|
133
|
+
visitor.visit_name_condition(self)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Name "in" condition: name in ("name1", "name2", ...)
|
|
138
|
+
class NameInCondition < Node
|
|
139
|
+
attr_reader :values
|
|
140
|
+
|
|
141
|
+
def initialize(values)
|
|
142
|
+
@values = values # Array of StringValue
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def accept(visitor)
|
|
146
|
+
visitor.visit_name_in_condition(self)
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Outgoing direct relation: -> Kind or -> "InstanceName"
|
|
151
|
+
# With optional verb filter: -{verb}> or -{!verb}>
|
|
152
|
+
class OutgoingDirectRelation < Node
|
|
153
|
+
attr_reader :target, :verbs, :exclude_verbs
|
|
154
|
+
|
|
155
|
+
def initialize(target, verbs = nil, exclude_verbs = false)
|
|
156
|
+
@target = target # KindTarget or InstanceTarget
|
|
157
|
+
@verbs = verbs # Array of verb strings, or nil for "all verbs"
|
|
158
|
+
@exclude_verbs = exclude_verbs # true = denylist, false = allowlist
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def accept(visitor)
|
|
162
|
+
visitor.visit_outgoing_direct_relation(self)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Outgoing transitive relation: ~> Kind or ~> "InstanceName"
|
|
167
|
+
# With optional verb filter: ~{verb}> or ~{!verb}>
|
|
168
|
+
class OutgoingTransitiveRelation < Node
|
|
169
|
+
attr_reader :target, :max_depth, :verbs, :exclude_verbs
|
|
170
|
+
|
|
171
|
+
def initialize(target, verbs = nil, exclude_verbs = false, max_depth = 10)
|
|
172
|
+
@target = target
|
|
173
|
+
@verbs = verbs # Array of verb strings, or nil for "all verbs"
|
|
174
|
+
@exclude_verbs = exclude_verbs # true = denylist, false = allowlist
|
|
175
|
+
@max_depth = max_depth
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def accept(visitor)
|
|
179
|
+
visitor.visit_outgoing_transitive_relation(self)
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Incoming direct relation: <- Kind or <- "InstanceName"
|
|
184
|
+
# With optional verb filter: <{verb}- or <{!verb}-
|
|
185
|
+
class IncomingDirectRelation < Node
|
|
186
|
+
attr_reader :target, :verbs, :exclude_verbs
|
|
187
|
+
|
|
188
|
+
def initialize(target, verbs = nil, exclude_verbs = false)
|
|
189
|
+
@target = target # KindTarget or InstanceTarget
|
|
190
|
+
@verbs = verbs # Array of verb strings, or nil for "all verbs"
|
|
191
|
+
@exclude_verbs = exclude_verbs # true = denylist, false = allowlist
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def accept(visitor)
|
|
195
|
+
visitor.visit_incoming_direct_relation(self)
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Incoming transitive relation: <~ Kind or <~ "InstanceName"
|
|
200
|
+
# With optional verb filter: <{verb}~ or <{!verb}~
|
|
201
|
+
class IncomingTransitiveRelation < Node
|
|
202
|
+
attr_reader :target, :max_depth, :verbs, :exclude_verbs
|
|
203
|
+
|
|
204
|
+
def initialize(target, verbs = nil, exclude_verbs = false, max_depth = 10)
|
|
205
|
+
@target = target
|
|
206
|
+
@verbs = verbs # Array of verb strings, or nil for "all verbs"
|
|
207
|
+
@exclude_verbs = exclude_verbs # true = denylist, false = allowlist
|
|
208
|
+
@max_depth = max_depth
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def accept(visitor)
|
|
212
|
+
visitor.visit_incoming_transitive_relation(self)
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Relation target: a kind name (identifier)
|
|
217
|
+
class KindTarget
|
|
218
|
+
attr_reader :kind_name
|
|
219
|
+
|
|
220
|
+
def initialize(kind_name)
|
|
221
|
+
@kind_name = kind_name
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Relation target: a specific instance name (string)
|
|
226
|
+
class InstanceTarget
|
|
227
|
+
attr_reader :instance_name
|
|
228
|
+
|
|
229
|
+
def initialize(instance_name)
|
|
230
|
+
@instance_name = instance_name
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Relation target: nothing (no relations)
|
|
235
|
+
# Used with -> # (no outgoing) or <- # (no incoming)
|
|
236
|
+
class NothingTarget
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# Relation target: sub-query result
|
|
240
|
+
# Used with -> $(expr) to match against query results
|
|
241
|
+
class SubqueryTarget
|
|
242
|
+
attr_reader :query
|
|
243
|
+
|
|
244
|
+
def initialize(query)
|
|
245
|
+
@query = query # QueryNode - the inner sub-query to evaluate
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# Value types
|
|
250
|
+
class StringValue
|
|
251
|
+
attr_reader :value
|
|
252
|
+
|
|
253
|
+
def initialize(value)
|
|
254
|
+
@value = value
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
class NumberValue
|
|
259
|
+
attr_reader :value
|
|
260
|
+
|
|
261
|
+
def initialize(value)
|
|
262
|
+
@value = value.to_f
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
class RegexValue
|
|
267
|
+
attr_reader :pattern, :flags
|
|
268
|
+
|
|
269
|
+
def initialize(pattern, flags = "")
|
|
270
|
+
@pattern = pattern
|
|
271
|
+
@flags = flags
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def to_regexp
|
|
275
|
+
options = @flags.include?("i") ? Regexp::IGNORECASE : 0
|
|
276
|
+
Regexp.new(@pattern, options)
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Base error class for all query-related errors
|
|
4
|
+
class Archsight::Query::QueryError < StandardError
|
|
5
|
+
attr_reader :position, :source
|
|
6
|
+
|
|
7
|
+
def initialize(message, position: nil, source: nil)
|
|
8
|
+
@position = position
|
|
9
|
+
@source = source
|
|
10
|
+
super(message)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def to_s
|
|
14
|
+
if @position && @source
|
|
15
|
+
line_info = extract_line_info
|
|
16
|
+
"#{super}\n#{line_info}"
|
|
17
|
+
else
|
|
18
|
+
super
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def extract_line_info
|
|
25
|
+
return "" unless @source && @position
|
|
26
|
+
|
|
27
|
+
pointer = "#{" " * @position}^"
|
|
28
|
+
" #{@source}\n #{pointer}"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Error during lexical analysis (tokenization)
|
|
33
|
+
class Archsight::Query::LexerError < Archsight::Query::QueryError; end
|
|
34
|
+
|
|
35
|
+
# Error during parsing
|
|
36
|
+
class Archsight::Query::ParseError < Archsight::Query::QueryError; end
|
|
37
|
+
|
|
38
|
+
# Error during query evaluation
|
|
39
|
+
class Archsight::Query::EvaluationError < Archsight::Query::QueryError; end
|