Coverage for src/models/image.py: 76%

55 statements  

« prev     ^ index     » next       coverage.py v7.3.0, created at 2024-04-27 07:17 -0500

1import datetime 

2import pathlib 

3import re 

4 

5 

6r_filename = re.compile( 

7 r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})(-(?P<slug>.*))?') 

8 

9 

10class Image: 

11 """ 

12 A website image. 

13 """ 

14 

15 def __init__(self, path: str | pathlib.Path, entry=None): 

16 self._path = pathlib.Path(path) 

17 self._entry = entry 

18 

19 @property 

20 def path(self) -> pathlib.Path: 

21 """ 

22 Image as a `pathlib.Path` object. 

23 """ 

24 return self._path 

25 

26 @property 

27 def filename(self): 

28 """ 

29 Name of the file, ex `test.jpg` 

30 """ 

31 return self.path.name 

32 

33 @property 

34 def date(self) -> datetime.datetime: 

35 """ 

36 Date, according to the image file's YYY-MM-DD date slug. 

37 """ 

38 

39 if match := r_filename.search(self.path.stem): 

40 return datetime.datetime( 

41 year=int(match.group('year')), 

42 month=int(match.group('month')), 

43 day=int(match.group('day')), 

44 ) 

45 raise ValueError(f'could not parse date from {self.filename}') 

46 

47 @property 

48 def date_slug(self): 

49 """ 

50 Parses the YYYY-MM-DD date slug from the file name. 

51 """ 

52 return self.date.strftime('%Y-%m-%d') 

53 

54 @property 

55 def slug(self): 

56 """ 

57 The portion of the filename without the extension or the date slug. 

58 

59 If the full filename is `2023-01-01-fish-soup.png`, the slug 

60 would be `fish-soup`. 

61 """ 

62 if match := r_filename.search(self.path.stem): 

63 return match.group('slug') 

64 

65 # otherwise just return the stem 

66 return self.path.stem 

67 

68 @property 

69 def title(self): 

70 """ 

71 Human readable name for the image, based on the date slug. 

72 

73 For example, `test-image.jpg`, becomes `Test Image` 

74 """ 

75 return self.slug.replace('-', ' ').title() 

76 

77 @property 

78 def href(self): 

79 """ 

80 The `href` html value that points to the image. 

81 

82 Can be used in templates like so: 

83 

84 ```html 

85 <a href="{{ img.href }}">...</a> 

86 ``` 

87 """ 

88 www_dir = pathlib.Path('./www') 

89 relpath = self.path.relative_to(www_dir) 

90 return f'./{relpath}' 

91 

92 @property 

93 def is_banner(self): 

94 """ 

95 True if the image lives in the banners directory. 

96 """ 

97 

98 banner_dir = pathlib.Path('./www/images/banners/') 

99 return banner_dir in self.path.parents 

100 

101 @property 

102 def entry(self): 

103 """ 

104 The entry where the image is referenced. 

105 """ 

106 return self._entry 

107 

108 

109def load_images(entries=[], images_dir='./www/images/') -> list[Image]: 

110 """ 

111 Loads complete set of images for website as a list of `Image` objects. 

112 

113 Requires a list of entries so it can associate the entry where it 

114 is referenced. 

115 

116 ```python 

117 images = src.load_images() 

118 ``` 

119 """ 

120 

121 images = [] 

122 

123 def is_image(p): 

124 return p.suffix.lower() in ( 

125 '.jpg', 

126 '.jpeg', 

127 '.png', 

128 ) 

129 

130 images_dir = pathlib.Path(images_dir) 

131 image_files = filter(is_image, images_dir.glob('**/*.*')) 

132 

133 # build a k/v map of image paths to entries 

134 ref_map = {} 

135 for entry in entries: 

136 for path in entry.extract_links(): 

137 ref_map[str(path)] = entry 

138 

139 # build the list of images 

140 for path in image_files: 

141 images.append(Image(path, ref_map.get(str(path)))) 

142 

143 # finally, sort them by name 

144 return sorted(images, key=lambda i: i.path.name, reverse=True)