Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,36 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

-------------------------------------------------------------------------------

This project includes the Carnegie Mellon University Pronouncing Dictionary.
The original license for this dictionary is included below:

Copyright (c) 2015, Carnegie Mellon University
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

* Neither the name of dictTools nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1 change: 0 additions & 1 deletion Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ verify_ssl = true
name = "pypi"

[packages]
cmudict = "*"
"repoze.lru" = "*"
setuptools = "*"
appdirs = "*"
Expand Down
380 changes: 131 additions & 249 deletions README.md

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
cmudict
setuptools
appdirs
111 changes: 103 additions & 8 deletions scireadability/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,105 @@
from .scireadability import scireadability
__version__ = "2.0.0"

from .scireadability import (
# Configuration
set_rounding,
set_rm_apostrophe,
# Dictionary management
add_word_to_dictionary,
add_words_from_file_to_dictionary,
overwrite_dictionary,
revert_dictionary_to_default,
print_dictionary,
# Core stats
char_count,
letter_count,
lexicon_count,
syllable_count,
sentence_count,
polysyllabcount,
monosyllabcount,
long_word_count,
miniword_count,
# Averaged stats
avg_sentence_length,
avg_syllables_per_word,
avg_character_per_word,
avg_letter_per_word,
avg_sentence_per_word,
# Readability formulas
flesch_reading_ease,
flesch_kincaid_grade,
smog_index,
coleman_liau_index,
automated_readability_index,
dale_chall_readability_score,
linsear_write_formula,
gunning_fog,
forcast,
spache_readability,
mcalpine_eflaw,
lix,
rix,
# Difficult words
difficult_words,
difficult_words_list,
is_difficult_word,
is_easy_word,
# Other utilities
text_standard,
reading_time,
remove_punctuation,
_cache_clear,
)

__version__ = (1, 0, 0)


for attribute in dir(scireadability):
if callable(getattr(scireadability, attribute)):
if not attribute.startswith("_"):
globals()[attribute] = getattr(scireadability, attribute)
__all__ = [
# Configuration
"set_rounding",
"set_rm_apostrophe",
# Dictionary management
"add_word_to_dictionary",
"add_words_from_file_to_dictionary",
"overwrite_dictionary",
"revert_dictionary_to_default",
"print_dictionary",
# Core stats
"char_count",
"letter_count",
"lexicon_count",
"syllable_count",
"sentence_count",
"polysyllabcount",
"monosyllabcount",
"long_word_count",
"miniword_count",
# Averaged stats
"avg_sentence_length",
"avg_syllables_per_word",
"avg_character_per_word",
"avg_letter_per_word",
"avg_sentence_per_word",
# Readability formulas
"flesch_reading_ease",
"flesch_kincaid_grade",
"smog_index",
"coleman_liau_index",
"automated_readability_index",
"dale_chall_readability_score",
"linsear_write_formula",
"gunning_fog",
"forcast",
"spache_readability",
"mcalpine_eflaw",
"lix",
"rix",
# Difficult words
"difficult_words",
"difficult_words_list",
"is_difficult_word",
"is_easy_word",
# Other utilities
"text_standard",
"reading_time",
"remove_punctuation",
"_cache_clear",
]
95 changes: 58 additions & 37 deletions scireadability/dictionary_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@

def _get_default_dict_path():
"""Returns the path to the default custom dictionary in the package."""
return 'resources/en/custom_dict.json'
return "resources/en/custom_dict.json"


def _get_user_dict_path():
"""Returns the path to the user's custom dictionary in the config directory."""
config_dir = user_config_dir(PACKAGE_NAME)
dict_dir = os.path.join(config_dir, "en")
os.makedirs(dict_dir, exist_ok=True)
return os.path.join(dict_dir, 'custom_dict.json')
return os.path.join(dict_dir, "custom_dict.json")


def load_custom_syllable_dict():
Expand All @@ -30,60 +30,70 @@ def load_custom_syllable_dict():
loaded_dict = {}

try:
with open(user_dict_path, 'r', encoding='utf-8') as f:
with open(user_dict_path, "r", encoding="utf-8") as f:
user_data = json.load(f)
if "CUSTOM_SYLLABLE_DICT" in user_data:
# Convert keys to lowercase when loading from user dict
loaded_dict.update(
{k.lower(): v for k, v in user_data["CUSTOM_SYLLABLE_DICT"].items()})
{k.lower(): v for k, v in user_data["CUSTOM_SYLLABLE_DICT"].items()}
)
print(f"Loaded custom dictionary from user config: {user_dict_path}")
return loaded_dict # User dict takes precedence
except FileNotFoundError:
pass # User dict is optional

try:
default_dict_string = pkg_resources.resource_string(__name__, default_dict_path).decode(
'utf-8')
default_dict_string = pkg_resources.resource_string(
__name__, default_dict_path
).decode("utf-8")
default_data = json.loads(default_dict_string)
if "CUSTOM_SYLLABLE_DICT" in default_data:
# Convert keys to lowercase when loading from default dict
loaded_dict.update(
{k.lower(): v for k, v in default_data["CUSTOM_SYLLABLE_DICT"].items()})
print(f"Loaded default dictionary from package: {default_dict_path}")
{k.lower(): v for k, v in default_data["CUSTOM_SYLLABLE_DICT"].items()}
)
except FileNotFoundError:
print(
f"Error: Default custom syllable dictionary file not found in package at "
f"{default_dict_path}.")
f"{default_dict_path}."
)
except json.JSONDecodeError as e:
print(
f"Error: Invalid JSON format in default dictionary file at {default_dict_path}. "
f"Error: {e}")
f"Error: {e}"
)

return loaded_dict


def overwrite_custom_dict(file_path):
"""Overwrites the user's custom dictionary with the contents of a given JSON file."""
try:
with open(file_path, 'r', encoding='utf-8') as f:
with open(file_path, "r", encoding="utf-8") as f:
new_dict_data = json.load(f)
if not isinstance(new_dict_data, dict) or "CUSTOM_SYLLABLE_DICT" not in new_dict_data:
if (
not isinstance(new_dict_data, dict)
or "CUSTOM_SYLLABLE_DICT" not in new_dict_data
):
raise ValueError(
"Invalid dictionary format in provided file. "
"Should be a JSON with 'CUSTOM_SYLLABLE_DICT' key.")
"Should be a JSON with 'CUSTOM_SYLLABLE_DICT' key."
)
user_dict_path = _get_user_dict_path()
with open(user_dict_path, 'w', encoding='utf-8') as outfile:
json.dump(new_dict_data, outfile, indent=4) # Pretty print for readability
with open(user_dict_path, "w", encoding="utf-8") as outfile:
json.dump(new_dict_data, outfile, indent=4)
print(
f"Custom dictionary overwritten with file: {file_path}. Saved to {user_dict_path}")
f"Custom dictionary overwritten with file: {file_path}. Saved to {user_dict_path}"
)
except FileNotFoundError:
raise FileNotFoundError(f"File not found: {file_path}")
except json.JSONDecodeError:
raise json.JSONDecodeError(f"Invalid JSON in file: {file_path}", "", 0)
except ValueError as ve:
raise ve
except Exception as e:
raise Exception(f"An unexpected error occurred during dictionary overwrite: {e}")
raise Exception(
f"An unexpected error occurred during dictionary overwrite: {e}"
)


def add_term_to_custom_dict(word, syllable_count):
Expand All @@ -92,31 +102,35 @@ def add_term_to_custom_dict(word, syllable_count):
raise ValueError("Syllable count must be a positive integer.")

user_dict_path = _get_user_dict_path()
current_dict = load_custom_syllable_dict() # Load existing dict (user or default)
current_dict = load_custom_syllable_dict()

current_dict[word] = syllable_count

dict_data_to_save = {"CUSTOM_SYLLABLE_DICT": current_dict} # Re-wrap for JSON structure
dict_data_to_save = {
"CUSTOM_SYLLABLE_DICT": current_dict
} # Re-wrap for JSON structure
try:
with open(user_dict_path, 'w', encoding='utf-8') as outfile:
with open(user_dict_path, "w", encoding="utf-8") as outfile:
json.dump(dict_data_to_save, outfile, indent=4)
print(
f"Added term '{word}': {syllable_count} syllables to custom dictionary. "
f"Saved to {user_dict_path}")
f"Saved to {user_dict_path}"
)
except Exception as e:
raise Exception(f"Error saving updated custom dictionary: {e}")


def add_terms_from_file(file_path):
"""Adds multiple terms from a JSON file to the user's custom dictionary."""
try:
with open(file_path, 'r', encoding='utf-8') as f:
full_json_data = json.load(f) # Load the full JSON
with open(file_path, "r", encoding="utf-8") as f:
full_json_data = json.load(f)

if "CUSTOM_SYLLABLE_DICT" not in full_json_data:
raise ValueError(
"Invalid dictionary format in provided file. "
"Should be a JSON with 'CUSTOM_SYLLABLE_DICT' key containing a dictionary.")
"Should be a JSON with 'CUSTOM_SYLLABLE_DICT' key containing a dictionary."
)

new_terms_data = full_json_data["CUSTOM_SYLLABLE_DICT"]

Expand All @@ -132,26 +146,28 @@ def add_terms_from_file(file_path):
dict_data_to_save = {"CUSTOM_SYLLABLE_DICT": current_dict}
user_dict_path = _get_user_dict_path()

with open(user_dict_path, 'w', encoding='utf-8') as outfile:
with open(user_dict_path, "w", encoding="utf-8") as outfile:
json.dump(dict_data_to_save, outfile, indent=4)
print(
f"Added terms from file: {file_path}. Updated dictionary saved to {user_dict_path}")
f"Added terms from file: {file_path}. Updated dictionary saved to {user_dict_path}"
)

except FileNotFoundError:
raise FileNotFoundError(f"File not found: {file_path}")
except json.JSONDecodeError:
raise json.JSONDecodeError(f"Invalid JSON in file: {file_path}", "", 0)
except ValueError as ve:
raise ve # Re-raise ValueError, which now will be raised correctly for bad format
raise ve
except Exception as e:
raise Exception(f"Error adding terms from file: {e}")


def print_custom_dict():
"""Prints the currently loaded custom dictionary to the console."""
current_dict = load_custom_syllable_dict()
print(json.dumps({"CUSTOM_SYLLABLE_DICT": current_dict},
indent=4)) # Print in readable JSON format
print(
json.dumps({"CUSTOM_SYLLABLE_DICT": current_dict}, indent=4)
) # Print in readable JSON format


def revert_custom_dict_to_default():
Expand All @@ -165,23 +181,28 @@ def revert_custom_dict_to_default():
try:
# Load the default dictionary content from the package resource
resource_path = _get_default_dict_path()
json_data = pkg_resources.resource_string(__name__, resource_path).decode('utf-8')
json_data = pkg_resources.resource_string(__name__, resource_path).decode(
"utf-8"
)
default_dict_data = json.loads(json_data)

# Write the default dictionary content to the user's custom dictionary path,
# effectively overwriting the user's customizations.
with open(user_dict_path, 'w', encoding='utf-8') as outfile:
with open(user_dict_path, "w", encoding="utf-8") as outfile:
json.dump(default_dict_data, outfile, indent=4)

print(
f"Custom dictionary reverted to the default package dictionary. "
f"User customizations have been removed from: {user_dict_path}")
f"User customizations have been removed from: {user_dict_path}"
)

except FileNotFoundError:
raise FileNotFoundError(f"Default dictionary file not found in package at: "
f"{default_dict_path}")
raise FileNotFoundError(
f"Default dictionary file not found in package at: {default_dict_path}"
)
except json.JSONDecodeError:
raise json.JSONDecodeError(f"Invalid JSON in default dictionary file at: "
f"{default_dict_path}", "", 0)
raise json.JSONDecodeError(
f"Invalid JSON in default dictionary file at: {default_dict_path}", "", 0
)
except Exception as e:
raise Exception(f"Error reverting custom dictionary to default: {e}")
Loading