-
Notifications
You must be signed in to change notification settings - Fork 144
Expand file tree
/
Copy pathextractor.go
More file actions
191 lines (174 loc) · 7.54 KB
/
Copy pathextractor.go
File metadata and controls
191 lines (174 loc) · 7.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
package parser
import (
"io"
"sort"
"strings"
"github.com/zzet/gortex/internal/graph"
)
// Extractor extracts graph nodes and edges from a single source file.
type Extractor interface {
Language() string
Extensions() []string
Extract(filePath string, src []byte) (*ExtractionResult, error)
}
// ExtractionOptions is immutable request-scoped parser configuration. Its
// fields remain private so an extractor cannot mutate repository-owned state.
// Use NewExtractionOptions to construct a normalized value and accessors to
// obtain defensive copies for serialization.
type ExtractionOptions struct {
temporalEnvHelpers []string
}
// NewExtractionOptions normalizes Temporal helper names by trimming whitespace,
// dropping empty entries, exact case-sensitive deduplication, and sorting. The
// stable order makes crash-worker serialization deterministic without changing
// Go identifier semantics.
func NewExtractionOptions(temporalEnvHelpers []string) ExtractionOptions {
if len(temporalEnvHelpers) == 0 {
return ExtractionOptions{}
}
seen := make(map[string]struct{}, len(temporalEnvHelpers))
normalized := make([]string, 0, len(temporalEnvHelpers))
for _, name := range temporalEnvHelpers {
name = strings.TrimSpace(name)
if name == "" {
continue
}
if _, exists := seen[name]; exists {
continue
}
seen[name] = struct{}{}
normalized = append(normalized, name)
}
if len(normalized) == 0 {
return ExtractionOptions{}
}
sort.Strings(normalized)
return ExtractionOptions{temporalEnvHelpers: normalized}
}
// TemporalEnvHelpers returns a defensive copy of the configured names.
func (o ExtractionOptions) TemporalEnvHelpers() []string {
return append([]string(nil), o.temporalEnvHelpers...)
}
// OptionsExtractor is an optional capability. Extractor remains unchanged so
// existing languages and external plugins keep their source compatibility.
type OptionsExtractor interface {
ExtractWithOptions(filePath string, src []byte, opts ExtractionOptions) (*ExtractionResult, error)
}
// Extract dispatches one request through the optional options-aware capability,
// falling back to the base Extractor contract for ordinary languages/plugins.
func Extract(e Extractor, filePath string, src []byte, opts ExtractionOptions) (*ExtractionResult, error) {
if oe, ok := e.(OptionsExtractor); ok {
return oe.ExtractWithOptions(filePath, src, opts)
}
return e.Extract(filePath, src)
}
// PreParser is an optional Extractor capability: a source-rewriting hook run
// before tree-sitter parsing. It lets a language neutralise constructs that
// confuse the grammar (e.g. C-family conditional-compilation directives that
// detach an enclosing declaration) without discarding any code.
//
// Implementations MUST preserve byte offsets and line counts exactly — the
// returned slice has the same length as the input and every newline stays in
// place — so all extracted node ranges, line numbers, and downstream
// resolution remain byte-accurate. Returning nil means "no rewrite".
//
// The hook is a first-class, language-agnostic interface rather than a
// per-language private step: any extractor opts in by implementing it, and the
// same offset-preserving rewrite machinery is then reusable across languages.
type PreParser interface {
PreParse(src []byte) []byte
}
// ApplyPreParse runs e's PreParse hook when e implements PreParser and the hook
// returns a non-nil rewrite; otherwise it returns src unchanged. The identity
// default means extractors opt in by implementing PreParser, with no behaviour
// change for those that don't.
func ApplyPreParse(e Extractor, src []byte) []byte {
if pp, ok := e.(PreParser); ok {
if rewritten := pp.PreParse(src); rewritten != nil {
return rewritten
}
}
return src
}
// StreamingExtractor is an optional Extractor capability: an extractor that
// reads the file itself — one unit (page / slide / sheet) at a time — through
// an io.ReaderAt, emitting nodes and edges as it goes, instead of receiving the
// whole file as a byte slice. Content extractors (PDF, office documents)
// implement it because their inputs are large and their per-unit processing
// never needs the whole file resident. The indexer prefers this path when it is
// available (on the in-process route), so peak memory is O(one unit), not
// O(file).
//
// emit is called once per produced node with that node's outgoing edges (pass
// nil for none). The extractor MUST recover its own per-document panics or
// return an error; the indexer isolates failures either way.
type StreamingExtractor interface {
Extractor
ExtractStream(filePath string, r io.ReaderAt, size int64, emit func(*graph.Node, []*graph.Edge)) error
}
// AssetClass labels a non-code extractor by the kind of large
// binary/document/data artifact it ingests, so the indexer can apply
// corpus-admission caps before a heavy file is ever read and extracted.
// Code extractors return the empty class and are never gated.
type AssetClass string
const (
// AssetDocument is a human document ingested as searchable content
// (one KindDoc node per page / slide / sheet): PDF, pptx, xlsx,
// plain text. Large ones dominate parse memory, so they are subject
// to a per-file size cap.
AssetDocument AssetClass = "document"
// AssetData is a pure binary / columnar / vector artifact recorded
// as a metadata-only node and never parsed: parquet, npy, lance, …
// Not code-intelligence content; admitted only when opted in.
AssetData AssetClass = "data"
// AssetImage is an image asset (metadata-only node). Cheap to
// admit; kept un-gated by default.
AssetImage AssetClass = "image"
)
// AssetExtractor is an optional Extractor capability: a non-code extractor
// that ingests large binary/document/data artifacts rather than source.
// The indexer reads AssetClass at walk time (via the registry, keyed by the
// detected language) to decide admission — a per-file size cap for documents
// and an opt-in gate for data assets — so a content-heavy corpus can't pull
// gigabytes of non-source artifacts into the parse pipeline. Extractors that
// don't implement it are treated as code (always admitted).
type AssetExtractor interface {
Extractor
AssetClass() AssetClass
}
// AssetClassOf returns e's AssetClass when e implements AssetExtractor, or the
// empty class (code) otherwise. The single place callers map an extractor to
// its admission class.
func AssetClassOf(e Extractor) AssetClass {
if a, ok := e.(AssetExtractor); ok {
return a.AssetClass()
}
return ""
}
// ExtractionResult holds the nodes and edges extracted from a single
// file, plus an optional handle to the parse tree the extractor used.
//
// When Tree is non-nil the indexer is responsible for releasing it
// after every per-file consumer (contract extractors, body-fact
// resolvers) has run. Languages whose extractor doesn't have a
// downstream consumer for the tree leave Tree as nil and close their
// own trees internally — the contract pipeline degrades to its regex
// fallback for those languages.
type ExtractionResult struct {
Nodes []*graph.Node
Edges []*graph.Edge
Tree *ParseTree
// ConstValues carries the literal value of each KindConstant node
// whose RHS is a string / numeric literal, for the indexer to persist
// in the queryable constant_values sidecar (kept out of the gob Meta
// blob). Keyed by the const node id. Empty for languages / files with
// no literal constants.
ConstValues []ConstValue
}
// ConstValue is one constant's persisted literal value: the const node id,
// its file (for file-scoped eviction), and the literal text.
type ConstValue struct {
NodeID string
FilePath string
Value string
}