twitter_text_config/
lib.rs1#[macro_use]
6extern crate serde_derive;
7extern crate serde_json;
8extern crate serde;
9
10#[macro_use]
11extern crate lazy_static;
12
13use std::cmp::Ordering;
14use std::path::PathBuf;
15use std::fmt;
16use std::fs::File;
17use std::io::Read;
18use serde::ser::{Serialize, Serializer, SerializeStruct};
19use serde::de::{self, Deserialize, Deserializer, Visitor, MapAccess};
20
21const DEFAULT_VERSION: i32 = 2;
22const DEFAULT_WEIGHTED_LENGTH: i32 = 280;
23const DEFAULT_SCALE: i32 = 100;
24const DEFAULT_WEIGHT: i32 = 200;
25const DEFAULT_TRANSFORMED_URL_LENGTH: i32 = 23;
26const V1_JSON: &str = include_str!("v1.json");
27const V2_JSON: &str = include_str!("v2.json");
28const V3_JSON: &str = include_str!("v3.json");
29
30lazy_static! {
31 static ref CONFIG_V1: Configuration = Configuration::configuration_from_str(V1_JSON);
32 static ref CONFIG_V2: Configuration = Configuration::configuration_from_str(V2_JSON);
33 static ref CONFIG_V3: Configuration = Configuration::configuration_from_str(V3_JSON);
34}
35
36pub fn config_v1() -> &'static Configuration {
37 &CONFIG_V1
38}
39
40pub fn config_v2() -> &'static Configuration {
41 &CONFIG_V2
42}
43
44pub fn config_v3() -> &'static Configuration {
45 &CONFIG_V3
46}
47
48pub fn default() -> &'static Configuration {
49 &CONFIG_V3
50}
51
52#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
53#[serde(rename_all = "camelCase")]
54pub struct Configuration {
55 pub version: i32,
56 pub max_weighted_tweet_length: i32,
57 pub scale: i32,
58 pub default_weight: i32,
59 #[serde(rename = "transformedURLLength")]
60 pub transformed_url_length: i32,
61 pub ranges: Vec<WeightedRange>,
62 #[serde(default)]
63 pub emoji_parsing_enabled: bool,
64}
65
66impl Configuration {
67 pub fn default() -> Configuration {
68 Configuration {
69 version: DEFAULT_VERSION,
70 max_weighted_tweet_length: DEFAULT_WEIGHTED_LENGTH,
71 scale: DEFAULT_SCALE,
72 default_weight: DEFAULT_WEIGHT,
73 ranges: Configuration::default_ranges(),
74 transformed_url_length: DEFAULT_TRANSFORMED_URL_LENGTH,
75 emoji_parsing_enabled: true
76 }
77 }
78
79 fn default_ranges() -> Vec<WeightedRange> {
80 vec!(
81 WeightedRange::new(0, 4351, 100),
82 WeightedRange::new(8192, 8205, 100),
83 WeightedRange::new(8208, 8223, 100),
84 WeightedRange::new(8242, 8247, 100),
85 )
86 }
87
88 pub fn configuration_from_json(config_file: &str) -> Configuration {
89 let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
90 path.push("config");
91 path.push(config_file);
92 Configuration::configuration_from_file(&path)
93 }
94
95 pub fn configuration_from_file(path: &PathBuf) -> Configuration {
96 let mut f = File::open(path).expect("Config file not found");
97 let mut contents = String::new();
98 f.read_to_string(&mut contents).expect("Error reading config file");
99 Configuration::configuration_from_str(&contents)
100 }
101
102 pub fn configuration_from_str(json: &str) -> Configuration {
103 serde_json::from_str(&json).expect("Error parsing json")
104 }
105}
106
107#[derive(Debug, PartialEq, Hash, Clone, Copy)]
108pub struct WeightedRange {
109 pub range: Range,
110 pub weight: i32
111}
112
113impl WeightedRange {
114 pub fn new(start: i32, end: i32, weight: i32) -> WeightedRange {
115 WeightedRange {
116 range: Range::new(start, end),
117 weight
118 }
119 }
120
121 pub fn contains(&self, i: i32) -> bool {
122 self.range.contains(&i)
123 }
124}
125
126impl Serialize for WeightedRange {
127 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
128 where
129 S: Serializer,
130 {
131 let mut state = serializer.serialize_struct("WeightedRange", 3)?;
132 state.serialize_field("start", &self.range.start())?;
133 state.serialize_field("end", &self.range.end())?;
134 state.serialize_field("weight", &self.weight)?;
135 state.end()
136 }
137}
138
139impl<'de> Deserialize<'de> for WeightedRange {
140 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
141 where
142 D: Deserializer<'de>,
143 {
144 #[derive(Deserialize)]
145 #[serde(field_identifier, rename_all = "lowercase")]
146 enum Field { Start, End, Weight }
147
148 struct WeightedRangeVisitor;
149 impl<'de> Visitor<'de> for WeightedRangeVisitor {
150 type Value = WeightedRange;
151
152 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
153 formatter.write_str("struct WeightedRange")
154 }
155
156 fn visit_map<V>(self, mut map: V) -> Result<WeightedRange, V::Error>
157 where
158 V: MapAccess<'de>,
159 {
160 let mut start = None;
161 let mut end = None;
162 let mut weight = None;
163 while let Some(key) = map.next_key()? {
164 match key {
165 Field::Start => {
166 if start.is_some() {
167 return Err(de::Error::duplicate_field("start"));
168 }
169 start = Some(map.next_value()?);
170 }
171 Field::End => {
172 if end.is_some() {
173 return Err(de::Error::duplicate_field("end"));
174 }
175 end = Some(map.next_value()?);
176 }
177 Field::Weight => {
178 if weight.is_some() {
179 return Err(de::Error::duplicate_field("weight"));
180 }
181 weight = Some(map.next_value()?);
182 }
183 }
184 }
185 let start = start.ok_or_else(|| de::Error::missing_field("start"))?;
186 let end = end.ok_or_else(|| de::Error::missing_field("end"))?;
187 let weight = weight.ok_or_else(|| de::Error::missing_field("weight"))?;
188 Ok(WeightedRange::new(start, end, weight))
189 }
190 }
191
192 const FIELDS: &'static [&'static str] = &["start", "end", "weight"];
193 deserializer.deserialize_struct("WeightedRange", FIELDS, WeightedRangeVisitor)
194 }
195}
196
197#[derive(Debug, PartialEq, Hash, Eq, Clone, Copy)]
198pub struct Range {
199 start: i32,
200 end: i32,
201}
202
203impl Range {
204 pub fn empty() -> Range {
205 Range { start: 0, end: 0 }
206 }
207
208 pub fn new( start: i32, end: i32 ) -> Range {
209 Range { start, end }
210 }
211
212 pub fn contains(&self, val: &i32) -> bool {
213 val >= &self.start && val <= &self.end
214 }
215
216 pub fn start(&self) -> i32 {
217 self.start
218 }
219
220 pub fn end(&self) -> i32 {
221 self.end
222 }
223}
224
225impl Ord for Range {
226 fn cmp(&self, other: &Self) -> Ordering {
227 if self.start < other.start {
228 Ordering::Less
229 } else if self.start == other.start {
230 if self.end < other.end {
231 Ordering::Less
232 } else if self.end < other.end {
233 Ordering::Equal
234 } else {
235 Ordering::Greater
236 }
237 } else {
238 Ordering::Greater
239 }
240 }
241}
242
243impl PartialOrd for Range {
244 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
245 Some(self.cmp(other))
246 }
247}