in2lambda 0.2.0__tar.gz → 0.2.1__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.
Files changed (32) hide show
  1. {in2lambda-0.2.0 → in2lambda-0.2.1}/PKG-INFO +2 -2
  2. {in2lambda-0.2.0 → in2lambda-0.2.1}/in2lambda/filters/PartPartSolSol/filter.py +1 -1
  3. {in2lambda-0.2.0 → in2lambda-0.2.1}/in2lambda/filters/PartSolPartSol/filter.py +1 -1
  4. {in2lambda-0.2.0 → in2lambda-0.2.1}/in2lambda/filters/PartsOneSol/filter.py +1 -1
  5. {in2lambda-0.2.0 → in2lambda-0.2.1}/in2lambda/filters/PartsSepSol/filter.py +1 -1
  6. in2lambda-0.2.0/in2lambda/filters/_markdown.py → in2lambda-0.2.1/in2lambda/filters/markdown.py +65 -20
  7. {in2lambda-0.2.0 → in2lambda-0.2.1}/in2lambda/katex_convert/katex_convert.py +4 -1
  8. {in2lambda-0.2.0 → in2lambda-0.2.1}/in2lambda/main.py +44 -4
  9. {in2lambda-0.2.0 → in2lambda-0.2.1}/pyproject.toml +3 -3
  10. {in2lambda-0.2.0 → in2lambda-0.2.1}/LICENSE +0 -0
  11. {in2lambda-0.2.0 → in2lambda-0.2.1}/README.md +0 -0
  12. {in2lambda-0.2.0 → in2lambda-0.2.1}/in2lambda/__init__.py +0 -0
  13. {in2lambda-0.2.0 → in2lambda-0.2.1}/in2lambda/api/__init__.py +0 -0
  14. {in2lambda-0.2.0 → in2lambda-0.2.1}/in2lambda/api/module.py +0 -0
  15. {in2lambda-0.2.0 → in2lambda-0.2.1}/in2lambda/api/part.py +0 -0
  16. {in2lambda-0.2.0 → in2lambda-0.2.1}/in2lambda/api/question.py +0 -0
  17. {in2lambda-0.2.0 → in2lambda-0.2.1}/in2lambda/filters/PartPartSolSol/__init__.py +0 -0
  18. {in2lambda-0.2.0 → in2lambda-0.2.1}/in2lambda/filters/PartPartSolSol/example.tex +0 -0
  19. {in2lambda-0.2.0 → in2lambda-0.2.1}/in2lambda/filters/PartSolPartSol/__init__.py +0 -0
  20. {in2lambda-0.2.0 → in2lambda-0.2.1}/in2lambda/filters/PartSolPartSol/example.tex +0 -0
  21. {in2lambda-0.2.0 → in2lambda-0.2.1}/in2lambda/filters/PartsOneSol/__init__.py +0 -0
  22. {in2lambda-0.2.0 → in2lambda-0.2.1}/in2lambda/filters/PartsOneSol/example.tex +0 -0
  23. {in2lambda-0.2.0 → in2lambda-0.2.1}/in2lambda/filters/PartsSepSol/__init__.py +0 -0
  24. {in2lambda-0.2.0 → in2lambda-0.2.1}/in2lambda/filters/PartsSepSol/example.tex +0 -0
  25. {in2lambda-0.2.0 → in2lambda-0.2.1}/in2lambda/filters/__init__.py +0 -0
  26. {in2lambda-0.2.0 → in2lambda-0.2.1}/in2lambda/json_convert/__init__.py +0 -0
  27. {in2lambda-0.2.0 → in2lambda-0.2.1}/in2lambda/json_convert/json_convert.py +0 -0
  28. {in2lambda-0.2.0 → in2lambda-0.2.1}/in2lambda/json_convert/minimal_template.json +0 -0
  29. {in2lambda-0.2.0 → in2lambda-0.2.1}/in2lambda/katex_convert/__init__.py +0 -0
  30. {in2lambda-0.2.0 → in2lambda-0.2.1}/in2lambda/katex_convert/delete_list.txt +0 -0
  31. {in2lambda-0.2.0 → in2lambda-0.2.1}/in2lambda/katex_convert/replace_list.txt +0 -0
  32. {in2lambda-0.2.0 → in2lambda-0.2.1}/in2lambda/py.typed +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: in2lambda
3
- Version: 0.2.0
4
- Summary: Automagically uploads questions to Lambda Feedback so you don't have to.
3
+ Version: 0.2.1
4
+ Summary: Converts content ready for import into Lambda Feedback
5
5
  Home-page: https://lambda-feedback.github.io/in2lambda
6
6
  License: MIT
7
7
  Requires-Python: >=3.10,<4.0
@@ -7,7 +7,7 @@ from typing import Optional
7
7
  import panflute as pf
8
8
 
9
9
  from in2lambda.api.module import Module
10
- from in2lambda.filters._markdown import filter
10
+ from in2lambda.filters.markdown import filter
11
11
 
12
12
 
13
13
  @filter
@@ -8,7 +8,7 @@ from typing import Optional
8
8
  import panflute as pf
9
9
 
10
10
  from in2lambda.api.module import Module
11
- from in2lambda.filters._markdown import filter
11
+ from in2lambda.filters.markdown import filter
12
12
 
13
13
 
14
14
  @filter
@@ -10,7 +10,7 @@ from typing import Optional
10
10
  import panflute as pf
11
11
 
12
12
  from in2lambda.api.module import Module
13
- from in2lambda.filters._markdown import filter
13
+ from in2lambda.filters.markdown import filter
14
14
 
15
15
 
16
16
  @filter
@@ -10,7 +10,7 @@ from typing import Optional
10
10
  import panflute as pf
11
11
 
12
12
  from in2lambda.api.module import Module
13
- from in2lambda.filters._markdown import filter
13
+ from in2lambda.filters.markdown import filter
14
14
 
15
15
 
16
16
  @filter
@@ -1,5 +1,7 @@
1
1
  """Generic helper functions used during the Pandoc filter stage for markdown conversion."""
2
2
 
3
+ import os
4
+ import re
3
5
  from functools import cache
4
6
  from pathlib import Path
5
7
 
@@ -13,7 +15,7 @@ from in2lambda.katex_convert.katex_convert import latex_to_katex
13
15
 
14
16
  @cache
15
17
  def image_directories(tex_file: str) -> list[str]:
16
- """Determines the image directories referenced by `graphicspath` in a given TeX document.
18
+ r"""Determines the image directories referenced by `graphicspath` in a given TeX document.
17
19
 
18
20
  Args:
19
21
  tex_file: The absolute path to a TeX file
@@ -21,22 +23,45 @@ def image_directories(tex_file: str) -> list[str]:
21
23
  Returns:
22
24
  The exact contents of `graphicspath`, regardless of whether the directories are
23
25
  absolute or relative.
26
+
27
+ Examples:
28
+ >>> from in2lambda.filters.markdown import image_directories
29
+ >>> import tempfile
30
+ >>> import os
31
+ >>> # Example TeX file with a graphicspath
32
+ >>> temp_dir = tempfile.mkdtemp()
33
+ >>> tex_file = os.path.join(temp_dir, 'test.tex')
34
+ >>> with open(tex_file, 'w') as f:
35
+ ... f.write("\graphicspath{{subdir1/}{subdir2/}{subdir3/}}")
36
+ 45
37
+ >>> image_directories(tex_file)
38
+ ['subdir1/', 'subdir2/', 'subdir3/']
39
+ >>> with open(tex_file, 'w') as f:
40
+ ... f.write("\graphicspath{ { subdir1/ }, { subdir2/ }, { subdir3/ } }")
41
+ 57
42
+ >>> image_directories.cache_clear()
43
+ >>> image_directories(tex_file)
44
+ ['subdir1/', 'subdir2/', 'subdir3/']
45
+ >>> with open(tex_file, 'w') as f:
46
+ ... f.write("No image directory")
47
+ 18
48
+ >>> image_directories.cache_clear()
49
+ >>> image_directories(tex_file)
50
+ []
24
51
  """
25
52
  with open(tex_file, "r") as file:
26
53
  for line in file:
27
- # Assumes line is in the format \graphicspath{ {...}, {...}, ...}
28
54
  if "graphicspath" in line:
29
- return [
30
- i.strip("{").rstrip("}")
31
- for i in line.replace(" ", "")[len("\graphicspath{") : -1].split(
32
- ","
33
- )
34
- ]
55
+ # Matches anything surrounded by curly braces, but excludes the top level
56
+ # graphicspath brace.
57
+ return [match.strip() for match in re.findall(r"{([^{]*?)}", line)]
35
58
  return []
36
59
 
37
60
 
61
+ # TODO: This assumes the file extension is included, but that isn't required by LaTeX
62
+ # See: https://www.overleaf.com/learn/latex/Inserting_Images#Generating_high-res_and_low-res_images
38
63
  def image_path(image_name: str, tex_file: str) -> Optional[str]:
39
- """Determines the absolute path to an image referenced in a tex_file.
64
+ r"""Determines the absolute path to an image referenced in a tex_file.
40
65
 
41
66
  Args:
42
67
  image_name: The file name of the image e.g. example.png
@@ -44,29 +69,49 @@ def image_path(image_name: str, tex_file: str) -> Optional[str]:
44
69
 
45
70
  Returns:
46
71
  The absolute path to the image if it can be found. If not, it returns None.
72
+
73
+ Examples:
74
+ >>> from in2lambda.filters.markdown import image_path
75
+ >>> import tempfile
76
+ >>> import os
77
+ >>> # Example TeX file with a subdirectory
78
+ >>> temp_dir = tempfile.mkdtemp()
79
+ >>> tex_file = os.path.join(temp_dir, 'test.tex')
80
+ >>> with open(tex_file, 'w') as f:
81
+ ... f.write("\graphicspath{{./subdir1/}{./subdir2/}{./subdir3/}}")
82
+ 51
83
+ >>> # Example image in a relative subdirectory
84
+ >>> sub_dir = os.path.join(temp_dir, 'subdir3')
85
+ >>> os.makedirs(sub_dir)
86
+ >>> with open(os.path.join(sub_dir, 'inside_folder.png'), 'w') as f:
87
+ ... pass
88
+ >>> image_path("inside_folder.png", tex_file) == os.path.join(temp_dir, 'subdir3', "inside_folder.png")
89
+ True
90
+ >>> # Absolute path provided
91
+ >>> image_path(os.path.join(temp_dir, 'subdir3', "inside_folder.png"), tex_file) == os.path.join(temp_dir, 'subdir3', "inside_folder.png")
92
+ True
47
93
  """
48
94
  # In case the filename is the exact absolute/relative location to the image
49
95
  # When handling relative locations (i.e. begins with dot), first go to the directory of the TeX file.
50
- filename = (
51
- f"{str(Path(tex_file).parent)}/" if image_name[0] == "." else ""
52
- ) + image_name
96
+
97
+ filename = os.path.join(
98
+ str(Path(tex_file).parent) if image_name[0] == "." else "", image_name
99
+ )
53
100
 
54
101
  if Path(filename).is_file():
55
- return filename
102
+ return os.path.normpath(filename)
56
103
 
57
104
  # Absolute or relative directories referenced by `graphicspath`
58
105
  image_locations = image_directories(tex_file)
59
106
 
60
107
  for directory in image_locations:
61
- if directory[-1] != "/":
62
- directory += "/"
63
- filename = (
64
- (f"{str(Path(tex_file).parent)}/" if directory[0] == "." else "")
65
- + directory
66
- + image_name
108
+ filename = os.path.join(
109
+ str(Path(tex_file).parent) if directory[0] == "." else "",
110
+ directory,
111
+ image_name,
67
112
  )
68
113
  if Path(filename).is_file():
69
- return filename
114
+ return os.path.normpath(filename)
70
115
  return None
71
116
 
72
117
 
@@ -34,6 +34,9 @@ def latex_to_katex(latex_string: str) -> str:
34
34
 
35
35
  Returns:
36
36
  Hopefully, a valid KaTeX expression that's similar to the LaTeX expression.
37
+
38
+ Examples:
39
+ >>>
37
40
  """
38
41
  katex_string = replace_functions(delete_functions(latex_string))
39
42
  return katex_string
@@ -143,7 +146,7 @@ def brace_remover(latex_string: str, brace_start_index: int) -> str:
143
146
 
144
147
 
145
148
  if __name__ == "__main__":
146
- latex_input = """ \\textbf{Vector Algebra:} for two vectors $\\vec{a}$ and $\\vec{b}$ in $\\mathbb{R}^3$ given by \\norm{a}"""
149
+ latex_input = "\\textbf{Vector Algebra:} for two vectors $\\vec{a}$ and $\\vec{b}$ in $\\mathbb{R}^3$ given by \\norm{a}"
147
150
 
148
151
  katex_output = latex_to_katex(latex_input)
149
152
  print(katex_output)
@@ -12,6 +12,41 @@ from in2lambda.api.module import Module
12
12
  from in2lambda.json_convert import json_convert
13
13
 
14
14
 
15
+ def file_type(file: str) -> str:
16
+ """Determines which pandoc file format to use for a given file.
17
+
18
+ See https://github.com/jgm/pandoc/blob/bad922a69236e22b20d51c4ec0b90c5a6c038433/src/Text/Pandoc/Format.hs#L171
19
+ (or any newer commit) for pandoc's supported file extensions.
20
+
21
+ Args:
22
+ file: A file path with the file extension included.
23
+
24
+ Returns:
25
+ An option in `pandoc --list-input-formats` that matches the given file type
26
+
27
+ Examples:
28
+ >>> from in2lambda.main import file_type
29
+ >>> file_type("example.tex")
30
+ 'latex'
31
+ >>> file_type("/some/random/path/demo.md")
32
+ 'markdown'
33
+ >>> file_type("no_extension")
34
+ Traceback (most recent call last):
35
+ RuntimeError: Unsupported file extension: .no_extension
36
+ >>> file_type("demo.unknown_extension")
37
+ Traceback (most recent call last):
38
+ RuntimeError: Unsupported file extension: .unknown_extension
39
+ """
40
+ match (extension := file.split(".")[-1].lower()):
41
+ case "tex" | "latex" | "ltx":
42
+ return "latex"
43
+ case "md" | "rmd" | "markdown" | "mdown" | "mdwn" | "mkd" | "mkdn" | "text" | "txt":
44
+ return "markdown"
45
+ case "docx":
46
+ return "docx" # Pandoc doesn't seem to support doc
47
+ raise RuntimeError(f"Unsupported file extension: .{extension}")
48
+
49
+
15
50
  def runner(
16
51
  question_file: str,
17
52
  chosen_filter: str,
@@ -52,7 +87,9 @@ def runner(
52
87
  # Parse the Pandoc AST using the relevant panflute filter.
53
88
  pf.run_filter(
54
89
  filter_module.pandoc_filter,
55
- doc=pf.convert_text(text, input_format="latex", standalone=True),
90
+ doc=pf.convert_text(
91
+ text, input_format=file_type(question_file), standalone=True
92
+ ),
56
93
  module=module,
57
94
  tex_file=question_file,
58
95
  parsing_answers=False,
@@ -65,7 +102,9 @@ def runner(
65
102
 
66
103
  pf.run_filter(
67
104
  filter_module.pandoc_filter,
68
- doc=pf.convert_text(answer_text, input_format="latex", standalone=True),
105
+ doc=pf.convert_text(
106
+ answer_text, input_format=file_type(answer_file), standalone=True
107
+ ),
69
108
  module=module,
70
109
  tex_file=answer_file,
71
110
  parsing_answers=True,
@@ -79,7 +118,8 @@ def runner(
79
118
 
80
119
 
81
120
  @click.command(
82
- epilog="See the docs at https://lambda-feedback.github.io/user-documentation/ for more details."
121
+ no_args_is_help=True,
122
+ epilog="See the docs at https://lambda-feedback.github.io/in2lambda/ for more details.",
83
123
  )
84
124
  @click.argument( # Use resolve_path to get absolute path
85
125
  "question_file", type=click.Path(exists=True, readable=True, resolve_path=True)
@@ -91,7 +131,7 @@ def runner(
91
131
  [
92
132
  i.name
93
133
  for i in pkgutil.iter_modules(in2lambda.filters.__path__)
94
- if i.name[0] != "_"
134
+ if i.name != "markdown"
95
135
  ],
96
136
  case_sensitive=False,
97
137
  ),
@@ -1,7 +1,7 @@
1
1
  [tool.poetry]
2
2
  name = "in2lambda"
3
- version = "0.2.0"
4
- description = "Automagically uploads questions to Lambda Feedback so you don't have to."
3
+ version = "0.2.1"
4
+ description = "Converts content ready for import into Lambda Feedback"
5
5
  authors = []
6
6
  license = "MIT"
7
7
  readme = "README.md"
@@ -43,7 +43,7 @@ pre-commit = "^3.3.3"
43
43
  [tool.poetry.group.docs.dependencies]
44
44
  furo = "^2023.8.19"
45
45
  myst-parser = "^2.0.0"
46
- sphinx = "^7.2.4"
46
+ sphinx = "^7.2.5"
47
47
  sphinx-copybutton = "^0.5.2"
48
48
  sphinx-design = "^0.5.0"
49
49
  sphinx-click = "^5.0.1"
File without changes
File without changes
File without changes