1+ pub mod tagspecs;
2+
13use std:: fs;
24use std:: path:: Path ;
35
@@ -9,6 +11,13 @@ use directories::ProjectDirs;
911use serde:: Deserialize ;
1012use thiserror:: Error ;
1113
14+ pub use crate :: tagspecs:: ArgTypeDef ;
15+ pub use crate :: tagspecs:: EndTagDef ;
16+ pub use crate :: tagspecs:: IntermediateTagDef ;
17+ pub use crate :: tagspecs:: SimpleArgTypeDef ;
18+ pub use crate :: tagspecs:: TagArgDef ;
19+ pub use crate :: tagspecs:: TagSpecDef ;
20+
1221#[ derive( Error , Debug ) ]
1322pub enum ConfigError {
1423 #[ error( "Configuration build/deserialize error" ) ]
@@ -26,6 +35,8 @@ pub struct Settings {
2635 #[ serde( default ) ]
2736 debug : bool ,
2837 venv_path : Option < String > ,
38+ #[ serde( default ) ]
39+ tagspecs : Vec < TagSpecDef > ,
2940}
3041
3142impl Settings {
@@ -88,6 +99,11 @@ impl Settings {
8899 pub fn venv_path ( & self ) -> Option < & str > {
89100 self . venv_path . as_deref ( )
90101 }
102+
103+ #[ must_use]
104+ pub fn tagspecs ( & self ) -> & [ TagSpecDef ] {
105+ & self . tagspecs
106+ }
91107}
92108
93109#[ cfg( test) ]
@@ -110,7 +126,8 @@ mod tests {
110126 settings,
111127 Settings {
112128 debug: false ,
113- venv_path: None
129+ venv_path: None ,
130+ tagspecs: vec![ ] ,
114131 }
115132 ) ;
116133 }
@@ -346,4 +363,229 @@ mod tests {
346363 assert ! ( matches!( result. unwrap_err( ) , ConfigError :: Config ( _) ) ) ;
347364 }
348365 }
366+
367+ mod tagspecs {
368+ use super :: * ;
369+ use crate :: tagspecs:: ArgTypeDef ;
370+ use crate :: tagspecs:: SimpleArgTypeDef ;
371+
372+ #[ test]
373+ fn test_load_tagspecs_from_djls_toml ( ) {
374+ let dir = tempdir ( ) . unwrap ( ) ;
375+ let content = r#"
376+ [[tagspecs]]
377+ name = "mytag"
378+ module = "myapp.templatetags.custom"
379+ end_tag = { name = "endmytag" }
380+
381+ [[tagspecs]]
382+ name = "for"
383+ module = "django.template.defaulttags"
384+ end_tag = { name = "endfor" }
385+ intermediate_tags = [{ name = "empty" }]
386+ args = [
387+ { name = "item", type = "variable" },
388+ { name = "in", type = "literal" },
389+ { name = "items", type = "variable" }
390+ ]
391+ "# ;
392+ fs:: write ( dir. path ( ) . join ( "djls.toml" ) , content) . unwrap ( ) ;
393+ let settings = Settings :: new ( dir. path ( ) ) . unwrap ( ) ;
394+
395+ assert_eq ! ( settings. tagspecs( ) . len( ) , 2 ) ;
396+
397+ let mytag = & settings. tagspecs ( ) [ 0 ] ;
398+ assert_eq ! ( mytag. name, "mytag" ) ;
399+ assert_eq ! ( mytag. module, "myapp.templatetags.custom" ) ;
400+ assert_eq ! ( mytag. end_tag. as_ref( ) . unwrap( ) . name, "endmytag" ) ;
401+
402+ let for_tag = & settings. tagspecs ( ) [ 1 ] ;
403+ assert_eq ! ( for_tag. name, "for" ) ;
404+ assert_eq ! ( for_tag. module, "django.template.defaulttags" ) ;
405+ assert_eq ! ( for_tag. intermediate_tags. len( ) , 1 ) ;
406+ assert_eq ! ( for_tag. args. len( ) , 3 ) ;
407+ }
408+
409+ #[ test]
410+ fn test_load_tagspecs_from_pyproject ( ) {
411+ let dir = tempdir ( ) . unwrap ( ) ;
412+ let content = r#"
413+ [tool.djls]
414+ debug = true
415+
416+ [[tool.djls.tagspecs]]
417+ name = "cache"
418+ module = "django.templatetags.cache"
419+ end_tag = { name = "endcache", optional = false }
420+ args = [
421+ { name = "expire_time", type = "variable" },
422+ { name = "fragment_name", type = "string" }
423+ ]
424+ "# ;
425+ fs:: write ( dir. path ( ) . join ( "pyproject.toml" ) , content) . unwrap ( ) ;
426+ let settings = Settings :: new ( dir. path ( ) ) . unwrap ( ) ;
427+
428+ assert_eq ! ( settings. tagspecs( ) . len( ) , 1 ) ;
429+ let cache = & settings. tagspecs ( ) [ 0 ] ;
430+ assert_eq ! ( cache. name, "cache" ) ;
431+ assert_eq ! ( cache. module, "django.templatetags.cache" ) ;
432+ assert_eq ! ( cache. args. len( ) , 2 ) ;
433+ }
434+
435+ #[ test]
436+ fn test_arg_types ( ) {
437+ let dir = tempdir ( ) . unwrap ( ) ;
438+ let content = r#"
439+ [[tagspecs]]
440+ name = "test"
441+ module = "test.module"
442+ args = [
443+ { name = "simple", type = "variable" },
444+ { name = "choice", type = { choice = ["on", "off"] } },
445+ { name = "optional", required = false, type = "string" }
446+ ]
447+ "# ;
448+ fs:: write ( dir. path ( ) . join ( "djls.toml" ) , content) . unwrap ( ) ;
449+ let settings = Settings :: new ( dir. path ( ) ) . unwrap ( ) ;
450+
451+ let test = & settings. tagspecs ( ) [ 0 ] ;
452+ assert_eq ! ( test. args. len( ) , 3 ) ;
453+
454+ // Check simple type
455+ assert ! ( matches!(
456+ test. args[ 0 ] . arg_type,
457+ ArgTypeDef :: Simple ( SimpleArgTypeDef :: Variable )
458+ ) ) ;
459+
460+ // Check choice type
461+ if let ArgTypeDef :: Choice { ref choice } = test. args [ 1 ] . arg_type {
462+ assert_eq ! ( choice, & vec![ "on" . to_string( ) , "off" . to_string( ) ] ) ;
463+ } else {
464+ panic ! ( "Expected choice type" ) ;
465+ }
466+
467+ // Check optional arg
468+ assert ! ( !test. args[ 2 ] . required) ;
469+ }
470+
471+ #[ test]
472+ fn test_intermediate_tags ( ) {
473+ let dir = tempdir ( ) . unwrap ( ) ;
474+ let content = r#"
475+ [[tagspecs]]
476+ name = "if"
477+ module = "django.template.defaulttags"
478+ end_tag = { name = "endif" }
479+ intermediate_tags = [
480+ { name = "elif" },
481+ { name = "else" }
482+ ]
483+ args = [
484+ { name = "condition", type = "expression" }
485+ ]
486+ "# ;
487+ fs:: write ( dir. path ( ) . join ( "djls.toml" ) , content) . unwrap ( ) ;
488+ let settings = Settings :: new ( dir. path ( ) ) . unwrap ( ) ;
489+
490+ let if_tag = & settings. tagspecs ( ) [ 0 ] ;
491+ assert_eq ! ( if_tag. name, "if" ) ;
492+
493+ assert_eq ! ( if_tag. intermediate_tags. len( ) , 2 ) ;
494+ assert_eq ! ( if_tag. intermediate_tags[ 0 ] . name, "elif" ) ;
495+ assert_eq ! ( if_tag. intermediate_tags[ 1 ] . name, "else" ) ;
496+ }
497+
498+ #[ test]
499+ fn test_end_tag_with_args ( ) {
500+ let dir = tempdir ( ) . unwrap ( ) ;
501+ let content = r#"
502+ [[tagspecs]]
503+ name = "block"
504+ module = "django.template.defaulttags"
505+ end_tag = { name = "endblock", args = [{ name = "name", required = false, type = "variable" }] }
506+ args = [
507+ { name = "name", type = "variable" }
508+ ]
509+ "# ;
510+ fs:: write ( dir. path ( ) . join ( "djls.toml" ) , content) . unwrap ( ) ;
511+ let settings = Settings :: new ( dir. path ( ) ) . unwrap ( ) ;
512+
513+ let block_tag = & settings. tagspecs ( ) [ 0 ] ;
514+ assert_eq ! ( block_tag. name, "block" ) ;
515+
516+ let end_tag = block_tag. end_tag . as_ref ( ) . unwrap ( ) ;
517+ assert_eq ! ( end_tag. name, "endblock" ) ;
518+ assert_eq ! ( end_tag. args. len( ) , 1 ) ;
519+ assert ! ( !end_tag. args[ 0 ] . required) ;
520+ }
521+
522+ #[ test]
523+ fn test_tagspecs_with_other_settings ( ) {
524+ let dir = tempdir ( ) . unwrap ( ) ;
525+ let content = r#"
526+ debug = true
527+ venv_path = "/path/to/venv"
528+
529+ [[tagspecs]]
530+ name = "custom"
531+ module = "myapp.tags"
532+ args = []
533+ "# ;
534+ fs:: write ( dir. path ( ) . join ( "djls.toml" ) , content) . unwrap ( ) ;
535+ let settings = Settings :: new ( dir. path ( ) ) . unwrap ( ) ;
536+
537+ assert ! ( settings. debug( ) ) ;
538+ assert_eq ! ( settings. venv_path( ) , Some ( "/path/to/venv" ) ) ;
539+ assert_eq ! ( settings. tagspecs( ) . len( ) , 1 ) ;
540+ assert_eq ! ( settings. tagspecs( ) [ 0 ] . name, "custom" ) ;
541+ }
542+
543+ #[ test]
544+ fn test_all_arg_types ( ) {
545+ let dir = tempdir ( ) . unwrap ( ) ;
546+ let content = r#"
547+ [[tagspecs]]
548+ name = "test_all_types"
549+ module = "test.module"
550+ args = [
551+ { name = "literal", type = "literal" },
552+ { name = "variable", type = "variable" },
553+ { name = "string", type = "string" },
554+ { name = "expression", type = "expression" },
555+ { name = "assignment", type = "assignment" },
556+ { name = "varargs", type = "varargs" }
557+ ]
558+ "# ;
559+ fs:: write ( dir. path ( ) . join ( "djls.toml" ) , content) . unwrap ( ) ;
560+ let settings = Settings :: new ( dir. path ( ) ) . unwrap ( ) ;
561+
562+ let test = & settings. tagspecs ( ) [ 0 ] ;
563+ assert_eq ! ( test. args. len( ) , 6 ) ;
564+
565+ assert ! ( matches!(
566+ test. args[ 0 ] . arg_type,
567+ ArgTypeDef :: Simple ( SimpleArgTypeDef :: Literal )
568+ ) ) ;
569+ assert ! ( matches!(
570+ test. args[ 1 ] . arg_type,
571+ ArgTypeDef :: Simple ( SimpleArgTypeDef :: Variable )
572+ ) ) ;
573+ assert ! ( matches!(
574+ test. args[ 2 ] . arg_type,
575+ ArgTypeDef :: Simple ( SimpleArgTypeDef :: String )
576+ ) ) ;
577+ assert ! ( matches!(
578+ test. args[ 3 ] . arg_type,
579+ ArgTypeDef :: Simple ( SimpleArgTypeDef :: Expression )
580+ ) ) ;
581+ assert ! ( matches!(
582+ test. args[ 4 ] . arg_type,
583+ ArgTypeDef :: Simple ( SimpleArgTypeDef :: Assignment )
584+ ) ) ;
585+ assert ! ( matches!(
586+ test. args[ 5 ] . arg_type,
587+ ArgTypeDef :: Simple ( SimpleArgTypeDef :: VarArgs )
588+ ) ) ;
589+ }
590+ }
349591}
0 commit comments