mirror of
https://github.com/Code-For-Groningen/temmies.git
synced 2025-03-16 23:20:16 +01:00
Compare commits
3 Commits
b819305704
...
27d21ac7c1
Author | SHA1 | Date | |
---|---|---|---|
27d21ac7c1 | |||
df8429d811 | |||
3c63a64eac |
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,6 +1,6 @@
|
|||||||
# Config - Testing
|
# Config - Testing
|
||||||
config.py
|
config.py
|
||||||
baller.py
|
tests/
|
||||||
|
|
||||||
#Doc env
|
#Doc env
|
||||||
.docs_env
|
.docs_env
|
||||||
|
@ -6,10 +6,13 @@ A python library which interacts with themis. Uses bs4. I'll try to end developm
|
|||||||
|
|
||||||
## Intended Features
|
## Intended Features
|
||||||
* [x] Log in
|
* [x] Log in
|
||||||
|
* [x] Submit
|
||||||
|
* [x] Bulk download of test cases and files
|
||||||
|
* [ ] Submission status
|
||||||
* [ ] Classes, methods and attributes described in the map below
|
* [ ] Classes, methods and attributes described in the map below
|
||||||
|
|
||||||
## Docs
|
## Docs
|
||||||
[here](http://temmies.rtfd.io/). Heavily WIP.
|
[here](http://temmies.rtfd.io/).
|
||||||
|
|
||||||
## Class map
|
## Class map
|
||||||

|

|
||||||
|
17
docs/api.md
17
docs/api.md
@ -134,8 +134,21 @@ Downloads all test cases in the exercise group to a directory `path`. Defaults t
|
|||||||
```
|
```
|
||||||
|
|
||||||
#### `submit(files)`
|
#### `submit(files)`
|
||||||
Submits the files to the exercise group. (This is not implemented yet)
|
Submits the files to the exercise group. Default arguments are `judge=True`, `wait=True` and `silent=True`. `judge` will judge the submission instantly, and `wait` will wait for the submission to finish. Turning off `silent` will print the submission status dynamically.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
assignment.submit(["file1.py", "file2.py"])
|
suitcase[7].exercises[1].submit("suitcase.py", silent=False)
|
||||||
|
|
||||||
|
>>> 1: ✅
|
||||||
|
>>> 2: ✅
|
||||||
|
>>> 3: ✅
|
||||||
|
>>> 4: ✅
|
||||||
|
>>> 5: ✅
|
||||||
|
>>> 6: ✅
|
||||||
|
>>> 7: ✅
|
||||||
|
>>> 8: ✅
|
||||||
|
>>> 9: ✅
|
||||||
|
>>> 10: ✅
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,126 +1,217 @@
|
|||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from exceptions.IllegalAction import IllegalAction
|
from exceptions.IllegalAction import IllegalAction
|
||||||
import re
|
import re
|
||||||
|
from json import loads
|
||||||
class ExerciseGroup():
|
from time import sleep
|
||||||
def __init__(self, url:str, soup, session, parent):
|
|
||||||
self.url = url
|
|
||||||
self.name = soup.text
|
|
||||||
self.__raw = soup
|
|
||||||
self.session = session
|
|
||||||
self.parent = parent # This is unnecessary, but I'll keep it for now
|
|
||||||
self.request = self.session.get(self.url)
|
|
||||||
self.soup = BeautifulSoup(self.request.text, 'lxml')
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"ExerciseGroup {self.name} in folder {self.parent.name}"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def amExercise(self):
|
|
||||||
return "ass-submitable" in self.__raw['class']
|
|
||||||
|
|
||||||
def submit(self):
|
|
||||||
if not self.amExercise:
|
|
||||||
raise IllegalAction(message="You are submitting to a folder.")
|
|
||||||
|
|
||||||
# Logic for submitting
|
|
||||||
|
|
||||||
# Test cases
|
|
||||||
@property
|
|
||||||
def testCases(self):
|
|
||||||
section = self.soup.find_all('div', class_="subsec round shade")
|
|
||||||
tcs = []
|
|
||||||
for div in section:
|
|
||||||
res = div.find("h4", class_="info")
|
|
||||||
if not res:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if "Test cases" in res.text:
|
|
||||||
for case in div.find_all("div", class_="cfg-line"):
|
|
||||||
if link := case.find("a"):
|
|
||||||
tcs.append(link)
|
|
||||||
return tcs
|
|
||||||
return None
|
|
||||||
|
|
||||||
def downloadTCs(self, path="."):
|
|
||||||
# Logic for downloading test cases(if any)
|
|
||||||
# In a div with class "subsec round shade", where there is an h4 with text "Test cases"
|
|
||||||
if not self.amExercise:
|
|
||||||
raise IllegalAction(message="You are downloading test cases from a folder.")
|
|
||||||
|
|
||||||
for tc in self.testCases:
|
|
||||||
url= f"https://themis.housing.rug.nl{tc['href']}"
|
|
||||||
|
|
||||||
print(f"Downloading {tc.text}")
|
|
||||||
# download the files
|
|
||||||
with open(f"{path}/{tc.text}", "wb") as f:
|
|
||||||
f.write(self.session.get(url).content)
|
|
||||||
|
|
||||||
return self.testCases
|
|
||||||
|
|
||||||
# Files
|
|
||||||
@property
|
|
||||||
def files(self):
|
|
||||||
details = self.soup.find('div', id=lambda x: x and x.startswith('details'))
|
|
||||||
|
|
||||||
cfg_lines = details.find_all('div', class_='cfg-line')
|
|
||||||
|
|
||||||
link_list = []
|
|
||||||
|
|
||||||
for line in cfg_lines:
|
|
||||||
key = line.find('span', class_='cfg-key')
|
|
||||||
|
|
||||||
if key and "Downloads" in key.text.strip():
|
|
||||||
# Extract all links in the cfg-val span
|
|
||||||
links = line.find_all('span', class_='cfg-val')
|
|
||||||
for link in links:
|
|
||||||
a = link.find_all('a')
|
|
||||||
for a in a:
|
|
||||||
link_list.append(a)
|
|
||||||
|
|
||||||
return link_list if link_list else None
|
|
||||||
|
|
||||||
|
|
||||||
|
class ExerciseGroup:
|
||||||
|
def __init__(self, url: str, soup, session, parent):
|
||||||
|
self.url = url
|
||||||
|
self.name = soup.text
|
||||||
|
self.__raw = soup
|
||||||
|
self.session = session
|
||||||
|
self.parent = parent # This is unnecessary, but I'll keep it for now
|
||||||
|
self.request = self.session.get(self.url)
|
||||||
|
self.soup = BeautifulSoup(self.request.text, "lxml")
|
||||||
|
|
||||||
def downloadFiles(self, path="."):
|
def __str__(self):
|
||||||
for file in self.files:
|
return f"ExerciseGroup {self.name} in folder {self.parent.name}"
|
||||||
print(f"Downloading file {file.text}")
|
|
||||||
url = f"https://themis.housing.rug.nl{file['href']}"
|
|
||||||
with open(f"{path}/{file.text}", "wb") as f:
|
|
||||||
f.write(self.session.get(url).content)
|
|
||||||
return self.files
|
|
||||||
|
|
||||||
# idea exercises and folders are identical, maybe merge them?
|
@property
|
||||||
@property
|
def amExercise(self):
|
||||||
def exercises(self) -> list:
|
return "ass-submitable" in self.__raw["class"]
|
||||||
if self.amExercise:
|
|
||||||
return self
|
|
||||||
|
|
||||||
section = self.soup.find('div', class_="ass-children")
|
def submit(self):
|
||||||
try:
|
if not self.amExercise:
|
||||||
submittables = section.find_all('a', class_="ass-submitable")
|
raise IllegalAction(message="You are submitting to a folder.")
|
||||||
except AttributeError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return [
|
# Logic for submitting
|
||||||
ExerciseGroup(f"https://themis.housing.rug.nl{x['href']}",
|
|
||||||
x,
|
|
||||||
self.session,
|
|
||||||
self)
|
|
||||||
for x in submittables]
|
|
||||||
|
|
||||||
@property
|
# Test cases
|
||||||
def folders(self) -> list:
|
@property
|
||||||
section = self.soup.find('div', class_="ass-children")
|
def testCases(self):
|
||||||
try:
|
section = self.soup.find_all("div", class_="subsec round shade")
|
||||||
folders = section.find_all('a', class_="ass-group")
|
tcs = []
|
||||||
except AttributeError:
|
for div in section:
|
||||||
return None
|
res = div.find("h4", class_="info")
|
||||||
|
if not res:
|
||||||
|
continue
|
||||||
|
|
||||||
return [
|
if "Test cases" in res.text:
|
||||||
ExerciseGroup(f"https://themis.housing.rug.nl{x['href']}",
|
for case in div.find_all("div", class_="cfg-line"):
|
||||||
x,
|
if link := case.find("a"):
|
||||||
session,
|
tcs.append(link)
|
||||||
self)
|
return tcs
|
||||||
for x in folders]
|
return None
|
||||||
|
|
||||||
|
def downloadTCs(self, path="."):
|
||||||
|
# Logic for downloading test cases(if any)
|
||||||
|
# In a div with class "subsec round shade", where there is an h4 with text "Test cases"
|
||||||
|
if not self.amExercise:
|
||||||
|
raise IllegalAction(message="You are downloading test cases from a folder.")
|
||||||
|
|
||||||
|
for tc in self.testCases:
|
||||||
|
url = f"https://themis.housing.rug.nl{tc['href']}"
|
||||||
|
|
||||||
|
print(f"Downloading {tc.text}")
|
||||||
|
# download the files
|
||||||
|
with open(f"{path}/{tc.text}", "wb") as f:
|
||||||
|
f.write(self.session.get(url).content)
|
||||||
|
|
||||||
|
return self.testCases
|
||||||
|
|
||||||
|
# Files
|
||||||
|
@property
|
||||||
|
def files(self):
|
||||||
|
details = self.soup.find("div", id=lambda x: x and x.startswith("details"))
|
||||||
|
|
||||||
|
cfg_lines = details.find_all("div", class_="cfg-line")
|
||||||
|
|
||||||
|
link_list = []
|
||||||
|
|
||||||
|
for line in cfg_lines:
|
||||||
|
key = line.find("span", class_="cfg-key")
|
||||||
|
|
||||||
|
if key and "Downloads" in key.text.strip():
|
||||||
|
# Extract all links in the cfg-val span
|
||||||
|
links = line.find_all("span", class_="cfg-val")
|
||||||
|
for link in links:
|
||||||
|
a = link.find_all("a")
|
||||||
|
for a in a:
|
||||||
|
link_list.append(a)
|
||||||
|
|
||||||
|
return link_list if link_list else None
|
||||||
|
|
||||||
|
def downloadFiles(self, path="."):
|
||||||
|
for file in self.files:
|
||||||
|
print(f"Downloading file {file.text}")
|
||||||
|
url = f"https://themis.housing.rug.nl{file['href']}"
|
||||||
|
with open(f"{path}/{file.text}", "wb") as f:
|
||||||
|
f.write(self.session.get(url).content)
|
||||||
|
return self.files
|
||||||
|
|
||||||
|
@property
|
||||||
|
def exercises(self) -> list:
|
||||||
|
if self.amExercise:
|
||||||
|
return self
|
||||||
|
|
||||||
|
section = self.soup.find("div", class_="ass-children")
|
||||||
|
try:
|
||||||
|
submittables = section.find_all("a", class_="ass-submitable")
|
||||||
|
except AttributeError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return [
|
||||||
|
ExerciseGroup(
|
||||||
|
f"https://themis.housing.rug.nl{x['href']}", x, self.session, self
|
||||||
|
)
|
||||||
|
for x in submittables
|
||||||
|
]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def folders(self) -> list:
|
||||||
|
section = self.soup.find("div", class_="ass-children")
|
||||||
|
try:
|
||||||
|
folders = section.find_all("a", class_="ass-group")
|
||||||
|
except AttributeError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return [
|
||||||
|
ExerciseGroup(f"https://themis.housing.rug.nl{x['href']}", x, self.session, self)
|
||||||
|
for x in folders
|
||||||
|
]
|
||||||
|
|
||||||
|
# Account for judge
|
||||||
|
def __raceCondition(self, soup, url:str, verbose:bool):
|
||||||
|
self.session.get(url.replace("submission", "judge"))
|
||||||
|
return self.__waitForResult(url, verbose, [])
|
||||||
|
|
||||||
|
def __parseTable(self, soup, url:str, verbose:bool, __printed:list):
|
||||||
|
cases = soup.find_all('tr', class_='sub-casetop')
|
||||||
|
fail_pass = {}
|
||||||
|
i = 1
|
||||||
|
for case in cases:
|
||||||
|
name = case.find('td', class_='sub-casename').text
|
||||||
|
status = case.find('td', class_='status-icon')
|
||||||
|
|
||||||
|
if "pending" in status.get("class"):
|
||||||
|
return self.__raceCondition(soup,url,verbose)
|
||||||
|
|
||||||
|
# queued status-icon
|
||||||
|
if "queued" in status.get("class"):
|
||||||
|
sleep(1) # <- 🗿
|
||||||
|
return self.__waitForResult(url, verbose, __printed)
|
||||||
|
|
||||||
|
if "Passed" in status.text:
|
||||||
|
fail_pass[int(name)] = True
|
||||||
|
if int(name) not in __printed:
|
||||||
|
print(f"{name}: ✅")
|
||||||
|
elif "Wrong output" in status.text:
|
||||||
|
fail_pass[int(name)] = False
|
||||||
|
if int(name) not in __printed:
|
||||||
|
print(f"{name}: ❌")
|
||||||
|
elif ("No status" or "error") in status.text:
|
||||||
|
fail_pass[int(name)] = None
|
||||||
|
if int(name) not in __printed:
|
||||||
|
print(f"{name}:🐛")
|
||||||
|
|
||||||
|
__printed.append(int(name))
|
||||||
|
i += 1
|
||||||
|
return fail_pass
|
||||||
|
|
||||||
|
def __waitForResult(self, url:str, verbose:bool, __printed:list):
|
||||||
|
# This waits for result and returns a bundled info package
|
||||||
|
r = self.session.get(url)
|
||||||
|
soup = BeautifulSoup(r.text, "lxml")
|
||||||
|
return self.__parseTable(soup, url, verbose, __printed)
|
||||||
|
|
||||||
|
|
||||||
|
# Submit
|
||||||
|
def submit(self, files: list, judge=True, wait=True, silent=True):
|
||||||
|
|
||||||
|
# Find the form with submit and store the action as url
|
||||||
|
# Store then the data-suffixes as file_types - dictionary
|
||||||
|
|
||||||
|
form = self.soup.find("form")
|
||||||
|
if not form:
|
||||||
|
raise IllegalAction(message="You cannot submit to this assignment.")
|
||||||
|
|
||||||
|
url = "https://themis.housing.rug.nl" + form["action"]
|
||||||
|
file_types = loads(form["data-suffixes"])
|
||||||
|
|
||||||
|
if isinstance(files, str):
|
||||||
|
temp = []
|
||||||
|
temp.append(files)
|
||||||
|
files = temp
|
||||||
|
|
||||||
|
# Package the files up into files[]
|
||||||
|
# DEBUG: Uncomment for better clarity
|
||||||
|
# print("Submitting files:")
|
||||||
|
# [print(f) for f in files]
|
||||||
|
packaged_files = []
|
||||||
|
data = {}
|
||||||
|
found_type = ""
|
||||||
|
for file in files:
|
||||||
|
for t in file_types:
|
||||||
|
if t in file:
|
||||||
|
found_type = file_types[t]
|
||||||
|
break
|
||||||
|
if not found_type:
|
||||||
|
raise IllegalAction(message="Illegal filetype for this assignment.")
|
||||||
|
|
||||||
|
packaged_files.append((f"files[]", (file, open(file, "rb"), "text/x-csrc")))
|
||||||
|
|
||||||
|
data = {"judgenow": "true" if judge else "false", "judgeLanguage": found_type}
|
||||||
|
|
||||||
|
resp = self.session.post(url, files=packaged_files, data=data)
|
||||||
|
|
||||||
|
# Close each file
|
||||||
|
i = 0
|
||||||
|
for f in packaged_files:
|
||||||
|
f[1][1].close()
|
||||||
|
|
||||||
|
if not wait:
|
||||||
|
return resp.url if "@submissions" in resp.url else None
|
||||||
|
|
||||||
|
return self.__waitForResult(resp.url, not silent, [])
|
||||||
|
@ -31,6 +31,7 @@ class Themis:
|
|||||||
# get the csrf token and add it to payload
|
# get the csrf token and add it to payload
|
||||||
csrfToken = soup.find('input',attrs = {'name':'_csrf'})['value']
|
csrfToken = soup.find('input',attrs = {'name':'_csrf'})['value']
|
||||||
data['_csrf'] = csrfToken
|
data['_csrf'] = csrfToken
|
||||||
|
data['sudo'] = user.lower()
|
||||||
|
|
||||||
# Login
|
# Login
|
||||||
r = s.post(url,data=data,headers = headers)
|
r = s.post(url,data=data,headers = headers)
|
||||||
|
25
src/main.py
Normal file
25
src/main.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
from Themis import Themis
|
||||||
|
def main():
|
||||||
|
# Debug
|
||||||
|
themis = Themis("s5230837","Bobit0Drog@231")
|
||||||
|
year = themis.getYear(2023, 2024)
|
||||||
|
|
||||||
|
# pf = year.getCourse("Programming Fundamentals (for CS)")
|
||||||
|
# pf = pf.getExerciseGroups()
|
||||||
|
# print(pf[1].exercises[1].submit("main.c")) # <- this should throw error
|
||||||
|
|
||||||
|
# no_folders = year.getCourse("Computer Architecture")
|
||||||
|
# ca_ass = no_folders.getExerciseGroups()
|
||||||
|
ai = year.getCourse("Imperative Programming (for AI)")
|
||||||
|
ai = ai.getExerciseGroups()
|
||||||
|
print(ai[7].exercises[1].submit("suitcase.py", silent=False))
|
||||||
|
|
||||||
|
ads = year.getCourse("Algorithms and Data Structures for CS")
|
||||||
|
ads = ads.getExerciseGroups()
|
||||||
|
# print(ads[0].folders)
|
||||||
|
print(ads[0].folders[5].folders[0].exercises[0].submit(["texteditor.c", "texteditor.h"], silent=False))
|
||||||
|
# for ass in ca_ass:
|
||||||
|
# print(ass.exercises)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
28
src/suitcase.py
Normal file
28
src/suitcase.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
def suitcase(maxVolume, sizes, values, n):
|
||||||
|
|
||||||
|
dp = [[0 for _ in range(maxVolume + 1)] for _ in range(n + 1)]
|
||||||
|
|
||||||
|
for i in range(1, n + 1):
|
||||||
|
for j in range(1, maxVolume + 1):
|
||||||
|
if sizes[i - 1] <= j:
|
||||||
|
dp[i][j] = max(values[i - 1] + dp[i - 1][j - sizes[i - 1]], dp[i - 1][j])
|
||||||
|
else:
|
||||||
|
dp[i][j] = dp[i - 1][j]
|
||||||
|
|
||||||
|
return dp[n][maxVolume]
|
||||||
|
|
||||||
|
def main():
|
||||||
|
n, maxVolume = map(int, input().split())
|
||||||
|
sizes = []
|
||||||
|
values = []
|
||||||
|
|
||||||
|
for _ in range(n):
|
||||||
|
item, size, value = input().split()
|
||||||
|
sizes.append(int(size))
|
||||||
|
values.append(int(value))
|
||||||
|
|
||||||
|
maxSatisfaction = suitcase(maxVolume, sizes, values, n)
|
||||||
|
print(maxSatisfaction)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
159
src/texteditor.c
Normal file
159
src/texteditor.c
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "EditOperation.h"
|
||||||
|
#include "texteditor.h"
|
||||||
|
#include "LibStack.h"
|
||||||
|
|
||||||
|
TextEditor* createTextEditor(void) {
|
||||||
|
TextEditor *editor = malloc(sizeof(*editor));
|
||||||
|
// Don't forget to initialize the data structure(s) here
|
||||||
|
editor->text = malloc(10 * sizeof(*editor->text));
|
||||||
|
editor->length = 0;
|
||||||
|
editor->capacity = 10;
|
||||||
|
return editor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Think this is correct
|
||||||
|
void insertCharacter(TextEditor* editor, int pos, char character) {
|
||||||
|
// Implement the insert operation
|
||||||
|
if (editor->length == editor->capacity) {
|
||||||
|
editor->text = realloc(editor->text, 2 * editor->capacity * sizeof(*editor->text));
|
||||||
|
editor->capacity *= 2;
|
||||||
|
}
|
||||||
|
// Shift all characters to the right
|
||||||
|
for (int i = editor->length; i > pos; i--) {
|
||||||
|
editor->text[i] = editor->text[i - 1];
|
||||||
|
}
|
||||||
|
editor->text[pos] = character;
|
||||||
|
editor->length++;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// This too
|
||||||
|
void deleteCharacter(TextEditor* editor, int pos) {
|
||||||
|
// Implement the delete operation
|
||||||
|
if (editor->length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Shift all characters to the left
|
||||||
|
for (int i = pos; i < editor->length - 1; i++) {
|
||||||
|
editor->text[i] = editor->text[i + 1];
|
||||||
|
}
|
||||||
|
editor->length--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The issue lies within the mem allocation of the stacks
|
||||||
|
void undo(TextEditor* editor, Stack* undoStack, Stack* redoStack) {
|
||||||
|
// Optional for the bonus exercise
|
||||||
|
if (isEmptyStack(*undoStack)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
EditOperation operation = pop(undoStack);
|
||||||
|
if (operation.type == INSERT) {
|
||||||
|
deleteCharacter(editor, operation.position);
|
||||||
|
} else {
|
||||||
|
insertCharacter(editor, operation.position, operation.character);
|
||||||
|
}
|
||||||
|
push(operation, redoStack);
|
||||||
|
}
|
||||||
|
|
||||||
|
void redo(TextEditor* editor, Stack* undoStack, Stack* redoStack) {
|
||||||
|
// Optional for the bonus exercise
|
||||||
|
if (isEmptyStack(*redoStack)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
EditOperation operation = pop(redoStack);
|
||||||
|
if (operation.type == INSERT) {
|
||||||
|
insertCharacter(editor, operation.position, operation.character);
|
||||||
|
} else {
|
||||||
|
deleteCharacter(editor, operation.position);
|
||||||
|
}
|
||||||
|
push(operation, undoStack);
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroyTextEditor(TextEditor* editor) {
|
||||||
|
// Free the memory allocated for the data structure(s)
|
||||||
|
free(editor->text);
|
||||||
|
free(editor);
|
||||||
|
}
|
||||||
|
|
||||||
|
void printText(TextEditor* editor) {
|
||||||
|
// Handle empty case
|
||||||
|
if (editor->length == 0) {
|
||||||
|
printf("EMPTY\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print the text stored in the editor
|
||||||
|
for (int i = 0; i < editor->length; i++) {
|
||||||
|
printf("%c", editor->text[i]);
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
|
||||||
|
TextEditor* editor = createTextEditor();
|
||||||
|
char command;
|
||||||
|
int pos;
|
||||||
|
char character;
|
||||||
|
|
||||||
|
// Initialize stacks
|
||||||
|
Stack undoStack;
|
||||||
|
Stack redoStack;
|
||||||
|
undoStack = newStack(1);
|
||||||
|
redoStack = newStack(1);
|
||||||
|
|
||||||
|
while(1) {
|
||||||
|
scanf(" %c", &command);
|
||||||
|
switch (command) {
|
||||||
|
// Insert a character at a given position
|
||||||
|
case 'i':
|
||||||
|
scanf("%d %c", &pos, &character);
|
||||||
|
insertCharacter(editor, pos, character);
|
||||||
|
EditOperation operation = {INSERT, character, pos};
|
||||||
|
|
||||||
|
// Stack operations
|
||||||
|
doubleStackSize(&undoStack);
|
||||||
|
push(operation, &undoStack);
|
||||||
|
break;
|
||||||
|
// Delete a character at a given position
|
||||||
|
case 'd':
|
||||||
|
scanf("%d", &pos);
|
||||||
|
character = editor->text[pos];
|
||||||
|
deleteCharacter(editor, pos);
|
||||||
|
EditOperation operation1 = {DELETE, character, pos};
|
||||||
|
|
||||||
|
doubleStackSize(&undoStack);
|
||||||
|
push(operation1, &undoStack);
|
||||||
|
break;
|
||||||
|
// Undo the last operation
|
||||||
|
case 'u':
|
||||||
|
undo(editor, &undoStack, &redoStack);
|
||||||
|
break;
|
||||||
|
// Redo the last operation
|
||||||
|
case 'r':
|
||||||
|
redo(editor, &undoStack, &redoStack);
|
||||||
|
break;
|
||||||
|
// Print and quit
|
||||||
|
case 'q':
|
||||||
|
printText(editor);
|
||||||
|
destroyTextEditor(editor);
|
||||||
|
freeStack(undoStack);
|
||||||
|
freeStack(redoStack);
|
||||||
|
return 0;
|
||||||
|
// Unknown command
|
||||||
|
default:
|
||||||
|
printf("Unknown command.\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
14
src/texteditor.h
Normal file
14
src/texteditor.h
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#ifndef TEXTEDITOR_H
|
||||||
|
#define TEXTEDITOR_H
|
||||||
|
|
||||||
|
#include "LibStack.h"
|
||||||
|
|
||||||
|
typedef struct TextEditor {
|
||||||
|
// Store the data structure in here
|
||||||
|
char *text;
|
||||||
|
int length;
|
||||||
|
int capacity;
|
||||||
|
} TextEditor;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user