JsonhPy 2.3__tar.gz → 2.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: JsonhPy
3
- Version: 2.3
3
+ Version: 2.5
4
4
  Summary: JSON for Humans in Python.
5
5
  Author-email: Joyless <joyless.mod@gmail.com>
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "JsonhPy"
3
- version = "2.3"
3
+ version = "2.5"
4
4
  authors = [
5
5
  { name="Joyless", email="joyless.mod@gmail.com" },
6
6
  ]
@@ -1,6 +1,7 @@
1
1
  import math
2
2
  from enum import Enum
3
3
  from typing import Iterator, Iterable
4
+ import json
4
5
 
5
6
  class JsonhResult[T, E]:
6
7
  is_error: bool
@@ -546,7 +547,7 @@ class JsonhReader:
546
547
  case _:
547
548
  return JsonhResult.from_error("Token type not implemented")
548
549
 
549
- next_element = parse_next_element()
550
+ next_element: JsonhResult[object, str] = parse_next_element()
550
551
 
551
552
  # Ensure exactly one element
552
553
  if not next_element.is_error:
@@ -557,6 +558,145 @@ class JsonhReader:
557
558
 
558
559
  return next_element
559
560
 
561
+ def parse_json(self, include_comments: bool = False, indent: str | None = None) -> JsonhResult[str, str]:
562
+ """
563
+ Parses a single element as JSON from the reader.
564
+
565
+ If `include_comments` is true, comments are included (`/*` and `*/` are escaped as `/ *` and `* /`).
566
+
567
+ If `indent` is not null, the output is pretty-printed with the given indentation.
568
+
569
+ Note: The result is **NOT** safe to embed in HTML. To safely embed in HTML, you need to escape characters like `<`, `>` and `&`.
570
+ """
571
+
572
+ def parse_next_element_as_json() -> JsonhResult[str, str]:
573
+ current_depth: int = 0
574
+ isStartOfStructure: bool = True
575
+ is_property_value: bool = False
576
+
577
+ result_builder: str = ""
578
+
579
+ for token_result in self.read_element():
580
+ # Check error
581
+ if (token_result.is_error):
582
+ return JsonhResult.from_error(token_result.error())
583
+ token: JsonhToken = token_result.value()
584
+
585
+ # Add comments and indents
586
+ if not is_property_value:
587
+ # Add comma before property/item
588
+ if (token.json_type not in [JsonTokenType.NONE, JsonTokenType.COMMENT]) and (current_depth > 0) and (not isStartOfStructure):
589
+ # Don't add trailing comma
590
+ if token.json_type not in [JsonTokenType.END_OBJECT, JsonTokenType.END_ARRAY]:
591
+ result_builder += ','
592
+
593
+ # Apply indentation
594
+ if indent != None:
595
+ # Don't indent inside empty structures
596
+ if not (token.json_type in [JsonTokenType.END_OBJECT, JsonTokenType.END_ARRAY] and isStartOfStructure):
597
+ # Don't indent comment if not included
598
+ if (not (token.json_type == JsonTokenType.COMMENT and (not include_comments))):
599
+ # Don't indent root elements
600
+ if current_depth > 0:
601
+ # Add newline before element
602
+ result_builder += '\n'
603
+
604
+ # Get current indent count
605
+ indent_count: int = current_depth
606
+ if token.json_type in [JsonTokenType.END_OBJECT, JsonTokenType.END_ARRAY]:
607
+ indent_count -= 1
608
+
609
+ # Add indent
610
+ for _ in range(0, indent_count):
611
+ result_builder += indent
612
+ # Track start of structure to avoid adding leading comma
613
+ if token.json_type not in [JsonTokenType.NONE, JsonTokenType.COMMENT]:
614
+ isStartOfStructure = False
615
+ if token.json_type in [JsonTokenType.START_OBJECT, JsonTokenType.START_ARRAY]:
616
+ isStartOfStructure = True
617
+
618
+ match token.json_type:
619
+ # Null
620
+ case JsonTokenType.NONE:
621
+ result_builder += "null"
622
+ if current_depth == 0:
623
+ return JsonhResult.from_value(result_builder)
624
+ # True
625
+ case JsonTokenType.TRUE:
626
+ result_builder += "true"
627
+ if current_depth == 0:
628
+ return JsonhResult.from_value(result_builder)
629
+ # False
630
+ case JsonTokenType.FALSE:
631
+ result_builder += "false"
632
+ if current_depth == 0:
633
+ return JsonhResult.from_value(result_builder)
634
+ # String
635
+ case JsonTokenType.STRING:
636
+ result_builder += json.dumps(token.value, ensure_ascii=False)
637
+ if current_depth == 0:
638
+ return JsonhResult.from_value(result_builder)
639
+ # Number
640
+ case JsonTokenType.NUMBER:
641
+ result: JsonhResult[float] = JsonhNumberParser.parse(token_result.value().value)
642
+ if (result.is_error):
643
+ return JsonhResult.from_error(result.error())
644
+ result_builder += str(result.value()).removesuffix(".0")
645
+ if current_depth == 0:
646
+ return JsonhResult.from_value(result_builder)
647
+ # Start Object
648
+ case JsonTokenType.START_OBJECT:
649
+ result_builder += '{'
650
+ current_depth += 1
651
+ # Start Array
652
+ case JsonTokenType.START_ARRAY:
653
+ result_builder += '['
654
+ current_depth += 1
655
+ # End Object
656
+ case JsonTokenType.END_OBJECT:
657
+ result_builder += '}'
658
+ current_depth -= 1
659
+ if current_depth == 0:
660
+ return JsonhResult.from_value(result_builder)
661
+ # End Array
662
+ case JsonTokenType.END_ARRAY:
663
+ result_builder += ']'
664
+ current_depth -= 1
665
+ if current_depth == 0:
666
+ return JsonhResult.from_value(result_builder)
667
+ # Property Name
668
+ case JsonTokenType.PROPERTY_NAME:
669
+ result_builder += json.dumps(token.value, ensure_ascii=False)
670
+ result_builder += ':'
671
+ if indent != None:
672
+ result_builder += ' '
673
+ # Comment
674
+ case JsonTokenType.COMMENT:
675
+ if (include_comments):
676
+ result_builder += "/*"
677
+ result_builder += token.value.replace("/*", "/ *").replace("*/", "* /")
678
+ result_builder += "*/"
679
+ # Not implemented
680
+ case _:
681
+ return JsonhResult.from_error("Token type not implemented")
682
+
683
+ if token.json_type != JsonTokenType.COMMENT:
684
+ is_property_value = token.json_type == JsonTokenType.PROPERTY_NAME
685
+
686
+ # End of input
687
+ return JsonhResult.from_error("Expected token, got end of input")
688
+
689
+ next_element_as_json: JsonhResult[str, str] = parse_next_element_as_json()
690
+
691
+ # Ensure exactly one element
692
+ if not next_element_as_json.is_error:
693
+ if self.options.parse_single_element:
694
+ for token in self.read_end_of_elements():
695
+ if token.is_error:
696
+ return JsonhResult.from_error(token.error())
697
+
698
+ return next_element_as_json
699
+
560
700
  def find_property_value(self, property_name: str) -> bool:
561
701
  """
562
702
  Tries to find the given property name in the reader.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: JsonhPy
3
- Version: 2.3
3
+ Version: 2.5
4
4
  Summary: JSON for Humans in Python.
5
5
  Author-email: Joyless <joyless.mod@gmail.com>
6
6
  License-Expression: MIT
File without changes
File without changes
File without changes
File without changes