note description: "Component implementing the wiki database, and wiki item resolving" date: "$Date$" revision: "$Revision$" class WDOCS_MANAGER inherit WIKI_LINK_RESOLVER rename wiki_url as link_to_wiki_url, missing_wiki_url as link_to_missing_wiki_url redefine link_to_missing_wiki_url end WIKI_IMAGE_RESOLVER rename wiki_url as image_to_wiki_url, url as image_to_url end WIKI_TEMPLATE_RESOLVER rename content as template_content end WIKI_PAGE_TITLE_NORMALIZER WDOCS_HELPER WIKI_FILE_RESOLVER REFACTORING_HELPER WDOCS_DATA_ACCESS WDOCS_SHARED_ENCODER create make, make_default feature {NONE} -- Initialization make (a_wiki_dir: PATH; a_version_id: like version_id; a_tmp_dir: PATH) do wiki_database_path := a_wiki_dir version_id := a_version_id tmp_dir := a_tmp_dir initialize end make_default (a_wiki_dir: PATH; a_version_id: like version_id; a_tmp_dir: PATH) do make (a_wiki_dir, a_version_id, a_tmp_dir) is_default_version := True end initialize -- Initialize values. do create {WDOCS_FS_STORAGE} storage.make (Current, wiki_database_path, tmp_dir) end feature -- Access wiki_database_path: PATH version_id: READABLE_STRING_GENERAL is_default_version: BOOLEAN -- Is Current manager for default version? -- i.e no need to include it in url. tmp_dir: PATH is_version_id (v: detachable like version_id): BOOLEAN -- Is `v' same version as `version_id' ? do if v = Void then Result := is_default_version else Result := version_id.is_case_insensitive_equal (v) end end feature -- Persistency storage: WDOCS_STORAGE data: like storage -- Same as `storage'. --| Kept for backward compatibility do Result := storage end feature -- Data access file_path (a_filename: READABLE_STRING_GENERAL; a_book_name: detachable READABLE_STRING_GENERAL): detachable PATH do Result := storage.file_path (a_filename, a_book_name) end image_path (a_title: READABLE_STRING_GENERAL; a_book_name: detachable READABLE_STRING_GENERAL): detachable PATH do Result := storage.image_path (a_title, a_book_name) end feature -- Basic operations reload_data -- Discard memory cache for `data', -- and reload it from file system. do storage.reload end refresh_data -- Refresh manager data. do storage.refresh end refresh_page_data (bn: READABLE_STRING_GENERAL; pg: WIKI_PAGE) do storage.refresh_page_data (bn, pg) reload_data end feature -- Access book_names: ITERABLE [READABLE_STRING_32] -- Available book names. do Result := storage.book_names end books_with_root_page: ITERABLE [WIKI_BOOK] -- Available books filled with only the root page. do Result := storage.books_with_root_page end has_book_named (a_bookid: READABLE_STRING_GENERAL): BOOLEAN -- Has book named `a_bookid' ? do across book_names as ic until Result loop Result := a_bookid.same_string (ic.item) end end book (a_bookid: READABLE_STRING_GENERAL): detachable WIKI_BOOK -- Book named `a_bookid' if any. do Result := storage.book (a_bookid) end index_page: detachable like new_wiki_page -- Main documentation index page. local p: PATH ut: FILE_UTILITIES do p := wiki_database_path.extended ("index.wiki") if ut.file_path_exists (p) then Result := new_wiki_page ("Documentation", "") Result.set_path (p) end end page (a_bookpage: detachable READABLE_STRING_GENERAL; a_bookid: READABLE_STRING_GENERAL): detachable like new_wiki_page -- Wiki page for book `a_bookid', and if provided title `a_bookpage', otherwise the root page of related wiki book. local n: READABLE_STRING_GENERAL p: PATH f: PLAIN_TEXT_FILE wi: WIKI_INDEX pg: detachable like new_wiki_page ut: FILE_UTILITIES do if a_bookpage = Void then n := "index" else n := a_bookpage end p := wiki_database_path.extended (a_bookid).extended ("book.index") if ut.file_path_exists (p) then create wi.make (a_bookid.as_string_8, p) pg := wi.page_by_id (n) else pg := storage.page (n, a_bookid) end if attached {WIKI_BOOK_PAGE} pg as l_book_page then p := wiki_database_path.extended (l_book_page.src) else p := wiki_database_path.extended (a_bookid).extended (n) end p := p.appended_with_extension ("wiki") create f.make_with_path (p) if not f.exists then if attached {WIKI_BOOK_PAGE} pg as l_book_pg then p := wiki_database_path.extended (l_book_pg.src) else p := wiki_database_path.extended (a_bookid).extended (n) end p := p.extended ("index.wiki") create f.make_with_path (p) end if f.exists then if pg /= Void then Result := pg else Result := new_wiki_page (n.as_string_8, n.as_string_8) end Result.get_structure (p) end end page_by_title (a_page_title: READABLE_STRING_GENERAL; a_bookid: detachable READABLE_STRING_GENERAL): detachable like new_wiki_page -- Wiki page with title `a_page_title', and in book related to `a_bookid' if provided. do Result := storage.page_by_title (a_page_title, a_bookid) if Result = Void and a_bookid /= Void then -- Find in other books. Result := storage.page_by_title (a_page_title, Void) end end page_by_link_title (a_page_link_title: READABLE_STRING_GENERAL; a_bookid: detachable READABLE_STRING_GENERAL): detachable like new_wiki_page -- Wiki page with link_title `a_page_link_title', and in book related to `a_bookid' if provided. do Result := page_by_metadata ("link_title", a_page_link_title, a_bookid, True) end page_by_metadata (a_metadataname, a_metadata_value: READABLE_STRING_GENERAL; a_bookid: detachable READABLE_STRING_GENERAL; is_caseless: BOOLEAN): detachable like new_wiki_page do Result := storage.page_by_metadata (a_metadataname, a_metadata_value, a_bookid, is_caseless) if Result = Void and a_bookid /= Void then -- Check in all books. Result := page_by_metadata (a_metadataname, a_metadata_value, Void, is_caseless) end end page_by_uuid (a_page_uuid: READABLE_STRING_GENERAL; a_bookid: detachable READABLE_STRING_GENERAL): detachable like new_wiki_page -- Wiki page associated to UUID `a_page_uuid'. do Result := storage.page_by_uuid (a_page_uuid, a_bookid) end book_and_page_by_path (a_path: PATH): detachable TUPLE [bookid: READABLE_STRING_GENERAL; page: like new_wiki_page] do if attached storage.page_book_and_title_for_path (a_path) as d then if attached page_by_title (d.title, d.bookid) as wp then Result := [d.bookid, wp] end end end wiki_text (p: WIKI_PAGE): detachable READABLE_STRING_8 do Result := storage.wiki_text (p) end feature -- Normalized title normalized_title (a_title: READABLE_STRING_GENERAL): STRING_32 do Result := normalized_fs_text (a_title) end url_friendly_wiki_name (wn: READABLE_STRING_GENERAL): STRING_32 do Result := normalized_title (wn) end feature -- Access: link link_to_wiki_url (a_link: WIKI_LINK; a_page: detachable WIKI_PAGE): detachable STRING local db,p,pp,lnk: detachable PATH f: RAW_FILE wi: WIKI_INDEX l_book_name: like book_name do if a_page /= Void then l_book_name := book_name (a_page) end if attached page_by_title (a_link.name, l_book_name) as pg then if is_default_version then Result := wiki_page_uri_path (pg, l_book_name, Void) else Result := wiki_page_uri_path (pg, l_book_name, version_id) end end if Result = Void and then attached page_by_link_title (a_link.name, l_book_name) as pg then -- Try to find using the `link_title' prop if is_default_version then Result := wiki_page_uri_path (pg, l_book_name, Void) else Result := wiki_page_uri_path (pg, l_book_name, version_id) end end if Result = Void then -- Try to find a book.index if any. db := wiki_database_path if p = Void then p := db else p := p.parent end from pp := p lnk := pp.extended ("book.index") create f.make_with_path (lnk) until f.exists or pp.same_as (db) or pp.is_current_symbol loop pp := pp.parent lnk := pp.extended ("book.index") create f.make_with_path (lnk) end if f.exists then create wi.make ("book", f.path) if attached wi.page (a_link.name) as pg then if is_default_version then Result := wiki_page_uri_path (pg, l_book_name, Void) else Result := wiki_page_uri_path (pg, l_book_name, version_id) end end end end end link_to_missing_wiki_url (a_link: WIKI_LINK; a_page: detachable WIKI_PAGE): detachable STRING -- URL accessing the missing wiki link `a_link' in the context of `a_page'. do Result := url_friendly_wiki_name (a_link.name) end wiki_page_uri_path (pg: WIKI_PAGE; a_book_name: detachable READABLE_STRING_GENERAL; a_version_id: detachable READABLE_STRING_GENERAL): STRING local utf: UTF_CONVERTER l_path: detachable READABLE_STRING_32 l_book_name: detachable READABLE_STRING_GENERAL do if attached page_metadata (pg, <<"path">>) as md then l_path := md.item ("path") end if l_path /= Void then Result := "/" + utf.string_32_to_utf_8_string_8 (l_path) else Result := "/doc" if a_version_id /= Void then Result.append ("/version/" + percent_encoder.percent_encoded_string (a_version_id)) end l_book_name := a_book_name if l_book_name = Void then l_book_name := book_name (pg) end if l_book_name /= Void then Result.append ("/" + wiki_name_to_url_encoded_string (url_friendly_wiki_name (l_book_name))) Result.append ("/" + wiki_name_to_url_encoded_string (url_friendly_wiki_name (pg.title))) elseif attached {WIKI_BOOK_PAGE} pg as l_book_pg then Result.append ("/" + l_book_pg.src) else Result.append ("/") -- FIXME end end end wiki_page_title (pg: WIKI_PAGE): STRING local utf: UTF_CONVERTER do if pg.metadata_count > 0 then Result := pg.title -- metadata already associated, so `title' should have expected value. else if attached page_metadata (pg, <<"title">>) as md and then attached md.item ("title") as l_title and then not l_title.is_whitespace then Result := utf.escaped_utf_32_string_to_utf_8_string_8 (l_title) else Result := pg.title end end end wiki_page_link_title (pg: WIKI_PAGE): STRING local utf: UTF_CONVERTER l_title: detachable READABLE_STRING_32 do l_title := pg.metadata ("link_title") if l_title = Void or else l_title.is_whitespace then if attached page_metadata (pg, <<"link_title">>) as md then l_title := md.item ("link_title") end end if l_title /= Void and then not l_title.is_whitespace then Result := utf.escaped_utf_32_string_to_utf_8_string_8 (l_title) else Result := wiki_page_title (pg) end end page_metadata (pg: WIKI_PAGE; a_restricted_names: detachable ITERABLE [READABLE_STRING_GENERAL]): detachable WDOCS_METADATA -- Metadata for page `pg', -- if `a_restricted_names' is set, include only those metadata names after `a_restricted_names' items. do Result := storage.page_metadata (pg, a_restricted_names) ensure result_attached_implies_exists: Result /= Void implies not attached {WDOCS_METADATA_FILE} Result as mdf or else mdf.exists end metadata (a_source: PATH; a_restricted_names: detachable ITERABLE [READABLE_STRING_GENERAL]): detachable WDOCS_METADATA -- Metadata for page `pg', -- if `a_restricted_names' is set, include only those metadata names after `a_restricted_names' items. do Result := storage.metadata (a_source, a_restricted_names) ensure result_attached_implies_exists: Result /= Void implies ( (not attached {WDOCS_METADATA_FILE} Result as en_mdf or else en_mdf.exists) or (not attached {WDOCS_METADATA_WIKI} Result as en_mdw or else en_mdw.exists) ) end feature -- Access: File file_to_url (a_file: WIKI_FILE_LINK; a_page: detachable WIKI_PAGE): detachable STRING -- URL accessing the file `a_file'. do if not a_file.name.has ('/') then create Result.make_from_string ("/doc-file") if not is_default_version then Result.append ("/version/" + percent_encoder.percent_encoded_string (version_id)) end Result.append_character ('/') if a_page /= Void and then attached book_name (a_page) as l_book_name then Result.append (percent_encoder.percent_encoded_string (l_book_name)) elseif attached file_path (a_file.name, Void) as p and then attached p.parent.parent.entry as l_book_entry then Result.append (percent_encoder.percent_encoded_string (l_book_entry.name)) end Result.append ("/" + a_file.name) end end feature -- Access: Image image_to_wiki_url (a_link: WIKI_IMAGE_LINK; a_page: detachable WIKI_PAGE): detachable STRING do if not a_link.name.has ('/') then create Result.make_from_string ("/doc-image") if not is_default_version then Result.append ("/version/" + percent_encoder.percent_encoded_string (version_id)) end Result.append ("/" + a_link.name) end end image_to_url (a_link: WIKI_IMAGE_LINK; a_page: detachable WIKI_PAGE): detachable STRING local l_book_name: detachable READABLE_STRING_32 db,p,pp,img: detachable PATH d: DIRECTORY f: RAW_FILE s0,s2: STRING_32 l_image_path: detachable PATH bak: INTEGER do if not a_link.name.has ('/') then if a_page /= Void then l_book_name := book_name (a_page) end -- `l_book_name' could be Void l_image_path := image_path (a_link.name, l_book_name) if l_image_path /= Void then s0 := l_image_path.parent.parent.name if l_book_name = Void then if attached l_image_path.parent.parent.entry as l_book_entry then l_book_name := l_book_entry.name else check has_book_name: False end l_book_name := {WDOCS_PAGES_DATA}.common_book_name end end else db := wiki_database_path if a_page /= Void then p := a_page.path end if p = Void then p := db else p := p.parent end from pp := p img := pp.extended ("_images") create d.make_with_path (img) until d.exists or pp.same_as (db) or pp.is_current_symbol loop bak := bak + 1 pp := pp.parent img := pp.extended ("_images") create d.make_with_path (img) end s0 := pp.name if d.exists then if attached d.path.parent.entry as l_book_entry then l_book_name := l_book_entry.name else check has_book_name: False end l_book_name := {WDOCS_PAGES_DATA}.common_book_name end l_image_path := image_path (a_link.name, l_book_name) if l_image_path = Void then p := d.path.extended (a_link.name).appended_with_extension ("png") create f.make_with_path (p) if f.exists then storage.save_image_path (p, a_link.name, l_book_name) l_image_path := p end end end end if l_image_path /= Void and l_book_name /= Void then create f.make_with_path (l_image_path) if f.exists then s2 := l_image_path.name if s2.starts_with (s0) then s2 := s2.substring (s0.count + 1 + 1, s2.count) create p.make_from_string (s2) else p := f.path bak := 0 end create Result.make_from_string ("/doc-image") if not is_default_version then Result.append ("/version/" + percent_encoder.percent_encoded_string (version_id)) end if a_page /= Void then Result.append ("/" + percent_encoder.percent_encoded_string (l_book_name)) across p.components as ic loop if not Result.is_empty then Result.append_character ('/') end Result.append (percent_encoder.percent_encoded_string (ic.item.name)) end else Result.append ("/" + a_link.name) -- This commented code was used to try to compute relative path, give another try later. -- create Result.make (p.name.count) -- from -- until -- bak <= 1 -- loop -- if not Result.is_empty then -- Result.append_character ('/') -- end -- Result.append ("..") -- bak := bak - 1 -- end -- across -- p.components as ic -- loop -- if not Result.is_empty then -- Result.append_character ('/') -- end -- Result.append (percent_encoder.percent_encoded_string (ic.item.name)) -- end end end end end end feature -- Access: Template template_content (a_template: WIKI_TEMPLATE; a_page: detachable WIKI_PAGE): detachable STRING -- Text content for template `a_template' in the context of `a_page' if precised. do Result := storage.template_content (a_template, a_page) end feature -- Helpers book_name (a_page: WIKI_PAGE): detachable READABLE_STRING_32 local db, p: detachable PATH pp: detachable PATH do p := a_page.path if p /= Void then p := p.absolute_path.canonical_path db := wiki_database_path.absolute_path.canonical_path if p.name.starts_with (db.name) then -- Search book folder, which has `db' as parent. from pp := p until pp = Void or else (pp.same_as (db) or pp.is_current_symbol) loop p := pp pp := p.parent if p.same_as (pp) then pp := Void end end if p.same_as (db) then Result := Void elseif attached p.entry as e then Result := e.name end else end end if Result /= Void and then not across book_names as ic some ic.item.same_string (Result) end then -- Found book name is unknown Result := Void end end feature -- Factory new_wiki_page (a_title: READABLE_STRING_8; a_parent_key: READABLE_STRING_8): WIKI_BOOK_PAGE -- Instantiate a new wiki page with title `a_title' and a parent key `a_parent_key'. do Result := storage.new_page (a_title, a_parent_key) end new_wiki_child_page (a_title: READABLE_STRING_8; a_parent: like new_wiki_page): WIKI_BOOK_PAGE -- Instantiate a new wiki page with title `a_title' with parent `a_parent'. do Result := new_wiki_page (a_title, a_parent.key) end end