or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

build.mdcheck.mdconfiguration.mdcreate.mdfragments.mdindex.mdproject.mdvcs.md

fragments.mddocs/

0

# Fragment Processing

1

2

Core functionality for discovering, parsing, and rendering news fragments into formatted changelog entries.

3

4

## Capabilities

5

6

### Fragment Discovery

7

8

Find and load news fragment files from the filesystem.

9

10

```python { .api }

11

def find_fragments(

12

base_directory: str,

13

config: Config,

14

strict: bool = False

15

) -> tuple[dict[str, dict[tuple[str, str, int], str]], list[tuple[str, str]]]:

16

"""

17

Find and load news fragment files.

18

19

Args:

20

base_directory: Base directory to search from

21

config: Configuration object with fragment settings

22

strict: If True, fail on invalid fragment filenames

23

24

Returns:

25

tuple: (fragment_contents, fragment_files)

26

- fragment_contents: Dict mapping section -> {(issue, type, counter): content}

27

- fragment_files: List of (filename, category) tuples

28

"""

29

```

30

31

### Fragment Parsing

32

33

Parse fragment filenames to extract issue, type, and counter information.

34

35

```python { .api }

36

def parse_newfragment_basename(

37

basename: str,

38

frag_type_names: Iterable[str]

39

) -> tuple[str, str, int] | tuple[None, None, None]:

40

"""

41

Parse a news fragment basename into components.

42

43

Args:

44

basename: Fragment filename without path

45

frag_type_names: Valid fragment type names

46

47

Returns:

48

tuple: (issue, category, counter) or (None, None, None) if invalid

49

50

Examples:

51

"123.feature" -> ("123", "feature", 0)

52

"456.bugfix.2" -> ("456", "bugfix", 2)

53

"fix-1.2.3.feature" -> ("fix-1.2.3", "feature", 0)

54

"+abc123.misc" -> ("+abc123", "misc", 0)

55

"""

56

```

57

58

### Fragment Organization

59

60

Split fragments by type and organize for rendering.

61

62

```python { .api }

63

def split_fragments(

64

fragment_contents: dict[str, dict[tuple[str, str, int], str]],

65

types: Mapping[str, Mapping[str, Any]],

66

all_bullets: bool = True

67

) -> dict[str, dict[str, list[tuple[str, list[str]]]]]:

68

"""

69

Split and organize fragments by section and type.

70

71

Args:

72

fragment_contents: Raw fragment content by section

73

types: Fragment type configuration

74

all_bullets: Whether to format all entries as bullet points

75

76

Returns:

77

dict: Organized fragments as {section: {type: [(issue, [content_lines])]}}

78

"""

79

```

80

81

### Fragment Rendering

82

83

Render organized fragments into final changelog format.

84

85

```python { .api }

86

def render_fragments(

87

template: str,

88

issue_format: str | None,

89

fragments: Mapping[str, Mapping[str, Mapping[str, Sequence[str]]]],

90

definitions: Mapping[str, Mapping[str, Any]],

91

underlines: Sequence[str],

92

wrap: bool,

93

versiondata: Mapping[str, str],

94

top_underline: str = "=",

95

all_bullets: bool = False,

96

render_title: bool = True,

97

md_header_level: int = 1

98

) -> str:

99

"""

100

Render fragments using Jinja2 template.

101

102

Args:

103

template: Jinja2 template string for formatting

104

issue_format: Optional format string for issue links

105

fragments: Organized fragment data by section/type

106

definitions: Type definitions and formatting rules

107

underlines: Sequence of characters for section underlining

108

wrap: Whether to wrap text output

109

versiondata: Version information for template variables

110

top_underline: Character for top-level section underlines

111

all_bullets: Whether to use bullets for all content

112

render_title: Whether to render the release title

113

md_header_level: Header level for Markdown output

114

115

Returns:

116

Formatted changelog content as string

117

"""

118

```

119

120

### News File Writing

121

122

Write rendered content to news files with proper insertion and existing content handling.

123

124

```python { .api }

125

def append_to_newsfile(

126

directory: str,

127

filename: str,

128

start_string: str,

129

top_line: str,

130

content: str,

131

single_file: bool

132

) -> None:

133

"""

134

Write content to directory/filename behind start_string.

135

136

Args:

137

directory: Directory containing the news file

138

filename: Name of the news file

139

start_string: Marker string for content insertion

140

top_line: Release header line to check for duplicates

141

content: Rendered changelog content to insert

142

single_file: Whether to append to existing file or create new

143

144

Raises:

145

ValueError: If top_line already exists in the file

146

"""

147

148

def _figure_out_existing_content(

149

news_file: Path,

150

start_string: str,

151

single_file: bool

152

) -> tuple[str, str]:

153

"""

154

Split existing news file into header and body parts.

155

156

Args:

157

news_file: Path to the news file

158

start_string: Marker string to split on

159

single_file: Whether this is a single file or per-release

160

161

Returns:

162

tuple: (header_content, existing_body_content)

163

"""

164

```

165

166

## Fragment Path Utilities

167

168

### FragmentsPath Class

169

170

Helper class for managing fragment directory paths.

171

172

```python { .api }

173

class FragmentsPath:

174

"""

175

Helper for getting full paths to fragment directories.

176

177

Handles both explicit directory configuration and package-based

178

fragment location resolution.

179

"""

180

181

def __init__(self, base_directory: str, config: Config):

182

"""

183

Initialize path helper.

184

185

Args:

186

base_directory: Base project directory

187

config: Configuration object

188

"""

189

190

def __call__(self, section_directory: str = "") -> str:

191

"""

192

Get fragment directory path for a section.

193

194

Args:

195

section_directory: Section subdirectory name

196

197

Returns:

198

str: Full path to fragment directory

199

"""

200

```

201

202

## Sorting and Organization

203

204

### Issue Sorting

205

206

```python { .api }

207

class IssueParts(NamedTuple):

208

"""Components of an issue identifier for sorting."""

209

prefix: str

210

number: int | None

211

suffix: str

212

213

def issue_key(issue: str) -> IssueParts:

214

"""

215

Generate sort key for issue identifiers.

216

217

Args:

218

issue: Issue identifier string

219

220

Returns:

221

IssueParts: Sortable components

222

223

Examples:

224

"123" -> IssueParts("", 123, "")

225

"issue-456" -> IssueParts("issue-", 456, "")

226

"+orphan" -> IssueParts("+", None, "orphan")

227

"""

228

```

229

230

### Entry Sorting

231

232

```python { .api }

233

def entry_key(entry: tuple[str, Sequence[str]]) -> tuple[str, list[IssueParts]]:

234

"""

235

Generate sort key for changelog entries.

236

237

Args:

238

entry: (issue_list, content_lines) tuple

239

240

Returns:

241

tuple: (concatenated_issues, parsed_issue_parts)

242

"""

243

244

def bullet_key(entry: tuple[str, Sequence[str]]) -> int:

245

"""

246

Generate sort key for bullet point entries.

247

248

Args:

249

entry: (issue_list, content_lines) tuple

250

251

Returns:

252

int: Number of lines in entry (for consistent ordering)

253

"""

254

```

255

256

## Content Processing

257

258

### Text Formatting

259

260

```python { .api }

261

def indent(text: str, prefix: str) -> str:

262

"""

263

Indent text lines with given prefix.

264

265

Args:

266

text: Text to indent

267

prefix: Prefix to add to each line

268

269

Returns:

270

str: Indented text

271

"""

272

273

def append_newlines_if_trailing_code_block(text: str) -> str:

274

"""

275

Add newlines after trailing code blocks for proper formatting.

276

277

Args:

278

text: Text content to process

279

280

Returns:

281

str: Text with proper code block spacing

282

"""

283

```

284

285

### Issue Reference Rendering

286

287

```python { .api }

288

def render_issue(issue_format: str | None, issue: str) -> str:

289

"""

290

Format issue references according to configuration.

291

292

Args:

293

issue_format: Format string with {issue} placeholder

294

issue: Issue identifier

295

296

Returns:

297

str: Formatted issue reference

298

299

Examples:

300

render_issue("#{issue}", "123") -> "#123"

301

render_issue("`#{issue} <url/{issue}>`_", "456") -> "`#456 <url/456>`_"

302

"""

303

```

304

305

## Usage Examples

306

307

### Basic Fragment Processing

308

309

```python

310

from towncrier._builder import find_fragments, split_fragments, render_fragments

311

from towncrier._settings import load_config_from_options

312

313

# Load configuration

314

base_directory, config = load_config_from_options(None, None)

315

316

# Find fragments

317

fragment_contents, fragment_files = find_fragments(

318

base_directory=base_directory,

319

config=config,

320

strict=True

321

)

322

323

# Organize fragments

324

fragments = split_fragments(

325

fragment_contents=fragment_contents,

326

types=config.types,

327

all_bullets=config.all_bullets

328

)

329

330

# Render to string

331

template = "# Version {version}\n\n{% for section in sections %}..."

332

rendered = render_fragments(

333

fragments=fragments,

334

config=config,

335

template=template,

336

project_name="My Project",

337

project_version="1.0.0",

338

project_date="2024-01-15"

339

)

340

```

341

342

### Custom Fragment Directory

343

344

```python

345

from towncrier._builder import FragmentsPath

346

347

# Create path helper

348

fragments_path = FragmentsPath(

349

base_directory="/path/to/project",

350

config=config

351

)

352

353

# Get fragment directories

354

main_fragments_dir = fragments_path() # Main section

355

api_fragments_dir = fragments_path("api") # API section

356

```

357

358

### Fragment Filename Parsing

359

360

```python

361

from towncrier._builder import parse_newfragment_basename

362

363

fragment_types = ["feature", "bugfix", "doc", "removal", "misc"]

364

365

# Parse various fragment names

366

result1 = parse_newfragment_basename("123.feature", fragment_types)

367

# -> ("123", "feature", 0)

368

369

result2 = parse_newfragment_basename("fix-auth.bugfix.2", fragment_types)

370

# -> ("fix-auth", "bugfix", 2)

371

372

result3 = parse_newfragment_basename("+orphan.misc", fragment_types)

373

# -> ("+orphan", "misc", 0)

374

```

375

376

## Template Variables

377

378

When rendering fragments, these variables are available in Jinja2 templates:

379

380

- `name`: Project name

381

- `version`: Project version

382

- `date`: Project date

383

- `sections`: Dictionary of section data

384

- `underlines`: RST underline characters

385

- `fragments`: Organized fragment data

386

387

## Error Handling

388

389

Fragment processing handles these error scenarios:

390

391

- **Invalid fragment filenames**: Optionally strict validation

392

- **Missing fragment directories**: Graceful fallback

393

- **Template rendering errors**: Jinja2 syntax or variable errors

394

- **File encoding issues**: UTF-8 encoding problems

395

- **Content parsing errors**: Malformed fragment content