1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-14 22:42:41 +01:00

Get rid of lowdown!

This A.I. slop library has the same license as the C code from lowdown
upon which it is based. This keeps our options open for upstreaming, if
we want to do that.
This commit is contained in:
John Ericson 2025-11-12 16:35:34 -05:00
parent 3645671570
commit 3e13a4d410
13 changed files with 1418 additions and 109 deletions

View file

@ -13,6 +13,7 @@ project(
) )
# Internal Libraries # Internal Libraries
subproject('libcmarkcpp')
subproject('libutil') subproject('libutil')
subproject('libstore') subproject('libstore')
subproject('libfetchers') subproject('libfetchers')

View file

@ -174,24 +174,6 @@ rec {
buildNoTests = forAllSystems (system: nixpkgsFor.${system}.native.nixComponents2.nix-cli); buildNoTests = forAllSystems (system: nixpkgsFor.${system}.native.nixComponents2.nix-cli);
# Toggles some settings for better coverage. Windows needs these
# library combinations, and Debian build Nix with GNU readline too.
buildReadlineNoMarkdown =
let
components = forAllSystems (
system:
nixpkgsFor.${system}.native.nixComponents2.overrideScope (
self: super: {
nix-cmd = super.nix-cmd.override {
enableMarkdown = false;
readlineFlavor = "readline";
};
}
)
);
in
forAllPackages (pkgName: forAllSystems (system: components.${system}.${pkgName}));
# Perl bindings for various platforms. # Perl bindings for various platforms.
perlBindings = forAllSystems (system: nixpkgsFor.${system}.native.nixComponents2.nix-perl-bindings); perlBindings = forAllSystems (system: nixpkgsFor.${system}.native.nixComponents2.nix-perl-bindings);

View file

@ -0,0 +1,17 @@
Copyright (c) 2008, Natacha Porté
Copyright (c) 2011, Vicent Martí
Copyright (c) 2014, Xavier Mendez, Devin Torres and the Hoedown authors
Copyright (c) 2016--2023, Kristaps Dzonsons
Copyright (c) 2025, Obsidian Systems
Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.

36
src/libcmarkcpp/README.md Normal file
View file

@ -0,0 +1,36 @@
# libcmarkcpp
A C++ terminal renderer for CommonMark documents.
## Overview
libcmarkcpp provides a terminal renderer for CommonMark (Markdown) documents using the cmark library. It renders formatted, colored output suitable for display in ANSI-capable terminals.
## Features
- ANSI color styling and text formatting (bold, italic, underline)
- Intelligent text wrapping and indentation
- Support for:
- Headers with hierarchical styling
- Lists (ordered and unordered)
- Code blocks (fenced and indented)
- Blockquotes
- Links with OSC8 hyperlink support
- Inline code, bold, and italic
- Horizontal rules
- Images
- Terminal width detection and adaptive wrapping
- Configurable margins, padding, and styling options
## Origin
This library is a C++ port of the terminal renderer from [lowdown](https://github.com/kristapsdz/lowdown) by Kristaps Dzonsons, adapted to work with the [cmark](https://github.com/commonmark/cmark) CommonMark implementation.
## License
ISC License (same as lowdown)
## Dependencies
- cmark >= 0.31.0
- C++20 compiler

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,89 @@
#pragma once
/**
* @file cmark-cpp.hh
* @brief C++ wrappers for the cmark CommonMark library
*/
#include <cmark.h>
#include <memory>
#include <string_view>
#include <cassert>
namespace cmark {
using Node = struct cmark_node;
using NodeType = cmark_node_type;
using ListType = cmark_list_type;
using Iter = struct cmark_iter;
struct Deleter
{
void operator()(Node * ptr)
{
cmark_node_free(ptr);
}
void operator()(Iter * ptr)
{
cmark_iter_free(ptr);
}
void operator()(char * ptr)
{
free(ptr);
}
};
template<typename T>
using UniquePtr = std::unique_ptr<Node, Deleter>;
static inline void parse_document(Node & root, std::string_view s, int options)
{
cmark_parser * parser = cmark_parser_new_with_mem_into_root(options, cmark_get_default_mem_allocator(), &root);
cmark_parser_feed(parser, s.data(), s.size());
(void) cmark_parser_finish(parser);
cmark_parser_free(parser);
}
static inline UniquePtr<Node> parse_document(std::string_view s, int options)
{
return UniquePtr<Node>{cmark_parse_document(s.data(), s.size(), options)};
}
static inline std::unique_ptr<char, Deleter> render_commonmark(Node & root, int options, int width)
{
return std::unique_ptr<char, Deleter>{cmark_render_commonmark(&root, options, width)};
}
static inline std::unique_ptr<char, Deleter> render_xml(Node & root, int options)
{
return std::unique_ptr<char, Deleter>{cmark_render_xml(&root, options)};
}
static inline UniquePtr<Node> node_new(NodeType type)
{
return UniquePtr<Node>{cmark_node_new(type)};
}
/**
* The parent takes ownership
*/
static inline Node & node_append_child(Node & node, UniquePtr<Node> child)
{
auto status = (bool) cmark_node_append_child(&node, &*child);
assert(status);
return *child.release();
}
static inline bool node_set_literal(Node & node, const char * content)
{
return (bool) cmark_node_set_literal(&node, content);
}
static inline bool node_set_list_type(Node & node, ListType type)
{
return (bool) cmark_node_set_list_type(&node, type);
}
} // namespace cmark

View file

@ -0,0 +1,108 @@
#pragma once
///@file
#include "cmark-cpp.hh"
#include <string>
#include <vector>
#include <memory>
namespace cmark {
/**
* Terminal rendering options
*/
struct TerminalOptions
{
/** Terminal width in columns */
size_t cols = 80;
/** Content width (0 = auto, max 80 or cols) */
size_t width = 0;
/** Horizontal margin (left padding) */
size_t hmargin = 0;
/** Horizontal padding (additional left padding) */
size_t hpadding = 4;
/** Vertical margin (blank lines before/after) */
size_t vmargin = 0;
/** Center content */
bool centre = false;
/** Disable ANSI escape sequences */
bool noAnsi = false;
/** Disable ANSI colors only */
bool noColor = false;
/** Don't show any link URLs */
bool noLink = false;
/** Don't show relative link URLs */
bool noRelLink = false;
/** Shorten long URLs */
bool shortLink = false;
};
/**
* Style attributes for terminal output
*/
struct Style
{
bool italic = false;
bool strike = false;
bool bold = false;
bool under = false;
size_t bcolour = 0; // Background color (ANSI code)
size_t colour = 0; // Foreground color (ANSI code)
int override = 0; // Override flags
static constexpr int OVERRIDE_UNDER = 0x01;
static constexpr int OVERRIDE_BOLD = 0x02;
bool hasStyle() const
{
return colour || bold || italic || under || strike || bcolour || override;
}
};
/**
* Terminal renderer for CommonMark documents
*
* Renders a CMark AST to ANSI terminal output with styling, wrapping,
* and proper indentation.
*/
class TerminalRenderer
{
public:
/**
* Create a new terminal renderer with the given options
*/
explicit TerminalRenderer(const TerminalOptions & opts = TerminalOptions{});
~TerminalRenderer();
// Non-copyable
TerminalRenderer(const TerminalRenderer &) = delete;
TerminalRenderer & operator=(const TerminalRenderer &) = delete;
/**
* Render a CMark node tree to a string
*/
std::string render(cmark::Node & root);
private:
struct Impl;
std::unique_ptr<Impl> impl;
};
/**
* Convenience function to render a CMark document to terminal output
*/
std::string renderTerminal(cmark::Node & root, const TerminalOptions & opts = TerminalOptions{});
} // namespace cmark

View file

@ -0,0 +1,57 @@
project(
'cmark-cpp',
'cpp',
version : '0.1',
default_options : [
'cpp_std=c++20',
'warning_level=1',
],
meson_version : '>= 1.1',
license : 'ISC',
)
cxx = meson.get_compiler('cpp')
# CMark dependency
cmark = dependency('libcmark', required : true)
sources = files(
'cmark-terminal.cc',
)
headers = files(
'include/cmark/cmark-cpp.hh',
'include/cmark/cmark-terminal.hh',
)
include_dirs = include_directories('include')
libcmarkcpp = library(
'cmarkcpp',
sources,
dependencies : cmark,
include_directories : include_dirs,
install : true,
version : meson.project_version(),
)
install_headers(headers, subdir : 'cmark')
# Make this available as a dependency for meson projects
meson.override_dependency(
'cmark-cpp',
declare_dependency(
include_directories : include_dirs,
link_with : libcmarkcpp,
dependencies : cmark,
),
)
# Pkg-config file
pkg = import('pkgconfig')
pkg.generate(
libcmarkcpp,
name : 'cmark-cpp',
description : 'C++ terminal renderer for CommonMark',
url : 'https://github.com/NixOS/nix',
)

View file

@ -1,79 +1,38 @@
#include "nix/cmd/markdown.hh" #include "nix/cmd/markdown.hh"
#include "nix/util/environment-variables.hh" #include "nix/util/environment-variables.hh"
#include "nix/util/error.hh" #include "nix/util/error.hh"
#include "nix/util/finally.hh"
#include "nix/util/terminal.hh" #include "nix/util/terminal.hh"
#include "cmd-config-private.hh" #include <cmark/cmark-cpp.hh>
#include <cmark/cmark-terminal.hh>
#if HAVE_LOWDOWN
# include <sys/queue.h>
# include <lowdown.h>
#endif
namespace nix { namespace nix {
#if HAVE_LOWDOWN
static std::string doRenderMarkdownToTerminal(std::string_view markdown) static std::string doRenderMarkdownToTerminal(std::string_view markdown)
{ {
int windowWidth = getWindowSize().second; int windowWidth = getWindowSize().second;
# if HAVE_LOWDOWN_1_4 // Set up terminal rendering options
struct lowdown_opts_term opts_term{ ::cmark::TerminalOptions opts;
.cols = (size_t) std::max(windowWidth - 5, 60), opts.cols = std::max(windowWidth - 5, 60);
.hmargin = 0, opts.hmargin = 0;
.vmargin = 0, opts.vmargin = 0;
}; opts.noRelLink = true; // Skip rendering relative links
# endif
struct lowdown_opts opts{
.type = LOWDOWN_TERM,
# if HAVE_LOWDOWN_1_4
.term = opts_term,
# endif
.maxdepth = 20,
# if !HAVE_LOWDOWN_1_4
.cols = (size_t) std::max(windowWidth - 5, 60),
.hmargin = 0,
.vmargin = 0,
# endif
.feat = LOWDOWN_COMMONMARK | LOWDOWN_FENCED | LOWDOWN_DEFLIST | LOWDOWN_TABLES,
.oflags =
# if HAVE_LOWDOWN_1_4
LOWDOWN_TERM_NORELLINK // To render full links while skipping relative ones
# else
LOWDOWN_TERM_NOLINK
# endif
};
if (!isTTY()) if (!isTTY())
opts.oflags |= LOWDOWN_TERM_NOANSI; opts.noAnsi = true;
auto doc = lowdown_doc_new(&opts); // Parse the markdown document
auto doc = ::cmark::parse_document(markdown, CMARK_OPT_DEFAULT);
if (!doc) if (!doc)
throw Error("cannot allocate Markdown document");
Finally freeDoc([&]() { lowdown_doc_free(doc); });
size_t maxn = 0;
auto node = lowdown_doc_parse(doc, &maxn, markdown.data(), markdown.size(), nullptr);
if (!node)
throw Error("cannot parse Markdown document"); throw Error("cannot parse Markdown document");
Finally freeNode([&]() { lowdown_node_free(node); });
auto renderer = lowdown_term_new(&opts); try {
if (!renderer) // Render to terminal
throw Error("cannot allocate Markdown renderer"); return ::cmark::renderTerminal(*doc, opts);
Finally freeRenderer([&]() { lowdown_term_free(renderer); }); } catch (const std::exception & e) {
throw Error("error rendering Markdown: %s", e.what());
auto buf = lowdown_buf_new(16384); }
if (!buf)
throw Error("cannot allocate Markdown output buffer");
Finally freeBuffer([&]() { lowdown_buf_free(buf); });
int rndr_res = lowdown_term_rndr(buf, renderer, node);
if (!rndr_res)
throw Error("allocation error while rendering Markdown");
return std::string(buf->data, buf->size);
} }
std::string renderMarkdownToTerminal(std::string_view markdown) std::string renderMarkdownToTerminal(std::string_view markdown)
@ -84,11 +43,4 @@ std::string renderMarkdownToTerminal(std::string_view markdown)
return doRenderMarkdownToTerminal(markdown); return doRenderMarkdownToTerminal(markdown);
} }
#else
std::string renderMarkdownToTerminal(std::string_view markdown)
{
return std::string(markdown);
}
#endif
} // namespace nix } // namespace nix

View file

@ -26,25 +26,13 @@ deps_public_maybe_subproject = [
dependency('nix-expr'), dependency('nix-expr'),
dependency('nix-flake'), dependency('nix-flake'),
dependency('nix-main'), dependency('nix-main'),
dependency('cmark-cpp'),
] ]
subdir('nix-meson-build-support/subprojects') subdir('nix-meson-build-support/subprojects')
nlohmann_json = dependency('nlohmann_json', version : '>= 3.9') nlohmann_json = dependency('nlohmann_json', version : '>= 3.9')
deps_public += nlohmann_json deps_public += nlohmann_json
lowdown = dependency(
'lowdown',
version : '>= 0.9.0',
required : get_option('markdown'),
)
deps_private += lowdown
configdata.set('HAVE_LOWDOWN', lowdown.found().to_int())
# The API changed slightly around terminal initialization.
configdata.set(
'HAVE_LOWDOWN_1_4',
lowdown.version().version_compare('>= 1.4.0').to_int(),
)
readline_flavor = get_option('readline-flavor') readline_flavor = get_option('readline-flavor')
if readline_flavor == 'editline' if readline_flavor == 'editline'
editline = dependency('libeditline', 'editline', version : '>=1.14') editline = dependency('libeditline', 'editline', version : '>=1.14')

View file

@ -1,11 +1,5 @@
# vim: filetype=meson # vim: filetype=meson
option(
'markdown',
type : 'feature',
description : 'Enable Markdown rendering in the Nix binary (requires lowdown)',
)
option( option(
'readline-flavor', 'readline-flavor',
type : 'combo', type : 'combo',

View file

@ -11,16 +11,12 @@
nix-main, nix-main,
editline, editline,
readline, readline,
lowdown,
nlohmann_json, nlohmann_json,
# Configuration Options # Configuration Options
version, version,
# Whether to enable Markdown rendering in the Nix binary.
enableMarkdown ? !stdenv.hostPlatform.isWindows,
# Which interactive line editor library to use for Nix's repl. # Which interactive line editor library to use for Nix's repl.
# #
# Currently supported choices are: # Currently supported choices are:
@ -53,8 +49,7 @@ mkMesonLibrary (finalAttrs: {
buildInputs = [ buildInputs = [
({ inherit editline readline; }.${readlineFlavor}) ({ inherit editline readline; }.${readlineFlavor})
] ];
++ lib.optional enableMarkdown lowdown;
propagatedBuildInputs = [ propagatedBuildInputs = [
nix-util nix-util
@ -67,7 +62,6 @@ mkMesonLibrary (finalAttrs: {
]; ];
mesonFlags = [ mesonFlags = [
(lib.mesonEnable "markdown" enableMarkdown)
(lib.mesonOption "readline-flavor" readlineFlavor) (lib.mesonOption "readline-flavor" readlineFlavor)
]; ];

View file

@ -24,6 +24,7 @@ deps_public_maybe_subproject = [
dependency('nix-util'), dependency('nix-util'),
dependency('nix-store'), dependency('nix-store'),
dependency('nix-fetchers'), dependency('nix-fetchers'),
dependency('cmark-cpp'),
] ]
subdir('nix-meson-build-support/subprojects') subdir('nix-meson-build-support/subprojects')
subdir('nix-meson-build-support/big-objs') subdir('nix-meson-build-support/big-objs')