microsoft/qdk
Publicmirrored fromhttps://github.com/microsoft/qdkAvailable
source/pip/src/fs.rs
203lines · modecode
| 1 | // Copyright (c) Microsoft Corporation. |
| 2 | // Licensed under the MIT License. |
| 3 | |
| 4 | use std::{ |
| 5 | path::{Path, PathBuf}, |
| 6 | sync::Arc, |
| 7 | }; |
| 8 | |
| 9 | use miette::miette; |
| 10 | use pyo3::{ |
| 11 | exceptions::PyException, |
| 12 | prelude::*, |
| 13 | types::{PyDict, PyList, PyString, PyTuple}, |
| 14 | }; |
| 15 | use qsc::project::{DirEntry, EntryType, FileSystem}; |
| 16 | |
| 17 | pub(crate) fn file_system( |
| 18 | py: Python, |
| 19 | read_file: PyObject, |
| 20 | list_directory: PyObject, |
| 21 | resolve_path: PyObject, |
| 22 | fetch_github: PyObject, |
| 23 | ) -> impl FileSystem + '_ { |
| 24 | Py { |
| 25 | py, |
| 26 | fs_hooks: FsHooks { |
| 27 | read_file, |
| 28 | list_directory, |
| 29 | resolve_path, |
| 30 | fetch_github, |
| 31 | }, |
| 32 | } |
| 33 | } |
| 34 | |
| 35 | struct FsHooks { |
| 36 | read_file: PyObject, |
| 37 | list_directory: PyObject, |
| 38 | resolve_path: PyObject, |
| 39 | fetch_github: PyObject, |
| 40 | } |
| 41 | |
| 42 | #[derive(Debug)] |
| 43 | struct Entry { |
| 44 | ty: EntryType, |
| 45 | path: String, |
| 46 | name: String, |
| 47 | } |
| 48 | |
| 49 | impl DirEntry for Entry { |
| 50 | type Error = pyo3::PyErr; |
| 51 | |
| 52 | fn entry_type(&self) -> Result<EntryType, Self::Error> { |
| 53 | Ok(self.ty) |
| 54 | } |
| 55 | |
| 56 | fn entry_name(&self) -> String { |
| 57 | self.name.clone() |
| 58 | } |
| 59 | |
| 60 | fn path(&self) -> PathBuf { |
| 61 | PathBuf::from(&self.path) |
| 62 | } |
| 63 | } |
| 64 | |
| 65 | struct Py<'a> { |
| 66 | pub py: Python<'a>, |
| 67 | fs_hooks: FsHooks, |
| 68 | } |
| 69 | |
| 70 | impl FileSystem for Py<'_> { |
| 71 | type Entry = Entry; |
| 72 | |
| 73 | fn read_file(&self, path: &Path) -> miette::Result<(Arc<str>, Arc<str>)> { |
| 74 | read_file(self.py, &self.fs_hooks.read_file, path).map_err(|e| diagnostic_from(self.py, &e)) |
| 75 | } |
| 76 | |
| 77 | fn list_directory(&self, path: &Path) -> miette::Result<Vec<Self::Entry>> { |
| 78 | list_directory(self.py, &self.fs_hooks.list_directory, path) |
| 79 | .map_err(|e| diagnostic_from(self.py, &e)) |
| 80 | } |
| 81 | |
| 82 | fn resolve_path(&self, base: &Path, path: &Path) -> miette::Result<PathBuf> { |
| 83 | resolve_path(self.py, &self.fs_hooks.resolve_path, base, path) |
| 84 | .map_err(|e| diagnostic_from(self.py, &e)) |
| 85 | } |
| 86 | |
| 87 | fn fetch_github( |
| 88 | &self, |
| 89 | owner: &str, |
| 90 | repo: &str, |
| 91 | r#ref: &str, |
| 92 | path: &str, |
| 93 | ) -> miette::Result<Arc<str>> { |
| 94 | fetch_github( |
| 95 | self.py, |
| 96 | &self.fs_hooks.fetch_github, |
| 97 | owner, |
| 98 | repo, |
| 99 | r#ref, |
| 100 | path, |
| 101 | ) |
| 102 | .map_err(|e| diagnostic_from(self.py, &e)) |
| 103 | } |
| 104 | } |
| 105 | |
| 106 | fn read_file(py: Python, read_file: &PyObject, path: &Path) -> PyResult<(Arc<str>, Arc<str>)> { |
| 107 | let read_file_result = read_file.call1(py, PyTuple::new(py, &[path.to_string_lossy()])?)?; |
| 108 | |
| 109 | let tuple = read_file_result.downcast_bound::<PyTuple>(py)?; |
| 110 | |
| 111 | Ok((get_tuple_string(tuple, 0)?, get_tuple_string(tuple, 1)?)) |
| 112 | } |
| 113 | |
| 114 | fn list_directory(py: Python, list_directory: &PyObject, path: &Path) -> PyResult<Vec<Entry>> { |
| 115 | let list_directory_result = |
| 116 | list_directory.call1(py, PyTuple::new(py, &[path.to_string_lossy()])?)?; |
| 117 | |
| 118 | list_directory_result |
| 119 | .downcast_bound::<PyList>(py)? |
| 120 | .into_iter() |
| 121 | .map(|e| { |
| 122 | let dict = e.downcast::<PyDict>()?; |
| 123 | let entry_type = match get_dict_string(dict, "type")?.to_string().as_str() { |
| 124 | "file" => EntryType::File, |
| 125 | "folder" => EntryType::Folder, |
| 126 | "symlink" => EntryType::Symlink, |
| 127 | _ => Err(PyException::new_err( |
| 128 | "expected valid value for `type` in list_directory result", |
| 129 | ))?, |
| 130 | }; |
| 131 | |
| 132 | Ok(Entry { |
| 133 | ty: entry_type, |
| 134 | path: get_dict_string(dict, "path")?.to_string(), |
| 135 | name: get_dict_string(dict, "entry_name")?.to_string(), |
| 136 | }) |
| 137 | }) |
| 138 | .collect() // Returns all values if all Ok, or first Err |
| 139 | } |
| 140 | |
| 141 | fn resolve_path( |
| 142 | py: Python, |
| 143 | resolve_path: &PyObject, |
| 144 | base: &Path, |
| 145 | path: &Path, |
| 146 | ) -> PyResult<PathBuf> { |
| 147 | let resolve_path_result = resolve_path.call1( |
| 148 | py, |
| 149 | PyTuple::new(py, &[base.to_string_lossy(), path.to_string_lossy()])?, |
| 150 | )?; |
| 151 | |
| 152 | Ok(PathBuf::from( |
| 153 | resolve_path_result |
| 154 | .downcast_bound::<PyString>(py)? |
| 155 | .str()? |
| 156 | .to_string(), |
| 157 | )) |
| 158 | } |
| 159 | |
| 160 | fn fetch_github( |
| 161 | py: Python, |
| 162 | fetch_github: &PyObject, |
| 163 | owner: &str, |
| 164 | repo: &str, |
| 165 | r#ref: &str, |
| 166 | path: &str, |
| 167 | ) -> PyResult<Arc<str>> { |
| 168 | let fetch_github_result = |
| 169 | fetch_github.call1(py, PyTuple::new(py, [owner, repo, r#ref, path])?)?; |
| 170 | |
| 171 | Ok(fetch_github_result |
| 172 | .downcast_bound::<PyString>(py)? |
| 173 | .to_string() |
| 174 | .into()) |
| 175 | } |
| 176 | |
| 177 | fn get_tuple_string(tuple: &Bound<'_, PyTuple>, index: usize) -> PyResult<Arc<str>> { |
| 178 | Ok(tuple |
| 179 | .get_item(index)? |
| 180 | .downcast::<PyString>()? |
| 181 | .to_string() |
| 182 | .into()) |
| 183 | } |
| 184 | |
| 185 | fn get_dict_string<'a>(dict: &Bound<'a, PyDict>, key: &'a str) -> PyResult<Bound<'a, PyString>> { |
| 186 | match dict.get_item(key)? { |
| 187 | Some(item) => Ok(item.downcast::<PyString>()?.str()?), |
| 188 | None => Err(PyException::new_err(format!("missing key `{key}` in dict"))), |
| 189 | } |
| 190 | } |
| 191 | |
| 192 | fn diagnostic_from(py: Python<'_>, err: &PyErr) -> miette::Report { |
| 193 | if let Some(traceback) = err.traceback(py) { |
| 194 | match traceback.format() { |
| 195 | Ok(traceback) => miette!(format!("{err}\n{traceback}",)), |
| 196 | Err(traceback_err) => { |
| 197 | miette!(format!("{err}\nerror getting traceback: {traceback_err}",)) |
| 198 | } |
| 199 | } |
| 200 | } else { |
| 201 | miette!(err.to_string()) |
| 202 | } |
| 203 | } |
| 204 | |