diff --git a/EMBEDDING.md b/EMBEDDING.md
index a83082b..327e923 100644
--- a/EMBEDDING.md
+++ b/EMBEDDING.md
@@ -87,6 +87,16 @@ Alternatively, you can specify a fragment using `start` and `end` patterns:
Patterns match the first and last lines of the desired fragment.
If a pattern is omitted, the fragment will start at the beginning or end at the end of the file, respectively.
+To embed a single line, use `line` with the same pattern syntax:
+
+````markdown
+
+```java
+```
+````
+
+The `line` attribute cannot be combined with `start`, `end`, or `fragment`.
+
### Pattern syntax
The tool supports an extended glob syntax for matching lines:
diff --git a/embedding/parsing/instruction.go b/embedding/parsing/instruction.go
index d061cd8..8628455 100644
--- a/embedding/parsing/instruction.go
+++ b/embedding/parsing/instruction.go
@@ -43,6 +43,8 @@ import (
// EndPattern — an optional glob-like pattern. If specified, lines after the matching one
// are excluded.
//
+// LinePattern — an optional glob-like pattern. If specified, only the matching line is embedded.
+//
// CommentMode — specifies which comments are retained in the embedded code.
//
// DocumentationFile — a documentation file containing the instruction.
@@ -55,6 +57,7 @@ type Instruction struct {
Fragment string
StartPattern *Pattern
EndPattern *Pattern
+ LinePattern *Pattern
CommentMode commentfilter.Mode
DocumentationFile string
DocumentationLine int
@@ -93,6 +96,7 @@ func (e PatternNotFoundError) Error() string {
// - start — an optional glob-like pattern. If specified, lines before the matching one
// are excluded;
// - end — an optional glob-like pattern. If specified, lines after the matching one are excluded.
+// - line — an optional glob-like pattern. If specified, only the matching line is embedded.
// - comments — an optional comment filtering mode. If omitted, all comments are retained.
//
// config — a Configuration with all embed-code settings.
@@ -104,16 +108,22 @@ func NewInstruction(
fragment := attributes["fragment"]
startValue := attributes["start"]
endValue := attributes["end"]
+ lineValue := attributes["line"]
commentMode, err := commentfilter.ParseMode(attributes["comments"])
if err != nil {
return Instruction{}, err
}
- if fragment != "" && (startValue != "" || endValue != "") {
+ if fragment != "" && (startValue != "" || endValue != "" || lineValue != "") {
+ return Instruction{},
+ fmt.Errorf(" must NOT specify both a fragment name and start/end/line patterns")
+ }
+ if lineValue != "" && (startValue != "" || endValue != "") {
return Instruction{},
- fmt.Errorf(" must NOT specify both a fragment name and start/end patterns")
+ fmt.Errorf(" must NOT specify both a line pattern and start/end patterns")
}
var end *Pattern
+ var line *Pattern
var start *Pattern
if startValue != "" {
@@ -124,12 +134,17 @@ func NewInstruction(
endPattern := NewPattern(endValue)
end = &endPattern
}
+ if lineValue != "" {
+ linePattern := NewPattern(lineValue)
+ line = &linePattern
+ }
return Instruction{
CodeFile: codeFile,
Fragment: fragment,
StartPattern: start,
EndPattern: end,
+ LinePattern: line,
CommentMode: commentMode,
Configuration: config,
}, nil
@@ -143,7 +158,7 @@ func (e Instruction) Content() ([]string, error) {
if err != nil {
return nil, err
}
- if e.StartPattern != nil || e.EndPattern != nil {
+ if e.StartPattern != nil || e.EndPattern != nil || e.LinePattern != nil {
codeFileReference, err := fragmentation.ResolveCodeFileReference(e.CodeFile, e.Configuration)
if err != nil {
return nil, err
@@ -166,15 +181,28 @@ func (e Instruction) Content() ([]string, error) {
// Returns string representation of Instruction.
func (e Instruction) String() string {
return fmt.Sprintf(
- "EmbeddingInstruction[file=`%s`, fragment=`%s`, start=`%s`, end=`%s`, comments=`%s`]",
- e.CodeFile, e.Fragment, e.StartPattern, e.EndPattern, e.CommentMode,
+ "EmbeddingInstruction[file=`%s`, fragment=`%s`, start=`%s`, end=`%s`, line=`%s`, comments=`%s`]",
+ e.CodeFile, e.Fragment, e.StartPattern, e.EndPattern, e.LinePattern, e.CommentMode,
)
}
-// Filters and returns a subset of input lines based on start and end patterns.
+// Filters and returns a subset of input lines based on start, end, or line patterns.
//
// lines — a list of strings representing the input lines.
func (e Instruction) matchingLines(lines []string, codeFileReference string) ([]string, error) {
+ if e.LinePattern != nil {
+ linePosition, err := e.matchGlob(
+ e.LinePattern, lines, 0, "line", codeFileReference,
+ )
+ if err != nil {
+ return nil, err
+ }
+ requiredLines := []string{lines[linePosition]}
+ indentation := indent.MaxCommonIndentation(requiredLines)
+
+ return indent.CutIndent(requiredLines, indentation), nil
+ }
+
startPosition := 0
if e.StartPattern != nil {
var err error
diff --git a/embedding/parsing/instruction_test.go b/embedding/parsing/instruction_test.go
index 80e7981..0425f98 100644
--- a/embedding/parsing/instruction_test.go
+++ b/embedding/parsing/instruction_test.go
@@ -37,6 +37,7 @@ type TestInstructionParams struct {
fragment string
startGlob string
endGlob string
+ lineGlob string
comments string
closeTag bool
}
@@ -205,6 +206,36 @@ var _ = Describe("Instruction", func() {
Expect(parsing.FromXML(xmlString, config)).Error().Should(HaveOccurred())
})
+ It("should have an error when parsing fragment with line glob", func() {
+ instructionParams := TestInstructionParams{
+ fragment: "fragment",
+ lineGlob: "public void hello()",
+ }
+ xmlString := buildInstruction("org/example/Hello.java", instructionParams)
+
+ Expect(parsing.FromXML(xmlString, config)).Error().Should(HaveOccurred())
+ })
+
+ It("should have an error when parsing line glob with start glob", func() {
+ instructionParams := TestInstructionParams{
+ startGlob: "public class*",
+ lineGlob: "public void hello()",
+ }
+ xmlString := buildInstruction("org/example/Hello.java", instructionParams)
+
+ Expect(parsing.FromXML(xmlString, config)).Error().Should(HaveOccurred())
+ })
+
+ It("should have an error when parsing line glob with end glob", func() {
+ instructionParams := TestInstructionParams{
+ endGlob: "*System.out*",
+ lineGlob: "public void hello()",
+ }
+ xmlString := buildInstruction("org/example/Hello.java", instructionParams)
+
+ Expect(parsing.FromXML(xmlString, config)).Error().Should(HaveOccurred())
+ })
+
It("should successfully parse XML from start to end glob", func() {
instructionParams := TestInstructionParams{
startGlob: "public class*",
@@ -257,6 +288,19 @@ var _ = Describe("Instruction", func() {
Expect(actualLines[0]).Should(Equal(expectedFirstLine))
})
+ It("should embed only the matching line when line glob is specified", func() {
+ instructionParams := TestInstructionParams{
+ lineGlob: "*class*",
+ }
+
+ actualLines := getXMLExtractionContent(
+ "org/example/Hello.java", instructionParams, config)
+
+ Expect(actualLines).Should(Equal([]string{
+ "public class Hello {",
+ }))
+ })
+
It("should successfully parse XML by only end glob", func() {
instructionParams := TestInstructionParams{
endGlob: "package*",
@@ -308,7 +352,7 @@ var _ = Describe("Instruction", func() {
Expect(actualLines[1]).Should(MatchRegexp(expectedLastLinePattern))
})
- It("should embed one line when the start and end globs match the same line", func() {
+ It("should embed one line when the start and end globs match the same line", func() {
instructionParams := TestInstructionParams{
startGlob: "*spine.enableJava()*",
endGlob: "*.server()",
@@ -423,6 +467,10 @@ func buildInstruction(fileName string, params TestInstructionParams) string {
endAttr := xmlAttribute("end", params.endGlob)
instructionLine += " " + endAttr
}
+ if len(params.lineGlob) > 0 {
+ lineAttr := xmlAttribute("line", params.lineGlob)
+ instructionLine += " " + lineAttr
+ }
if len(params.comments) > 0 {
commentsAttr := xmlAttribute("comments", params.comments)
instructionLine += " " + commentsAttr
diff --git a/embedding/parsing/xml_parse.go b/embedding/parsing/xml_parse.go
index 330d87e..9d4dcda 100644
--- a/embedding/parsing/xml_parse.go
+++ b/embedding/parsing/xml_parse.go
@@ -47,6 +47,7 @@ type Item struct {
// - start — an optional glob-like pattern. If specified, lines before the matching one
// are excluded;
// - end — an optional glob-like pattern. If specified, lines after the matching one are excluded.
+// - line — an optional glob-like pattern. If specified, only the matching line is embedded.
// - comments — an optional comment filtering mode. If omitted, all comments are retained.
//
// config — a Configuration with all embed-code settings.