This commit is contained in:
Josh Thomas 2025-04-29 13:09:43 -05:00
parent 39862b2d63
commit 5dbffd4494
35 changed files with 121 additions and 143 deletions

View file

@ -4,6 +4,7 @@ resolver = "2"
[workspace.dependencies] [workspace.dependencies]
djls = { path = "crates/djls" } djls = { path = "crates/djls" }
djls-conf = { path = "crates/djls-conf" }
djls-project = { path = "crates/djls-project" } djls-project = { path = "crates/djls-project" }
djls-server = { path = "crates/djls-server" } djls-server = { path = "crates/djls-server" }
djls-templates = { path = "crates/djls-templates" } djls-templates = { path = "crates/djls-templates" }

View file

@ -0,0 +1,16 @@
[package]
name = "djls-conf"
version = "0.0.0"
edition = "2021"
[dependencies]
djls-templates = { workspace = true }
anyhow = { workspace = true }
serde = { workspace = true }
thiserror = { workspace = true }
config = { version = "0.15", features = ["toml"] }
[dev-dependencies]
tempfile = "3.19"

View file

@ -0,0 +1,12 @@
use config::{Config, File, FileFormat, Value};
use djls_templates::TagSpecs;
use serde::Deserialize;
use thiserror::Error;
#[derive(Deserialize, Debug, Default, Clone)]
pub struct Settings {
#[serde(default)]
pub debug: bool,
#[serde(default)]
pub tagspecs: TagSpecs,
}

View file

@ -1,5 +1,5 @@
use crate::ast::{AstError, Span};
use crate::lexer::LexerError; use crate::lexer::LexerError;
use crate::nodes::{AstError, Span};
use crate::parser::ParserError; use crate::parser::ParserError;
use serde::Serialize; use serde::Serialize;
use thiserror::Error; use thiserror::Error;

View file

@ -1,15 +1,15 @@
mod ast;
mod error; mod error;
mod lexer; mod lexer;
mod nodes;
mod parser; mod parser;
mod tagspecs; mod tagspecs;
mod tokens; mod tokens;
use ast::Ast;
pub use error::{to_lsp_diagnostic, QuickFix, TemplateError}; pub use error::{to_lsp_diagnostic, QuickFix, TemplateError};
use lexer::Lexer; use lexer::Lexer;
use nodes::NodeList;
pub use parser::{Parser, ParserError}; pub use parser::{Parser, ParserError};
pub use tagspecs::TagSpecs;
/// Parses a Django template and returns the AST and any parsing errors. /// Parses a Django template and returns the AST and any parsing errors.
/// ///
@ -18,7 +18,7 @@ pub use parser::{Parser, ParserError};
/// ///
/// Returns a `Result` containing a tuple of `(Ast, Vec<ParserError>)` on success, /// Returns a `Result` containing a tuple of `(Ast, Vec<ParserError>)` on success,
/// or a `ParserError` on failure. /// or a `ParserError` on failure.
pub fn parse_template(source: &str) -> Result<(Ast, Vec<TemplateError>), TemplateError> { pub fn parse_template(source: &str) -> Result<(NodeList, Vec<TemplateError>), TemplateError> {
let tokens = Lexer::new(source) let tokens = Lexer::new(source)
.tokenize() .tokenize()
.map_err(|e| TemplateError::Lexer(e.to_string()))?; .map_err(|e| TemplateError::Lexer(e.to_string()))?;

View file

@ -3,14 +3,14 @@ use serde::Serialize;
use thiserror::Error; use thiserror::Error;
#[derive(Clone, Debug, Default, Serialize)] #[derive(Clone, Debug, Default, Serialize)]
pub struct Ast { pub struct NodeList {
nodelist: Vec<Node>, nodes: Vec<Node>,
line_offsets: LineOffsets, line_offsets: LineOffsets,
} }
impl Ast { impl NodeList {
pub fn nodelist(&self) -> &Vec<Node> { pub fn nodes(&self) -> &Vec<Node> {
&self.nodelist &self.nodes
} }
pub fn line_offsets(&self) -> &LineOffsets { pub fn line_offsets(&self) -> &LineOffsets {
@ -18,18 +18,11 @@ impl Ast {
} }
pub fn add_node(&mut self, node: Node) { pub fn add_node(&mut self, node: Node) {
self.nodelist.push(node); self.nodes.push(node);
} }
pub fn set_line_offsets(&mut self, tokens: &TokenStream) { pub fn set_line_offsets(&mut self, tokens: &TokenStream) {
for token in tokens.tokens() { self.line_offsets = LineOffsets::from_tokens(tokens)
if let TokenType::Newline = token.token_type() {
if let Some(start) = token.start() {
// Add offset for next line
self.line_offsets.add_line(start + 1);
}
}
}
} }
} }
@ -37,8 +30,20 @@ impl Ast {
pub struct LineOffsets(pub Vec<u32>); pub struct LineOffsets(pub Vec<u32>);
impl LineOffsets { impl LineOffsets {
pub fn from_tokens(tokens: &TokenStream) -> Self {
let mut offsets = LineOffsets::default();
for token in tokens.tokens() {
if let TokenType::Newline = token.token_type() {
if let Some(start) = token.start() {
offsets.add_line(start + token.length().unwrap_or(1));
}
}
}
offsets
}
pub fn add_line(&mut self, offset: u32) { pub fn add_line(&mut self, offset: u32) {
self.0.push(offset); self.0.push(offset)
} }
pub fn position_to_line_col(&self, position: usize) -> (usize, usize) { pub fn position_to_line_col(&self, position: usize) -> (usize, usize) {
@ -188,7 +193,7 @@ mod tests {
assert!(errors.is_empty()); assert!(errors.is_empty());
// Find the variable node // Find the variable node
let nodes = nodelist.nodelist(); let nodes = nodelist.nodes();
let var_node = nodes let var_node = nodes
.iter() .iter()
.find(|n| matches!(n, Node::Variable { .. })) .find(|n| matches!(n, Node::Variable { .. }))

View file

@ -1,5 +1,5 @@
use crate::ast::{Ast, AstError, Node, Span};
use crate::lexer::LexerError; use crate::lexer::LexerError;
use crate::nodes::{AstError, Node, NodeList, Span};
use crate::tokens::{Token, TokenStream, TokenType}; use crate::tokens::{Token, TokenStream, TokenType};
use thiserror::Error; use thiserror::Error;
@ -18,8 +18,8 @@ impl Parser {
} }
} }
pub fn parse(&mut self) -> Result<(Ast, Vec<ParserError>), ParserError> { pub fn parse(&mut self) -> Result<(NodeList, Vec<ParserError>), ParserError> {
let mut ast = Ast::default(); let mut ast = NodeList::default();
ast.set_line_offsets(&self.tokens); ast.set_line_offsets(&self.tokens);
while !self.is_at_end() { while !self.is_at_end() {

View file

@ -2,7 +2,7 @@
source: crates/djls-templates/src/parser.rs source: crates/djls-templates/src/parser.rs
expression: nodelist expression: nodelist
--- ---
nodelist: nodes:
- Text: - Text:
content: "<!-- HTML comment -->" content: "<!-- HTML comment -->"
span: span:

View file

@ -2,7 +2,7 @@
source: crates/djls-templates/src/parser.rs source: crates/djls-templates/src/parser.rs
expression: nodelist expression: nodelist
--- ---
nodelist: nodes:
- Tag: - Tag:
name: if name: if
bits: bits:

View file

@ -2,7 +2,7 @@
source: crates/djls-templates/src/parser.rs source: crates/djls-templates/src/parser.rs
expression: nodelist expression: nodelist
--- ---
nodelist: nodes:
- Tag: - Tag:
name: for name: for
bits: bits:

View file

@ -2,7 +2,7 @@
source: crates/djls-templates/src/parser.rs source: crates/djls-templates/src/parser.rs
expression: nodelist expression: nodelist
--- ---
nodelist: nodes:
- Tag: - Tag:
name: if name: if
bits: bits:

View file

@ -2,7 +2,7 @@
source: crates/djls-templates/src/parser.rs source: crates/djls-templates/src/parser.rs
expression: nodelist expression: nodelist
--- ---
nodelist: nodes:
- Tag: - Tag:
name: url name: url
bits: bits:

View file

@ -2,7 +2,7 @@
source: crates/djls-templates/src/parser.rs source: crates/djls-templates/src/parser.rs
expression: nodelist expression: nodelist
--- ---
nodelist: nodes:
- Variable: - Variable:
var: user.name var: user.name
filters: [] filters: []

View file

@ -2,7 +2,7 @@
source: crates/djls-templates/src/parser.rs source: crates/djls-templates/src/parser.rs
expression: nodelist expression: nodelist
--- ---
nodelist: nodes:
- Variable: - Variable:
var: user.name var: user.name
filters: filters:

View file

@ -2,7 +2,7 @@
source: crates/djls-templates/src/parser.rs source: crates/djls-templates/src/parser.rs
expression: nodelist expression: nodelist
--- ---
nodelist: nodes:
- Variable: - Variable:
var: value var: value
filters: filters:

View file

@ -2,7 +2,7 @@
source: crates/djls-templates/src/parser.rs source: crates/djls-templates/src/parser.rs
expression: nodelist expression: nodelist
--- ---
nodelist: nodes:
- Text: - Text:
content: "Welcome," content: "Welcome,"
span: span:

View file

@ -2,7 +2,7 @@
source: crates/djls-templates/src/parser.rs source: crates/djls-templates/src/parser.rs
expression: nodelist expression: nodelist
--- ---
nodelist: nodes:
- Tag: - Tag:
name: for name: for
bits: bits:

View file

@ -2,7 +2,7 @@
source: crates/djls-templates/src/parser.rs source: crates/djls-templates/src/parser.rs
expression: nodelist expression: nodelist
--- ---
nodelist: nodes:
- Text: - Text:
content: "<div class=\"container\">" content: "<div class=\"container\">"
span: span:

View file

@ -2,7 +2,7 @@
source: crates/djls-templates/src/parser.rs source: crates/djls-templates/src/parser.rs
expression: nodelist expression: nodelist
--- ---
nodelist: nodes:
- Tag: - Tag:
name: for name: for
bits: bits:

View file

@ -2,7 +2,7 @@
source: crates/djls-templates/src/parser.rs source: crates/djls-templates/src/parser.rs
expression: nodelist expression: nodelist
--- ---
nodelist: nodes:
- Tag: - Tag:
name: if name: if
bits: bits:

View file

@ -2,7 +2,7 @@
source: crates/djls-templates/src/parser.rs source: crates/djls-templates/src/parser.rs
expression: nodelist expression: nodelist
--- ---
nodelist: nodes:
- Text: - Text:
content: "<div>" content: "<div>"
span: span:

View file

@ -2,7 +2,7 @@
source: crates/djls-templates/src/parser.rs source: crates/djls-templates/src/parser.rs
expression: nodelist expression: nodelist
--- ---
nodelist: nodes:
- Text: - Text:
content: "<script>console.log('test');" content: "<script>console.log('test');"
span: span:

View file

@ -2,7 +2,7 @@
source: crates/djls-templates/src/parser.rs source: crates/djls-templates/src/parser.rs
expression: nodelist expression: nodelist
--- ---
nodelist: nodes:
- Text: - Text:
content: "<style>body { color: blue;" content: "<style>body { color: blue;"
span: span:

View file

@ -2,7 +2,7 @@
source: crates/djls-templates/src/parser.rs source: crates/djls-templates/src/parser.rs
expression: nodelist expression: nodelist
--- ---
nodelist: nodes:
- Text: - Text:
content: "<!DOCTYPE html>" content: "<!DOCTYPE html>"
span: span:

View file

@ -2,7 +2,7 @@
source: crates/djls-templates/src/parser.rs source: crates/djls-templates/src/parser.rs
expression: nodelist expression: nodelist
--- ---
nodelist: nodes:
- Text: - Text:
content: "<!DOCTYPE html>" content: "<!DOCTYPE html>"
span: span:

View file

@ -2,7 +2,7 @@
source: crates/djls-templates/src/parser.rs source: crates/djls-templates/src/parser.rs
expression: nodelist expression: nodelist
--- ---
nodelist: nodes:
- Text: - Text:
content: "<div class=\"container\">Hello</div>" content: "<div class=\"container\">Hello</div>"
span: span:

View file

@ -2,7 +2,7 @@
source: crates/djls-templates/src/parser.rs source: crates/djls-templates/src/parser.rs
expression: nodelist expression: nodelist
--- ---
nodelist: nodes:
- Text: - Text:
content: "<input type=\"text\" />" content: "<input type=\"text\" />"
span: span:

View file

@ -2,7 +2,7 @@
source: crates/djls-templates/src/parser.rs source: crates/djls-templates/src/parser.rs
expression: nodelist expression: nodelist
--- ---
nodelist: nodes:
- Text: - Text:
content: "<script type=\"text/javascript\">" content: "<script type=\"text/javascript\">"
span: span:

View file

@ -2,7 +2,7 @@
source: crates/djls-templates/src/parser.rs source: crates/djls-templates/src/parser.rs
expression: nodelist expression: nodelist
--- ---
nodelist: nodes:
- Text: - Text:
content: "<style type=\"text/css\">" content: "<style type=\"text/css\">"
span: span:

View file

@ -2,7 +2,7 @@
source: crates/djls-templates/src/parser.rs source: crates/djls-templates/src/parser.rs
expression: nodelist expression: nodelist
--- ---
nodelist: nodes:
- Text: - Text:
content: hello content: hello
span: span:

View file

@ -2,7 +2,7 @@
source: crates/djls-templates/src/parser.rs source: crates/djls-templates/src/parser.rs
expression: nodelist expression: nodelist
--- ---
nodelist: nodes:
- Text: - Text:
content: hello content: hello
span: span:

View file

@ -2,7 +2,7 @@
source: crates/djls-templates/src/parser.rs source: crates/djls-templates/src/parser.rs
expression: nodelist expression: nodelist
--- ---
nodelist: nodes:
- Text: - Text:
content: hello content: hello
span: span:

View file

@ -2,7 +2,7 @@
source: crates/djls-templates/src/parser.rs source: crates/djls-templates/src/parser.rs
expression: nodelist expression: nodelist
--- ---
nodelist: nodes:
- Text: - Text:
content: hello content: hello
span: span:

View file

@ -1,6 +1,5 @@
use anyhow::Result; use anyhow::Result;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use std::fs; use std::fs;
use std::path::{Path, PathBuf}; // Add PathBuf use std::path::{Path, PathBuf}; // Add PathBuf
@ -226,7 +225,8 @@ fn extract_specs(
if !current_path.is_empty() { if !current_path.is_empty() {
match TagSpec::deserialize(current_value.clone()) { match TagSpec::deserialize(current_value.clone()) {
Ok(tag_spec) => { Ok(tag_spec) => {
if let Some(tag_name) = current_path.split('.').last().filter(|s| !s.is_empty()) { if let Some(tag_name) = current_path.split('.').last().filter(|s| !s.is_empty())
{
specs_map.insert(tag_name.to_string(), tag_spec); specs_map.insert(tag_name.to_string(), tag_spec);
} else { } else {
return Err(format!( return Err(format!(
@ -296,7 +296,7 @@ mod tests {
// Renamed from test_can_load_builtins // Renamed from test_can_load_builtins
let content = r#" let content = r#"
# Using dotted path table names under [tagspecs] base # Using dotted path table names under [tagspecs] base
[tagspecs.django.template.defaulttags.if] // Corrected path [tagspecs.django.template.defaulttags.if]
end = { tag = "endif" } end = { tag = "endif" }
[tagspecs.django.template.defaulttags.block] [tagspecs.django.template.defaulttags.block]
end = { tag = "endblock" } end = { tag = "endblock" }

View file

@ -1,104 +1,48 @@
[django.template.defaulttags.autoescape] [tagspecs.django.template.defaulttags.autoescape]
args = [{ name = "setting", required = true, allowed_values = ["on", "off"] }] end = { tag = "endautoescape" }
closing = "endautoescape"
type = "container"
[django.template.defaulttags.block] [tagspecs.django.template.defaulttags.block]
closing = "endblock" end = { tag = "endblock" }
type = "container"
[django.template.defaulttags.comment] [tagspecs.django.template.defaulttags.comment]
closing = "endcomment" end = { tag = "endcomment" }
type = "container"
[django.template.defaulttags.cycle] [tagspecs.django.template.defaulttags.filter]
args = [ end = { tag = "endfilter" }
{ name = "cyclevars", required = true },
{ name = "variable_name", required = false, is_kwarg = true },
]
type = "single"
[django.template.defaulttags.debug] [tagspecs.django.template.defaulttags.for]
type = "single" end = { tag = "endfor" }
intermediates = [ "empty" ]
[django.template.defaulttags.extends] [tagspecs.django.template.defaulttags.if]
args = [{ name = "parent_name", required = true }] end = { tag = "endif" }
type = "inclusion" intermediates = [ "elif", "else" ]
[django.template.defaulttags.for] [tagspecs.django.template.defaulttags.ifchanged]
args = [ end = { tag = "endifchanged" }
{ name = "{item}", required = true }, intermediates = [ "else" ]
{ name = "in", required = true },
{ name = "{iterable}", required = true },
]
branches = ["empty"]
closing = "endfor"
type = "container"
[django.template.defaulttags.filter] [tagspecs.django.template.defaulttags.spaceless]
args = [{ name = "filter_expr", required = true }] end = { tag = "endspaceless" }
closing = "endfilter"
type = "container"
[django.template.defaulttags.firstof] [tagspecs.django.template.defaulttags.verbatim]
args = [{ name = "variables", required = true }] end = { tag = "endverbatim" }
type = "single"
[django.template.defaulttags.if] [tagspecs.django.template.defaulttags.with]
args = [{ name = "condition", required = true }] end = { tag = "endwith" }
branches = ["elif", "else"]
closing = "endif"
type = "container"
[django.template.defaulttags.include] [tagspecs.django.templatetags.cache.cache]
args = [ end = { tag = "endcache" }
{ name = "template", required = true },
{ name = "with", required = false, is_kwarg = true },
{ name = "only", required = false, is_kwarg = true },
]
type = "inclusion"
[django.template.defaulttags.load] [tagspecs.django.templatetags.i10n.localize]
args = [{ name = "library", required = true }] end = { tag = "endlocalize" }
type = "single"
[django.template.defaulttags.now] [tagspecs.django.templatetags.i18n.blocktranslate]
args = [{ name = "format_string", required = true }] end = { tag = "endblocktranslate" }
type = "single" intermediates = [ "plural" ]
[django.template.defaulttags.spaceless] [tagspecs.django.templatetags.tz.localtime]
closing = "endspaceless" end = { tag = "endlocaltime" }
type = "container"
[django.template.defaulttags.templatetag] [tagspecs.django.templatetags.tz.timezone]
type = "single" end = { tag = "endtimezone" }
[[django.template.defaulttags.templatetag.args]]
allowed_values = [
"openblock",
"closeblock",
"openvariable",
"closevariable",
"openbrace",
"closebrace",
"opencomment",
"closecomment",
]
name = "tagtype"
required = true
[django.template.defaulttags.url]
args = [
{ name = "view_name", required = true },
{ name = "asvar", required = false, is_kwarg = true },
]
type = "single"
[django.template.defaulttags.verbatim]
closing = "endverbatim"
type = "container"
[django.template.defaulttags.with]
args = [{ name = "extra_context", required = true }]
closing = "endwith"
type = "container"