text-summarizer-aweebtaku 1.0.0__py3-none-any.whl

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.
@@ -0,0 +1,3 @@
1
+ from .summarizer import TextSummarizer
2
+
3
+ __version__ = "1.0.0"
text_summarizer/cli.py ADDED
@@ -0,0 +1,82 @@
1
+ import argparse
2
+ from .summarizer import TextSummarizer
3
+
4
+ def main():
5
+ parser = argparse.ArgumentParser(description="Text Summarization Tool")
6
+ parser.add_argument("--glove-path", default="glove.6B.100d.txt/glove.6B.100d.txt",
7
+ help="Path to GloVe embeddings file")
8
+ parser.add_argument("--num-sentences", type=int, default=5,
9
+ help="Number of sentences in summary")
10
+ parser.add_argument("--csv-file", help="Path to CSV file with articles")
11
+ parser.add_argument("--article-id", type=int, help="Article ID to summarize (if CSV provided)")
12
+
13
+ args = parser.parse_args()
14
+
15
+ try:
16
+ summarizer = TextSummarizer(glove_path=args.glove_path, num_sentences=args.num_sentences)
17
+
18
+ if args.csv_file:
19
+ import pandas as pd
20
+ df = pd.read_csv(args.csv_file)
21
+ scored_sentences = summarizer.run_summarization(df)
22
+
23
+ if args.article_id:
24
+ article_text, summary = summarizer.summarize_article(scored_sentences, args.article_id, df)
25
+ if article_text and summary:
26
+ print("ARTICLE:")
27
+ print(article_text)
28
+ print('\nSUMMARY:')
29
+ print(summary)
30
+ else:
31
+ print(f"Article ID {args.article_id} not found.")
32
+ else:
33
+ summaries = summarizer.summarize_all_articles(scored_sentences, df)
34
+ for article_id, data in summaries.items():
35
+ print(f"Processing Article ID: {article_id}")
36
+ print("ARTICLE:")
37
+ print(data['article'])
38
+ print('\nSUMMARY:')
39
+ print(data['summary'])
40
+ print('\n')
41
+ else:
42
+ # Interactive mode
43
+ df = summarizer.load_data()
44
+ if df.empty:
45
+ return
46
+
47
+ scored_sentences = summarizer.run_summarization(df)
48
+
49
+ while True:
50
+ choice = input("Enter 'S' for a particular article or 'M' for all articles: ").upper()
51
+ if choice == 'S':
52
+ try:
53
+ article_id = int(input("Enter Article ID: "))
54
+ article_text, summary = summarizer.summarize_article(scored_sentences, article_id, df)
55
+ if article_text and summary:
56
+ print("ARTICLE:")
57
+ print(article_text)
58
+ print('\nSUMMARY:')
59
+ print(summary)
60
+ else:
61
+ print(f"Article ID {article_id} not found.")
62
+ except ValueError:
63
+ print("Invalid Article ID.")
64
+ break
65
+ elif choice == 'M':
66
+ summaries = summarizer.summarize_all_articles(scored_sentences, df)
67
+ for article_id, data in summaries.items():
68
+ print(f"Processing Article ID: {article_id}")
69
+ print("ARTICLE:")
70
+ print(data['article'])
71
+ print('\nSUMMARY:')
72
+ print(data['summary'])
73
+ print('\n')
74
+ break
75
+ else:
76
+ print("Invalid choice. Please enter 'S' or 'M'.")
77
+
78
+ except Exception as e:
79
+ print(f"Error: {e}")
80
+
81
+ if __name__ == "__main__":
82
+ main()
@@ -0,0 +1,9 @@
1
+ article_id,article_text
2
+ 1,"Maria Sharapova has basically no friends as tennis players on the WTA Tour. The Russian player has no problems in openly speaking about it and in a recent interview she said: 'I don't really hide any feelings too much. I think everyone knows this is my job here. When I'm on the courts or when I'm on the court playing, I'm a competitor and I want to beat every single person whether they're in the locker room or across the net.So I'm not the one to strike up a conversation about the weather and know that in the next few minutes I have to go and try to win a tennis match. I'm a pretty competitive girl. I say my hellos, but I'm not sending any players flowers as well. Uhm, I'm not really friendly or close to many players. I have not a lot of friends away from the courts.' When she said she is not really close to a lot of players, is that something strategic that she is doing? Is it different on the men's tour than the women's tour? 'No, not at all. I think just because you're in the same sport doesn't mean that you have to be friends with everyone just because you're categorized, you're a tennis player, so you're going to get along with tennis players. I think every person has different interests. I have friends that have completely different jobs and interests, and I've met them in very different parts of my life. I think everyone just thinks because we're tennis players we should be the greatest of friends. But ultimately tennis is just a very small part of what we do. There are so many other things that we're interested in, that we do.'"
3
+ 2,"BASEL, Switzerland (AP), Roger Federer advanced to the 14th Swiss Indoors final of his career by beating seventh-seeded Daniil Medvedev 6-1, 6-4 on Saturday. Seeking a ninth title at his hometown event, and a 99th overall, Federer will play 93th-ranked Marius Copil on Sunday. Federer dominated the 20th-ranked Medvedev and had his first match-point chance to break serve again at 5-1. He then dropped his serve to love, and let another match point slip in Medvedev's next service game by netting a backhand. He clinched on his fourth chance when Medvedev netted from the baseline. Copil upset expectations of a Federer final against Alexander Zverev in a 6-3, 6-7 (6), 6-4 win over the fifth-ranked German in the earlier semifinal. The Romanian aims for a first title after arriving at Basel without a career win over a top-10 opponent. Copil has two after also beating No. 6 Marin Cilic in the second round. Copil fired 26 aces past Zverev and never dropped serve, clinching after 2 1/2 hours with a forehand volley winner to break Zverev for the second time in the semifinal. He came through two rounds of qualifying last weekend to reach the Basel main draw, including beating Zverev's older brother, Mischa. Federer had an easier time than in his only previous match against Medvedev, a three-setter at Shanghai two weeks ago."
4
+ 3,"Roger Federer has revealed that organisers of the re-launched and condensed Davis Cup gave him three days to decide if he would commit to the controversial competition. Speaking at the Swiss Indoors tournament where he will play in Sundays final against Romanian qualifier Marius Copil, the world number three said that given the impossibly short time frame to make a decision, he opted out of any commitment. ""They only left me three days to decide"", Federer said. ""I didn't to have time to consult with all the people I had to consult. ""I could not make a decision in that time, so I told them to do what they wanted."" The 20-time Grand Slam champion has voiced doubts about the wisdom of the one-week format to be introduced by organisers Kosmos, who have promised the International Tennis Federation up to $3 billion in prize money over the next quarter-century. The competition is set to feature 18 countries in the November 18-24 finals in Madrid next year, and will replace the classic home-and-away ties played four times per year for decades. Kosmos is headed by Barcelona footballer Gerard Pique, who is hoping fellow Spaniard Rafael Nadal will play in the upcoming event. Novak Djokovic has said he will give precedence to the ATP's intended re-launch of the defunct World Team Cup in January 2020, at various Australian venues. Major players feel that a big event in late November combined with one in January before the Australian Open will mean too much tennis and too little rest. Federer said earlier this month in Shanghai in that his chances of playing the Davis Cup were all but non-existent. ""I highly doubt it, of course. We will see what happens,"" he said. ""I do not think this was designed for me, anyhow. This was designed for the future generation of players."" Argentina and Britain received wild cards to the new-look event, and will compete along with the four 2018 semi-finalists and the 12 teams who win qualifying rounds next February. ""I don't like being under that kind of pressure,"" Federer said of the deadline Kosmos handed him."
5
+ 4,"Kei Nishikori will try to end his long losing streak in ATP finals and Kevin Anderson will go for his second title of the year at the Erste Bank Open on Sunday. The fifth-seeded Nishikori reached his third final of 2018 after beating Mikhail Kukushkin of Kazakhstan 6-4, 6-3 in the semifinals. A winner of 11 ATP events, Nishikori hasn't triumphed since winning in Memphis in February 2016. He has lost eight straight finals since. The second-seeded Anderson defeated Fernando Verdasco 6-3, 3-6, 6-4. Anderson has a shot at a fifth career title and second of the year after winning in New York in February. Nishikori leads Anderson 4-2 on career matchups, but the South African won their only previous meeting this year. With a victory on Sunday, Anderson will qualify for the ATP Finals. Currently in ninth place, Nishikori with a win could move to within 125 points of the cut for the eight-man event in London next month. Nishikori held serve throughout against Kukushkin, who came through qualifying. He used his first break point to close out the first set before going up 3-0 in the second and wrapping up the win on his first match point. Against Verdasco, Anderson hit nine of his 19 aces in the opening set. The Spaniard broke Anderson twice in the second but didn't get another chance on the South African's serve in the final set."
6
+ 5,"Federer, 37, first broke through on tour over two decades ago and he has since gone on to enjoy a glittering career. The 20-time Grand Slam winner is chasing his 99th ATP title at the Swiss Indoors this week and he faces Jan-Lennard Struff in the second round on Thursday (6pm BST). Davenport enjoyed most of her success in the late 1990s and her third and final major tournament win came at the 2000 Australian Open. But she claims the mentality of professional tennis players slowly began to change after the new millennium. ""It seems pretty friendly right now,"" said Davenport. ""I think there is a really nice environment and a great atmosphere, especially between some of the veteran players helping some of the younger players out. ""It's a very pleasant atmosphere, I'd have to say, around the locker rooms. ""I felt like the best weeks that I had to get to know players when I was playing were the Fed Cup weeks or the Olympic weeks, not necessarily during the tournaments. ""And even though maybe we had smaller teams, I still think we kept to ourselves quite a bit. ""Not always, but I really feel like in the mid-2000 years there was a huge shift of the attitudes of the top players and being more friendly and being more giving, and a lot of that had to do with players like Roger coming up. ""I just felt like it really kind of changed where people were a little bit, definitely in the 90s, a lot more quiet, into themselves, and then it started to become better."" Meanwhile, Federer is hoping he can improve his service game as he hunts his ninth Swiss Indoors title this week. ""I didn't serve very well [against first-round opponent Filip Kranjovic,"" Federer said. ""I think I was misfiring the corners, I was not hitting the lines enough. ""Clearly you make your life more difficult, but still I was up 6-2, 3-1, break points, so things could have ended very quickly today, even though I didn't have the best serve percentage stats. ""But maybe that's exactly what caught up to me eventually. It's just getting used to it. This is where the first rounds can be tricky."""
7
+ 6,"Nadal has not played tennis since he was forced to retire from the US Open semi-finals against Juan Martin Del Porto with a knee injury. The world No 1 has been forced to miss Spain's Davis Cup clash with France and the Asian hard court season. But with the ATP World Tour Finals due to begin next month, Nadal is ready to prove his fitness before the season-ending event at the 02 Arena. Nadal flew to Paris on Friday and footage from the Paris Masters official Twitter account shows the Spaniard smiling as he strides onto court for practice. The Paris Masters draw has been made and Nadal will start his campaign on Tuesday or Wednesday against either Fernando Verdasco or Jeremy Chardy. Nadal could then play defending champion Jack Sock in the third round before a potential quarter-final with either Borna Coric or Dominic Thiem. Nadal's appearance in Paris is a big boost to the tournament organisers who could see Roger Federer withdraw. Federer is in action at the Swiss Indoors in Basel and if he reaches the final, he could pull out of Paris in a bid to stay fresh for London. But as it stands, Federer is in the draw and is scheduled to face either former world No 3 Milos Raonic or Jo-Wilfried Tsonga in the second round. Federer's projected route to the Paris final could also lead to matches against Kevin Anderson and Novak Djokovic. Djokovic could play Marco Cecchinato in the second round. British No 1 Kyle Edmund is the 12th seed in Paris and will get underway in round two against either Karen Khachanov or Filip Krajinovic."
8
+ 7,"Tennis giveth, and tennis taketh away. The end of the season is finally in sight, and with so many players defending,or losing,huge chunks of points in Singapore, Zhuhai and London, podcast co-hosts Nina Pantic and Irina Falconi discuss the art of defending points (02:14). It's no secret that Jack Sock has struggled on the singles court this year (his record is 7-19). He could lose 1,400 points in the next few weeks, but instead of focusing on the negative, it can all be about perspective (06:28). Let's also not forget his two Grand Slam doubles triumphs this season. Two players, Stefanos Tsitsipas and Kyle Edmund, won their first career ATP titles last week (13:26). It's a big deal because you never forget your first. Irina looks back at her WTA title win in Bogota in 2016, and tells an unforgettable story about her semifinal drama (14:04). In Singapore, one of the biggest storylines (aside from the matches, of course) has been the on-court coaching debate. Nina and Irina give their opinions on what coaching should look like in the future, on both tours (18:55)."
9
+ 8,"Federer won the Swiss Indoors last week by beating Romanian qualifier Marius Copil in the final. The 37-year-old claimed his 99th ATP title and is hunting the century in the French capital this week. Federer has been handed a difficult draw where could could come across Kevin Anderson, Novak Djokovic and Rafael Nadal in the latter rounds. But first the 20-time Grand Slam winner wants to train on the Paris Masters court this afternoon before deciding whether to appear for his opening match against either Milos Raonic or Jo-Wilfried Tsonga. ""On Monday, I am free and will look how I feel,"" Federer said after winning the Swiss Indoors. ""On Tuesday I will fly to Paris and train in the afternoon to be ready for my first match on Wednesday night. ""I felt good all week and better every day. ""We also had the impression that at this stage it might be better to play matches than to train. ""And as long as I fear no injury, I play."" Federer's success in Basel last week was the ninth time he has won his hometown tournament. And he was delighted to be watched on by all of his family and friends as he purchased 60 tickets for the final for those dearest to him. ""My children, my parents, my sister and my team are all there,"" Federer added. ""It is always very emotional for me to thank my team. And sometimes it tilts with the emotions, sometimes I just stumble. ""It means the world to me. It makes me incredibly happy to win my home tournament and make people happy here. ""I do not know if it's maybe my last title, so today I try a lot more to absorb that and enjoy the moments much more consciously. ""Maybe I should celebrate as if it were my last title. ""There are very touching moments: seeing the ball children, the standing ovations, all the familiar faces in the audience. Because it was not always easy in the last weeks."""
@@ -0,0 +1,202 @@
1
+ import pandas as pd
2
+ import numpy as np
3
+ import nltk
4
+ import os
5
+ from nltk.tokenize import sent_tokenize
6
+ from nltk.corpus import stopwords
7
+ from sklearn.metrics.pairwise import cosine_similarity
8
+ import networkx as nx
9
+
10
+ # Download necessary NLTK data
11
+ # nltk.download('punkt_tab')
12
+ # nltk.download('stopwords')
13
+
14
+ class TextSummarizer:
15
+ """A class for summarizing text documents using GloVe embeddings and PageRank."""
16
+
17
+ def __init__(self, glove_path='glove.6B.100d.txt/glove.6B.100d.txt', num_sentences=5):
18
+ self.glove_path = glove_path
19
+ self.num_sentences = num_sentences
20
+ self.word_embeddings = {}
21
+ self.stop_words = set(stopwords.words('english'))
22
+ self._load_embeddings()
23
+
24
+ def _load_embeddings(self):
25
+ """Load GloVe word embeddings from file."""
26
+ try:
27
+ with open(self.glove_path, 'r', encoding='utf-8') as f:
28
+ for line in f:
29
+ values = line.split()
30
+ word = values[0]
31
+ coefs = np.asarray(values[1:], dtype='float32')
32
+ self.word_embeddings[word] = coefs
33
+ except FileNotFoundError:
34
+ raise FileNotFoundError(f"GloVe file not found at {self.glove_path}")
35
+
36
+ def load_data(self):
37
+ """Load data interactively."""
38
+ while True:
39
+ choice = input("Enter 'P' to paste a single article,\n'U' to upload a CSV with multiple articles,\n'C' to create a new CSV with multiple articles: ").upper()
40
+ df = pd.DataFrame()
41
+ save_csv = True
42
+
43
+ if choice == 'P':
44
+ article_text = input("Paste your article text here:\n")
45
+ df = pd.DataFrame([{'article_id': 1, 'article_text': article_text}])
46
+ print('DataFrame created from single article.')
47
+ save_csv = False
48
+ break
49
+ elif choice == 'U':
50
+ print("You chose to load an existing CSV file. It should contain 'article_id' and 'article_text' columns.")
51
+ save_csv = False
52
+ while True:
53
+ file_name = input("Enter the name of the CSV file (e.g., 'tennis.csv') or type 'cancel' to go back: ").strip()
54
+ if file_name.lower() == 'cancel':
55
+ break
56
+ if os.path.exists(file_name) and file_name.lower().endswith('.csv'):
57
+ try:
58
+ df = pd.read_csv(file_name)
59
+ print(f'CSV file "{file_name}" loaded successfully.')
60
+ break
61
+ except Exception as e:
62
+ print(f"Error reading file '{file_name}': {e}")
63
+ else:
64
+ print(f"File '{file_name}' not found or is not a CSV. Please try again.")
65
+ if not df.empty:
66
+ break
67
+ elif choice == 'C':
68
+ print("You've chosen to create a CSV with multiple articles. Enter 'done' for article ID when finished.")
69
+ articles_data = []
70
+ article_counter = 1
71
+ while True:
72
+ article_id_input = input(f"Enter article ID for article {article_counter} (or 'done' to finish): ").strip()
73
+ if article_id_input.lower() == 'done':
74
+ break
75
+ try:
76
+ article_id = int(article_id_input)
77
+ except ValueError:
78
+ print("Invalid Article ID. Please enter a number or 'done'.")
79
+ continue
80
+ article_text = input("Enter article text:\n").strip()
81
+ if not article_text:
82
+ print("Article text cannot be empty. Please try again.")
83
+ continue
84
+ articles_data.append({'article_id': article_id, 'article_text': article_text})
85
+ article_counter += 1
86
+ if articles_data:
87
+ df = pd.DataFrame(articles_data)
88
+ print('DataFrame created from multiple articles.')
89
+ break
90
+ else:
91
+ print("No articles were entered. Please try again or choose another option.")
92
+ else:
93
+ print("Invalid choice. Please enter 'P', 'U', or 'C'.")
94
+
95
+ if not df.empty and save_csv:
96
+ df.to_csv('article.csv', index=False)
97
+ print('CSV file "article.csv" created/updated successfully.')
98
+ elif df.empty:
99
+ print("No DataFrame was created.")
100
+ return df
101
+
102
+ def preprocess_sentences(self, df):
103
+ """Tokenize articles into sentences and store metadata."""
104
+ all_sentences_data = []
105
+ sentence_counter_global = 0
106
+ for _, article_row in df.iterrows():
107
+ article_id = article_row['article_id']
108
+ article_text = article_row['article_text']
109
+ article_sentences = sent_tokenize(article_text)
110
+ for sent_idx, sentence_text in enumerate(article_sentences):
111
+ all_sentences_data.append({
112
+ 'global_sentence_idx': sentence_counter_global,
113
+ 'article_id': article_id,
114
+ 'sentence_text': sentence_text,
115
+ 'original_article_sentence_idx': sent_idx
116
+ })
117
+ sentence_counter_global += 1
118
+ return all_sentences_data
119
+
120
+ def clean_sentences(self, sentences):
121
+ """Clean sentences: remove non-alphabetic, lowercase, remove stopwords."""
122
+ clean_sentences = pd.Series(sentences).str.replace(r"[^a-zA-Z\s]", " ", regex=True)
123
+ clean_sentences = clean_sentences.str.lower()
124
+ clean_sentences = clean_sentences.apply(lambda s: self._remove_stopwords(s.split()))
125
+ return clean_sentences.tolist()
126
+
127
+ def _remove_stopwords(self, sen):
128
+ """Remove stopwords from a list of words."""
129
+ return " ".join([word for word in sen if word not in self.stop_words])
130
+
131
+ def compute_sentence_vectors(self, clean_sentences):
132
+ """Compute sentence vectors using GloVe embeddings."""
133
+ sentence_vectors = []
134
+ for sentence in clean_sentences:
135
+ words = sentence.split()
136
+ if words:
137
+ vectors = [self.word_embeddings.get(w, np.zeros(100)) for w in words]
138
+ v = np.mean(vectors, axis=0)
139
+ else:
140
+ v = np.zeros(100)
141
+ sentence_vectors.append(v)
142
+ return sentence_vectors
143
+
144
+ def compute_similarity_matrix(self, sentence_vectors):
145
+ """Compute cosine similarity matrix."""
146
+ n = len(sentence_vectors)
147
+ sim_mat = np.zeros((n, n))
148
+ for i in range(n):
149
+ for j in range(n):
150
+ if i != j:
151
+ sim_mat[i][j] = cosine_similarity(
152
+ sentence_vectors[i].reshape(1, -1),
153
+ sentence_vectors[j].reshape(1, -1)
154
+ )[0, 0]
155
+ return sim_mat
156
+
157
+ def rank_sentences(self, sim_mat):
158
+ """Rank sentences using PageRank."""
159
+ nx_graph = nx.from_numpy_array(sim_mat)
160
+ scores = nx.pagerank(nx_graph)
161
+ return scores
162
+
163
+ def summarize_article(self, scored_sentences, article_id, df):
164
+ """Generate summary for a specific article."""
165
+ article_sentences = [s for s in scored_sentences if s['article_id'] == article_id]
166
+ if not article_sentences:
167
+ return None, None
168
+
169
+ article_sentences.sort(key=lambda x: x['score'], reverse=True)
170
+ top_sentences = article_sentences[:self.num_sentences]
171
+ top_sentences.sort(key=lambda x: x['original_article_sentence_idx'])
172
+ summary = " ".join([s['sentence_text'] for s in top_sentences])
173
+
174
+ article_row = df[df['article_id'] == article_id]
175
+ if not article_row.empty:
176
+ article_text = article_row['article_text'].iloc[0]
177
+ return article_text, summary
178
+ return None, None
179
+
180
+ def summarize_all_articles(self, scored_sentences, df):
181
+ """Generate summaries for all articles."""
182
+ summaries = {}
183
+ for _, article_row in df.iterrows():
184
+ article_id = article_row['article_id']
185
+ article_text, summary = self.summarize_article(scored_sentences, article_id, df)
186
+ if article_text and summary:
187
+ summaries[article_id] = {'article': article_text, 'summary': summary}
188
+ return summaries
189
+
190
+ def run_summarization(self, df):
191
+ """Run the full summarization pipeline."""
192
+ sentences_data = self.preprocess_sentences(df)
193
+ sentences = [s['sentence_text'] for s in sentences_data]
194
+ clean_sentences = self.clean_sentences(sentences)
195
+ sentence_vectors = self.compute_sentence_vectors(clean_sentences)
196
+ sim_mat = self.compute_similarity_matrix(sentence_vectors)
197
+ scores = self.rank_sentences(sim_mat)
198
+
199
+ for i, sentence_data in enumerate(sentences_data):
200
+ sentence_data['score'] = scores[i]
201
+
202
+ return sentences_data
text_summarizer/ui.py ADDED
@@ -0,0 +1,380 @@
1
+ import tkinter as tk
2
+ from tkinter import ttk, filedialog, messagebox, scrolledtext
3
+ import pandas as pd
4
+ import threading
5
+ from .summarizer import TextSummarizer
6
+
7
+
8
+ class TextSummarizerUI:
9
+ """A GUI application for text summarization."""
10
+
11
+ def __init__(self, root):
12
+ self.root = root
13
+ self.root.title("Text Summarizer")
14
+ self.root.geometry("1000x700")
15
+ self.root.resizable(True, True)
16
+ self.root.configure(bg='#f0f0f0')
17
+
18
+ # Configure styles
19
+ style = ttk.Style()
20
+ style.configure('TButton', font=('Arial', 10))
21
+ style.configure('TLabel', font=('Arial', 10))
22
+ style.configure('TFrame', background='#f0f0f0')
23
+
24
+ self.summarizer = None
25
+ self.df = None
26
+ self.scored_sentences = None
27
+ self.is_single = False
28
+
29
+ self.create_widgets()
30
+
31
+ def create_widgets(self):
32
+ """Create and layout all UI widgets."""
33
+ # Main frame
34
+ main_frame = ttk.Frame(self.root, padding="10")
35
+ main_frame.pack(fill=tk.BOTH, expand=True)
36
+
37
+ # Title
38
+ title_label = ttk.Label(main_frame, text="Text Summarizer", font=("Arial", 16, "bold"))
39
+ title_label.pack(pady=10)
40
+
41
+ # Data loading section
42
+ load_frame = ttk.LabelFrame(main_frame, text="Load Data", padding="10")
43
+ load_frame.pack(fill=tk.X, pady=10)
44
+
45
+ ttk.Button(load_frame, text="Paste Single Document", command=self.paste_single).pack(side=tk.LEFT, padx=5)
46
+ ttk.Button(load_frame, text="Upload CSV", command=self.upload_csv).pack(side=tk.LEFT, padx=5)
47
+ ttk.Button(load_frame, text="Create CSV", command=self.create_csv).pack(side=tk.LEFT, padx=5)
48
+
49
+ # Status
50
+ self.status_label = ttk.Label(main_frame, text="Ready to load data")
51
+ self.status_label.pack(pady=5)
52
+
53
+ # Summarization section
54
+ self.sum_frame = ttk.LabelFrame(main_frame, text="Summarization", padding="10")
55
+ self.sum_frame.pack(fill=tk.BOTH, expand=True, pady=10)
56
+
57
+ self.update_summarization_ui()
58
+
59
+ # Results display
60
+ results_frame = ttk.LabelFrame(main_frame, text="Results", padding="10")
61
+ results_frame.pack(fill=tk.BOTH, expand=True, pady=10)
62
+
63
+ # Original document display
64
+ original_frame = ttk.Frame(results_frame)
65
+ original_frame.pack(fill=tk.BOTH, expand=True, pady=5)
66
+ label_frame = ttk.Frame(original_frame)
67
+ label_frame.pack(fill=tk.X)
68
+ ttk.Label(label_frame, text="Original Document:").pack(side=tk.LEFT)
69
+ ttk.Button(label_frame, text="View Full", command=self.view_original).pack(side=tk.RIGHT)
70
+ self.original_text = scrolledtext.ScrolledText(original_frame, wrap=tk.WORD, height=8)
71
+ self.original_text.pack(fill=tk.BOTH, expand=True)
72
+ self.original_text.config(state='disabled')
73
+
74
+ # Summary display
75
+ summary_frame = ttk.Frame(results_frame)
76
+ summary_frame.pack(fill=tk.BOTH, expand=True, pady=5)
77
+ label_frame2 = ttk.Frame(summary_frame)
78
+ label_frame2.pack(fill=tk.X)
79
+ ttk.Label(label_frame2, text="Summary:").pack(side=tk.LEFT)
80
+ ttk.Button(label_frame2, text="View Full", command=self.view_summary).pack(side=tk.RIGHT)
81
+ self.summary_text = scrolledtext.ScrolledText(summary_frame, wrap=tk.WORD, height=8)
82
+ self.summary_text.pack(fill=tk.BOTH, expand=True)
83
+ self.summary_text.config(state='disabled')
84
+
85
+ # Bottom frame for Clear All and Save Summaries buttons
86
+ bottom_frame = ttk.Frame(main_frame)
87
+ bottom_frame.pack(side=tk.BOTTOM, fill=tk.X, pady=1)
88
+ ttk.Button(bottom_frame, text="Save Summaries", command=self.save_summaries).pack(side=tk.RIGHT, padx=5)
89
+ ttk.Button(bottom_frame, text="Clear All", command=self.clear_all).pack(side=tk.RIGHT, padx=5)
90
+
91
+ def update_summarization_ui(self):
92
+ """Update the summarization UI based on data type (single or multiple)."""
93
+ # Clear existing widgets in sum_frame
94
+ for widget in self.sum_frame.winfo_children():
95
+ widget.destroy()
96
+
97
+ if self.is_single:
98
+ ttk.Button(self.sum_frame, text="Summarize", command=self.summarize_single).pack(side=tk.LEFT, padx=5)
99
+ else:
100
+ ttk.Button(self.sum_frame, text="Summarize Single Document", command=self.summarize_single).pack(side=tk.LEFT, padx=5)
101
+ ttk.Button(self.sum_frame, text="Summarize All Documents", command=self.summarize_all).pack(side=tk.LEFT, padx=5)
102
+
103
+ # Article ID input
104
+ id_frame = ttk.Frame(self.sum_frame)
105
+ id_frame.pack(side=tk.LEFT, padx=10)
106
+ ttk.Label(id_frame, text="Document ID:").pack(side=tk.LEFT)
107
+ self.article_id_entry = ttk.Entry(id_frame, width=10)
108
+ self.article_id_entry.pack(side=tk.LEFT, padx=5)
109
+
110
+ def save_summaries(self):
111
+ """Save the generated summaries to a CSV file."""
112
+ if self.scored_sentences is None or self.df is None:
113
+ messagebox.showwarning("Warning", "No summaries to save. Please summarize first.")
114
+ return
115
+ if self.is_single:
116
+ # Summarize the single document
117
+ article_id = 1
118
+ article_text, summary = self.summarizer.summarize_article(self.scored_sentences, article_id, self.df)
119
+ data = [{"article_id": article_id, "article_text": article_text, "summary": summary}]
120
+ else:
121
+ # Summarize all documents
122
+ summaries = self.summarizer.summarize_all_articles(self.scored_sentences, self.df)
123
+ data = []
124
+ for article_id, d in summaries.items():
125
+ data.append({"article_id": article_id, "article_text": d["article"], "summary": d["summary"]})
126
+ file_path = filedialog.asksaveasfilename(defaultextension=".csv", filetypes=[("CSV files", "*.csv")])
127
+ if file_path:
128
+ try:
129
+ df = pd.DataFrame(data)
130
+ df.to_csv(file_path, index=False)
131
+ messagebox.showinfo("Success", f"Summaries saved to {file_path}")
132
+ except Exception as e:
133
+ messagebox.showerror("Error", f"Failed to save summaries: {str(e)}")
134
+
135
+ def clear_all(self):
136
+ """Clear all data and reset the UI."""
137
+ self.is_single = False
138
+ self.df = None
139
+ self.scored_sentences = None
140
+ self.summarizer = None
141
+ self.status_label.config(text="Ready to load data")
142
+ self.update_summarization_ui()
143
+ self.original_text.config(state='normal')
144
+ self.original_text.delete(1.0, tk.END)
145
+ self.original_text.config(state='disabled')
146
+ self.summary_text.config(state='normal')
147
+ self.summary_text.delete(1.0, tk.END)
148
+ self.summary_text.config(state='disabled')
149
+
150
+ def paste_single(self):
151
+ """Open dialog to paste a single document."""
152
+ dialog = PasteDialog(self.root)
153
+ self.root.wait_window(dialog.top)
154
+ if dialog.result:
155
+ self.df = pd.DataFrame([{'article_id': 1, 'article_text': dialog.result}])
156
+ self.is_single = True
157
+ self.status_label.config(text="Single document loaded")
158
+ self.update_summarization_ui()
159
+ self.initialize_summarizer()
160
+
161
+ def upload_csv(self):
162
+ """Upload and load a CSV file with documents."""
163
+ file_path = filedialog.askopenfilename(filetypes=[("CSV files", "*.csv")])
164
+ if file_path:
165
+ try:
166
+ self.df = pd.read_csv(file_path)
167
+ self.is_single = False
168
+ self.status_label.config(text=f"CSV loaded from {file_path}")
169
+ self.update_summarization_ui()
170
+ self.initialize_summarizer()
171
+ except Exception as e:
172
+ messagebox.showerror("Error", f"Failed to load CSV: {str(e)}")
173
+
174
+ def create_csv(self):
175
+ """Open dialog to create a new CSV with multiple documents."""
176
+ dialog = CreateCSVDialog(self.root)
177
+ self.root.wait_window(dialog.top)
178
+ if dialog.result:
179
+ self.df = pd.DataFrame(dialog.result)
180
+ self.is_single = False
181
+ self.status_label.config(text="CSV created")
182
+ self.update_summarization_ui()
183
+ self.initialize_summarizer()
184
+
185
+ def initialize_summarizer(self):
186
+ """Initialize the summarizer and start processing data in a thread."""
187
+ if self.df is not None and not self.df.empty:
188
+ self.summarizer = TextSummarizer()
189
+ self.status_label.config(text="Processing data...")
190
+ threading.Thread(target=self.process_data).start()
191
+
192
+ def process_data(self):
193
+ """Process the data to compute sentence scores."""
194
+ try:
195
+ self.scored_sentences = self.summarizer.run_summarization(self.df)
196
+ self.status_label.config(text="Data processed successfully")
197
+ except Exception as e:
198
+ messagebox.showerror("Error", f"Processing failed: {str(e)}")
199
+
200
+ def summarize_single(self):
201
+ """Summarize a single document."""
202
+ if self.scored_sentences is None:
203
+ messagebox.showwarning("Warning", "Please load and process data first")
204
+ return
205
+ if self.is_single:
206
+ article_id = 1
207
+ else:
208
+ try:
209
+ article_id = int(self.article_id_entry.get())
210
+ except ValueError:
211
+ messagebox.showerror("Error", "Invalid Document ID")
212
+ return
213
+ article_text, summary = self.summarizer.summarize_article(self.scored_sentences, article_id, self.df)
214
+ if article_text and summary:
215
+ self.display_result(article_text, summary)
216
+ else:
217
+ messagebox.showerror("Error", f"Document ID {article_id} not found")
218
+
219
+ def summarize_all(self):
220
+ """Summarize all documents and display in text areas."""
221
+ if self.scored_sentences is None:
222
+ messagebox.showwarning("Warning", "Please load and process data first")
223
+ return
224
+ summaries = self.summarizer.summarize_all_articles(self.scored_sentences, self.df)
225
+ self.original_text.config(state='normal')
226
+ self.summary_text.config(state='normal')
227
+ self.original_text.delete(1.0, tk.END)
228
+ self.summary_text.delete(1.0, tk.END)
229
+ for article_id, data in summaries.items():
230
+ self.original_text.insert(tk.END, f"Document ID: {article_id}\n{data['article']}\n\n")
231
+ self.summary_text.insert(tk.END, f"Document ID: {article_id}\n{data['summary']}\n\n")
232
+ self.original_text.config(state='disabled')
233
+ self.summary_text.config(state='disabled')
234
+
235
+ def display_result(self, article, summary):
236
+ """Display the article and summary in the text areas."""
237
+ self.original_text.config(state='normal')
238
+ self.original_text.delete(1.0, tk.END)
239
+ self.original_text.insert(tk.END, article)
240
+ self.original_text.config(state='disabled')
241
+ self.summary_text.config(state='normal')
242
+ self.summary_text.delete(1.0, tk.END)
243
+ self.summary_text.insert(tk.END, summary)
244
+ self.summary_text.config(state='disabled')
245
+
246
+ def view_original(self):
247
+ """Open a full view window for the original document."""
248
+ top = tk.Toplevel(self.root)
249
+ top.title("Original Document - Full View")
250
+ top.geometry("900x700")
251
+ top.resizable(True, True)
252
+ text = scrolledtext.ScrolledText(top, wrap=tk.WORD)
253
+ text.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
254
+ text.insert(tk.END, self.original_text.get(1.0, tk.END))
255
+ text.config(state=tk.DISABLED) # Make it read-only
256
+
257
+ def view_summary(self):
258
+ """Open a full view window for the summary."""
259
+ top = tk.Toplevel(self.root)
260
+ top.title("Summary - Full View")
261
+ top.geometry("900x700")
262
+ top.resizable(True, True)
263
+ text = scrolledtext.ScrolledText(top, wrap=tk.WORD)
264
+ text.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
265
+ text.insert(tk.END, self.summary_text.get(1.0, tk.END))
266
+ text.config(state=tk.DISABLED) # Make it read-only
267
+
268
+
269
+ class PasteDialog:
270
+ """Dialog for pasting a single document."""
271
+
272
+ def __init__(self, parent):
273
+ self.top = tk.Toplevel(parent)
274
+ self.top.title("Paste Document")
275
+ self.top.geometry("700x600")
276
+ self.top.transient(parent)
277
+ self.top.grab_set()
278
+ self.result = None
279
+
280
+ ttk.Label(self.top, text="Paste your document:").pack(pady=5)
281
+ self.text = scrolledtext.ScrolledText(self.top, wrap=tk.WORD)
282
+ self.text.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
283
+
284
+ button_frame = ttk.Frame(self.top)
285
+ button_frame.pack(side=tk.BOTTOM, fill=tk.X, pady=5)
286
+ ttk.Button(button_frame, text="OK", command=self.ok).pack(side=tk.RIGHT, padx=5)
287
+ ttk.Button(button_frame, text="Cancel", command=self.cancel).pack(side=tk.RIGHT, padx=5)
288
+
289
+ def ok(self):
290
+ self.result = self.text.get(1.0, tk.END).strip()
291
+ self.top.destroy()
292
+
293
+ def cancel(self):
294
+ self.top.destroy()
295
+
296
+
297
+ class CreateCSVDialog:
298
+ """Dialog for creating a CSV with multiple documents."""
299
+
300
+ def __init__(self, parent):
301
+ self.top = tk.Toplevel(parent)
302
+ self.top.title("Create CSV")
303
+ self.top.geometry("700x600")
304
+ self.top.transient(parent)
305
+ self.top.grab_set()
306
+ self.result = []
307
+
308
+ self.articles = []
309
+ self.counter = 1
310
+
311
+ ttk.Label(self.top, text="Enter documents (ID and text):").pack(pady=5)
312
+
313
+ input_frame = ttk.Frame(self.top)
314
+ input_frame.pack(fill=tk.X, padx=10, pady=5)
315
+
316
+ ttk.Label(input_frame, text="Document ID:").grid(row=0, column=0, sticky=tk.W)
317
+ self.id_entry = ttk.Entry(input_frame, width=10)
318
+ self.id_entry.grid(row=0, column=1, padx=5)
319
+
320
+ ttk.Label(input_frame, text="Document Text:").grid(row=1, column=0, sticky=tk.W)
321
+ self.text_entry = scrolledtext.ScrolledText(input_frame, wrap=tk.WORD, height=5)
322
+ self.text_entry.grid(row=1, column=1, padx=5, pady=5)
323
+
324
+ button_frame = ttk.Frame(self.top)
325
+ button_frame.pack(fill=tk.X, padx=10, pady=5)
326
+
327
+ ttk.Button(button_frame, text="Add Document", command=self.add_article).pack(side=tk.LEFT, padx=5)
328
+ ttk.Button(button_frame, text="Done", command=self.done).pack(side=tk.LEFT, padx=5)
329
+ ttk.Button(button_frame, text="Cancel", command=self.cancel).pack(side=tk.RIGHT, padx=5)
330
+
331
+ self.listbox = tk.Listbox(self.top, height=10)
332
+ self.listbox.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
333
+
334
+ def add_article(self):
335
+ try:
336
+ article_id = int(self.id_entry.get())
337
+ article_text = self.text_entry.get(1.0, tk.END).strip()
338
+ if article_text:
339
+ self.articles.append({'article_id': article_id, 'article_text': article_text})
340
+ self.listbox.insert(tk.END, f"ID: {article_id} - {article_text[:50]}...")
341
+ self.id_entry.delete(0, tk.END)
342
+ self.text_entry.delete(1.0, tk.END)
343
+ self.counter += 1
344
+ self.id_entry.insert(0, str(self.counter))
345
+ else:
346
+ messagebox.showwarning("Warning", "Document cannot be empty")
347
+ except ValueError:
348
+ messagebox.showerror("Error", "Invalid Document ID")
349
+
350
+ def done(self):
351
+ if self.articles:
352
+ save = messagebox.askyesno("Save CSV", "Do you want to save the CSV file?")
353
+ if save:
354
+ file_path = filedialog.asksaveasfilename(defaultextension=".csv", filetypes=[("CSV files", "*.csv")])
355
+ if file_path:
356
+ df = pd.DataFrame(self.articles)
357
+ df.to_csv(file_path, index=False)
358
+ try:
359
+ loaded_df = pd.read_csv(file_path)
360
+ self.result = loaded_df.to_dict(orient='records')
361
+ except Exception:
362
+ self.result = self.articles
363
+ else:
364
+ self.result = self.articles
365
+ else:
366
+ self.result = self.articles
367
+ self.top.destroy()
368
+
369
+ def cancel(self):
370
+ self.top.destroy()
371
+
372
+
373
+ def main():
374
+ root = tk.Tk()
375
+ app = TextSummarizerUI(root)
376
+ root.mainloop()
377
+
378
+
379
+ if __name__ == "__main__":
380
+ main()
@@ -0,0 +1,173 @@
1
+ Metadata-Version: 2.4
2
+ Name: text-summarizer-aweebtaku
3
+ Version: 1.0.0
4
+ Summary: A text summarization tool using GloVe embeddings and PageRank algorithm
5
+ Home-page: https://github.com/AWeebTaku/Summarizer
6
+ Author: Your Name
7
+ Author-email: your.email@example.com
8
+ License: MIT
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Requires-Python: >=3.8
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: pandas
21
+ Requires-Dist: numpy
22
+ Requires-Dist: nltk
23
+ Requires-Dist: scikit-learn
24
+ Requires-Dist: networkx
25
+ Dynamic: author
26
+ Dynamic: author-email
27
+ Dynamic: classifier
28
+ Dynamic: description
29
+ Dynamic: description-content-type
30
+ Dynamic: home-page
31
+ Dynamic: license
32
+ Dynamic: license-file
33
+ Dynamic: requires-dist
34
+ Dynamic: requires-python
35
+ Dynamic: summary
36
+
37
+ # Text Summarizer
38
+
39
+ A Python-based text summarization tool that uses GloVe word embeddings and PageRank algorithm to generate extractive summaries of documents.
40
+
41
+ ## Features
42
+
43
+ - **Extractive Summarization**: Uses sentence similarity and PageRank to identify the most important sentences
44
+ - **GloVe Embeddings**: Leverages pre-trained GloVe word vectors for semantic similarity calculation
45
+ - **Multiple Input Methods**: Support for single documents, CSV files, or interactive creation
46
+ - **GUI Interface**: User-friendly Tkinter-based graphical interface
47
+ - **Command Line Interface**: Scriptable command-line tool for automation
48
+ - **Batch Processing**: Process multiple documents at once
49
+
50
+ ## Installation
51
+
52
+ ### Prerequisites
53
+
54
+ - Python 3.8 or higher
55
+ - Required packages (automatically installed): pandas, numpy, nltk, scikit-learn, networkx
56
+
57
+ ### Install from PyPI
58
+
59
+ ```bash
60
+ pip install text-summarizer-aweebtaku
61
+ ```
62
+
63
+ ### Install from Source
64
+
65
+ 1. Clone the repository:
66
+ ```bash
67
+ git clone https://github.com/AWeebTaku/Summarizer.git
68
+ cd Summarizer
69
+ ```
70
+
71
+ 2. Install the package:
72
+ ```bash
73
+ pip install -e .
74
+ ```
75
+
76
+ ### Download GloVe Embeddings
77
+
78
+ The tool requires GloVe word embeddings. Download the 100d version:
79
+
80
+ ```bash
81
+ wget http://nlp.stanford.edu/data/glove.6B.zip
82
+ unzip glove.6B.zip
83
+ ```
84
+
85
+ Place the `glove.6B.100d.txt` file in the project root or specify the path.
86
+
87
+ ## Usage
88
+
89
+ ### Command Line Interface
90
+
91
+ ```bash
92
+ # Summarize a CSV file
93
+ text-summarizer-aweebtaku --csv-file data/tennis.csv --article-id 1
94
+
95
+ # Interactive mode
96
+ text-summarizer-aweebtaku
97
+ ```
98
+
99
+ ### GUI Interface
100
+
101
+ ```bash
102
+ python -m text_summarizer.ui
103
+ ```
104
+
105
+ ### Python API
106
+
107
+ ```python
108
+ from text_summarizer import TextSummarizer
109
+ import pandas as pd
110
+
111
+ # Initialize summarizer
112
+ summarizer = TextSummarizer(glove_path='glove.6B.100d.txt')
113
+
114
+ # Load data
115
+ df = pd.DataFrame([{'article_id': 1, 'article_text': 'Your text here...'}])
116
+
117
+ # Run summarization
118
+ scored_sentences = summarizer.run_summarization(df)
119
+
120
+ # Get summary for article ID 1
121
+ article_text, summary = summarizer.summarize_article(scored_sentences, 1, df)
122
+ print(summary)
123
+ ```
124
+
125
+ ## Data Format
126
+
127
+ Input data should be in CSV format with columns:
128
+ - `article_id`: Unique identifier for each document
129
+ - `article_text`: The full text of the document
130
+
131
+ Example:
132
+ ```csv
133
+ article_id,article_text
134
+ 1,"This is the first article. It contains multiple sentences..."
135
+ 2,"This is the second article. It also has several sentences..."
136
+ ```
137
+
138
+ ## Algorithm
139
+
140
+ The summarization process follows these steps:
141
+
142
+ 1. **Sentence Tokenization**: Split documents into individual sentences
143
+ 2. **Text Cleaning**: Remove punctuation, convert to lowercase, remove stopwords
144
+ 3. **Sentence Vectorization**: Convert sentences to vectors using GloVe embeddings
145
+ 4. **Similarity Calculation**: Compute cosine similarity between all sentence pairs
146
+ 5. **PageRank Scoring**: Apply PageRank algorithm to identify important sentences
147
+ 6. **Summary Extraction**: Select top-ranked sentences in original order
148
+
149
+ ## Configuration
150
+
151
+ - `glove_path`: Path to GloVe embeddings file (default: 'glove.6B.100d.txt/glove.6B.100d.txt')
152
+ - `num_sentences`: Number of sentences in summary (default: 5)
153
+
154
+ ## License
155
+
156
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
157
+
158
+ ## Contributing
159
+
160
+ Contributions are welcome! Please feel free to submit a Pull Request.
161
+
162
+ ## Citation
163
+
164
+ If you use this tool in your research, please cite:
165
+
166
+ ```
167
+ @software{text_summarizer,
168
+ title = {Text Summarizer},
169
+ author = {Your Name},
170
+ url = {https://github.com/AWeebTaku/Summarizer},
171
+ year = {2024}
172
+ }
173
+ ```
@@ -0,0 +1,11 @@
1
+ text_summarizer/__init__.py,sha256=juqSmwYQLqoiZpyLfxE1sJKoYLNAe_-a3_LOIUV6J6g,63
2
+ text_summarizer/cli.py,sha256=ZwpSJTbAQvXLDhSdbKWE054O4ttLj60X7GBuWowitLU,3550
3
+ text_summarizer/summarizer.py,sha256=ctQlZSjP9jkgM9ZZ7yTtjqG60xr9CRnUWciCeSpLujo,9242
4
+ text_summarizer/ui.py,sha256=Ky40zcr-_0zh5I7Kh4Bc8hKrEBdOALe5G4i3ukDJWts,16638
5
+ text_summarizer/data/tennis.csv,sha256=oEPZr4Dy6cmCDtdQ2QYJyJpERzQseuNJ53JP2XyIfBk,12943
6
+ text_summarizer_aweebtaku-1.0.0.dist-info/licenses/LICENSE,sha256=q53YqEH5OACuJ8YmE3i9pND509hapVaOX42ix2AMkZ8,1085
7
+ text_summarizer_aweebtaku-1.0.0.dist-info/METADATA,sha256=bzPTGyKfPT54u4BNlDRmgyrI5f3ngUZBLL3CHE3l0Co,4802
8
+ text_summarizer_aweebtaku-1.0.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
9
+ text_summarizer_aweebtaku-1.0.0.dist-info/entry_points.txt,sha256=5PYNpbprDgkQtQUFzv5f_MW5OXI5GZu_zKqhGFoh_2o,71
10
+ text_summarizer_aweebtaku-1.0.0.dist-info/top_level.txt,sha256=2s-4Uyii86k2iEeiIi0JghAXW47cEQ8qM_ONYPs9Gh8,16
11
+ text_summarizer_aweebtaku-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.10.2)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ text-summarizer-aweebtaku = text_summarizer.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Your Name
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ text_summarizer