Skip to content

Commit

Permalink
Add the ability to output code using CSS classes
Browse files Browse the repository at this point in the history
  • Loading branch information
gjtorikian committed Dec 2, 2023
1 parent 2d1dffe commit fef7d87
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 102 deletions.
142 changes: 72 additions & 70 deletions ext/commonmarker/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,7 @@ use options::iterate_options_hash;

mod plugins;
use plugins::{
syntax_highlighting::{
fetch_syntax_highlighter_path, fetch_syntax_highlighter_theme,
SYNTAX_HIGHLIGHTER_PLUGIN_DEFAULT_THEME,
},
syntax_highlighting::{fetch_syntax_highlighter_path, fetch_syntax_highlighter_theme},
SYNTAX_HIGHLIGHTER_PLUGIN,
};

Expand Down Expand Up @@ -57,78 +54,83 @@ fn commonmark_to_html(args: &[Value]) -> Result<String, magnus::Error> {

let theme = match rb_plugins.get(Symbol::new(SYNTAX_HIGHLIGHTER_PLUGIN)) {
Some(syntax_highlighter_options) => {
fetch_syntax_highlighter_theme(syntax_highlighter_options)?
}
None => SYNTAX_HIGHLIGHTER_PLUGIN_DEFAULT_THEME.to_string(), // no `syntax_highlighter:` defined
};

let path = match rb_plugins.get(Symbol::new(SYNTAX_HIGHLIGHTER_PLUGIN)) {
Some(syntax_highlighter_options) => {
fetch_syntax_highlighter_path(syntax_highlighter_options)?
match fetch_syntax_highlighter_theme(syntax_highlighter_options) {
Ok(theme) => theme,
Err(e) => {
return Err(e);
}
}
}
None => PathBuf::from("".to_string()), // no `syntax_highlighter:` defined
None => None, // no `syntax_highlighter:` defined
};

if !path.eq(&PathBuf::from("".to_string())) && !path.exists() {
return Err(Error::new(
exception::arg_error(),
"path does not exist".to_string(),
));
}

if theme.is_empty() && path.exists() {
return Err(Error::new(
exception::arg_error(),
"`path` also needs `theme` passed into the `syntax_highlighter`",
));
}
if path.exists() && !path.is_dir() {
return Err(Error::new(
exception::arg_error(),
"`path` needs to be a directory",
));
}

if path.exists() {
let builder = SyntectAdapterBuilder::new();
let mut ts = ThemeSet::load_defaults();

match ts.add_from_folder(&path) {
Ok(_) => {}
Err(e) => {
return Err(Error::new(
exception::arg_error(),
format!("failed to load theme set from path: {e}"),
));
match theme {
None => syntax_highlighter = None,
Some(theme) => {
if theme.is_empty() {
// no theme? uss css classes
adapter = SyntectAdapter::new(None);
syntax_highlighter = Some(&adapter);
} else {
let path = match rb_plugins.get(Symbol::new(SYNTAX_HIGHLIGHTER_PLUGIN)) {
Some(syntax_highlighter_options) => {
fetch_syntax_highlighter_path(syntax_highlighter_options)?
}
None => PathBuf::from("".to_string()), // no `syntax_highlighter:` defined
};

if path.exists() {
if !path.is_dir() {
return Err(Error::new(
exception::arg_error(),
"`path` needs to be a directory",
));
}

let builder = SyntectAdapterBuilder::new();
let mut ts = ThemeSet::load_defaults();

match ts.add_from_folder(&path) {
Ok(_) => {}
Err(e) => {
return Err(Error::new(
exception::arg_error(),
format!("failed to load theme set from path: {e}"),
));
}
}

// check if the theme exists in the dir
match ts.themes.get(&theme) {
Some(theme) => theme,
None => {
return Err(Error::new(
exception::arg_error(),
format!("theme `{}` does not exist", theme),
));
}
};

adapter = builder.theme_set(ts).theme(&theme).build();

syntax_highlighter = Some(&adapter);
} else {
// no path? default theme lookup
ThemeSet::load_defaults()
.themes
.get(&theme)
.ok_or_else(|| {
Error::new(
exception::arg_error(),
format!("theme `{}` does not exist", theme),
)
})?;
adapter = SyntectAdapter::new(Some(&theme));
syntax_highlighter = Some(&adapter);
}
}
}

ts.themes.get(&theme).ok_or_else(|| {
Error::new(
exception::arg_error(),
format!("theme `{}` does not exist", theme),
)
})?;

adapter = builder.theme_set(ts).theme(&theme).build();

syntax_highlighter = Some(&adapter);
} else if theme.is_empty() || theme == "none" {
syntax_highlighter = None;
} else {
ThemeSet::load_defaults()
.themes
.get(&theme)
.ok_or_else(|| {
Error::new(
exception::arg_error(),
format!("theme `{}` does not exist", theme),
)
})?;
adapter = SyntectAdapter::new(Some(&theme));
syntax_highlighter = Some(&adapter);
}

comrak_plugins.render.codefence_syntax_highlighter = syntax_highlighter;

Ok(markdown_to_html_with_plugins(
Expand Down
31 changes: 23 additions & 8 deletions ext/commonmarker/src/plugins/syntax_highlighting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,43 @@ use crate::EMPTY_STR;

pub const SYNTAX_HIGHLIGHTER_PLUGIN_THEME_KEY: &str = "theme";
pub const SYNTAX_HIGHLIGHTER_PLUGIN_PATH_KEY: &str = "path";
pub const SYNTAX_HIGHLIGHTER_PLUGIN_DEFAULT_THEME: &str = "base16-ocean.dark";

pub fn fetch_syntax_highlighter_theme(value: Value) -> Result<String, magnus::Error> {
pub fn fetch_syntax_highlighter_theme(value: Value) -> Result<Option<String>, magnus::Error> {
if value.is_nil() {
// `syntax_highlighter: nil`
return Ok(EMPTY_STR.to_string());
return Ok(None);
}

let syntax_highlighter_plugin: RHash = match TryConvert::try_convert(value) {
Ok(plugin) => plugin, // `syntax_highlighter: { theme: "<something>" }`
Err(e) => {
// not a hash!
return Err(e);
}
};

if syntax_highlighter_plugin.is_nil() || syntax_highlighter_plugin.is_empty() {
return Err(magnus::Error::new(
magnus::exception::type_error(),
"theme cannot be blank hash",
));
}

let syntax_highlighter_plugin: RHash = TryConvert::try_convert(value)?;
let theme_key = Symbol::new(SYNTAX_HIGHLIGHTER_PLUGIN_THEME_KEY);

match syntax_highlighter_plugin.get(theme_key) {
Some(theme) => {
if theme.is_nil() {
// `syntax_highlighter: { theme: nil }`
return Ok(EMPTY_STR.to_string());
return Err(magnus::Error::new(
magnus::exception::type_error(),
"theme cannot be nil",
));
}
Ok(TryConvert::try_convert(theme)?)
}
None => {
// `syntax_highlighter: { }`
Ok(EMPTY_STR.to_string())
// `syntax_highlighter: { theme: nil }`
Ok(None)
}
}
}
Expand Down
48 changes: 24 additions & 24 deletions test/syntax_highlighting_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def hello
assert_equal(result, html)
end

def test_lack_of_theme_has_no_highlighting
def test_non_hash_value_is_an_error
code = <<~CODE
```ruby
def hello
Expand All @@ -61,19 +61,12 @@ def hello
```
CODE

html = Commonmarker.to_html(code, plugins: { syntax_highlighter: {} })

result = <<~CODE
<pre lang="ruby"><code>def hello
puts &quot;hello&quot;
end
</code></pre>
CODE

assert_equal(result, html)
assert_raises(TypeError) do
Commonmarker.to_html(code, plugins: { syntax_highlighter: "wow!" })
end
end

def test_nil_theme_removes_highlighting
def test_lack_of_theme_is_an_error
code = <<~CODE
```ruby
def hello
Expand All @@ -82,19 +75,26 @@ def hello
```
CODE

html = Commonmarker.to_html(code, plugins: { syntax_highlighter: { theme: nil } })
assert_raises(TypeError) do
Commonmarker.to_html(code, plugins: { syntax_highlighter: {} })
end
end

result = <<~CODE
<pre lang="ruby"><code>def hello
puts &quot;hello&quot;
def test_nil_theme_is_an_error
code = <<~CODE
```ruby
def hello
puts "hello"
end
</code></pre>
```
CODE

assert_equal(result, html)
assert_raises(TypeError) do
Commonmarker.to_html(code, plugins: { syntax_highlighter: { theme: nil } })
end
end

def test_empty_theme_is_no_highlighting
def test_empty_theme_provides_class_highlighting
code = <<~CODE
```ruby
def hello
Expand All @@ -106,10 +106,10 @@ def hello
html = Commonmarker.to_html(code, plugins: { syntax_highlighter: { theme: "" } })

result = <<~CODE
<pre lang="ruby"><code>def hello
puts &quot;hello&quot;
end
</code></pre>
<pre class="syntax-highlighting"><code><span class="source ruby"><span class="meta function ruby"><span class="keyword control def ruby">def</span></span><span class="meta function ruby"> <span class="entity name function ruby">hello</span></span>
<span class="support function builtin ruby">puts</span> <span class="string quoted double ruby"><span class="punctuation definition string begin ruby">&quot;</span>hello<span class="punctuation definition string end ruby">&quot;</span></span>
<span class="keyword control ruby">end</span>
</span></code></pre>
CODE

assert_equal(result, html)
Expand Down Expand Up @@ -152,7 +152,7 @@ def hello
CODE

assert_raises(ArgumentError) do
Commonmarker.to_html(code, plugins: { syntax_highlighter: { path: "test/fixtures" } })
Commonmarker.to_html(code, plugins: { syntax_highlighter: { theme: "WowieZowie", path: "test/fixtures" } })
end
end

Expand Down

0 comments on commit fef7d87

Please sign in to comment.